Spring IoC 容器原理

Spring 是什么

  • Spring 是 Java 世界應(yīng)用的事實(shí)標(biāo)準(zhǔn)篙骡。(事實(shí)標(biāo)準(zhǔn)是指非由標(biāo)準(zhǔn)化組織制定的囚似,而是由處于技術(shù)領(lǐng)先地位的企業(yè)、企業(yè)集團(tuán)制定滨攻,由市場實(shí)際接納的技術(shù)標(biāo)準(zhǔn))
  • 沒有 Spring 的年代我們怎么開發(fā) Java 應(yīng)用够话?
    1.一個(gè) main 程序打天下蓝翰,這么做規(guī)模上來以后難以維護(hù),就是異常災(zāi)難女嘲。
    2.根據(jù)功能拆分為多個(gè)模塊畜份,拆分并且手動管理所有的依賴,依賴關(guān)系紛繁復(fù)雜欣尼。
  • Spring 容器就是一個(gè) IoC 容器爆雹。
  • Spring MVC 基于 Spring 和 Servlet 的 Web 應(yīng)?框架。Servlet 就是一個(gè)小服務(wù)器愕鼓,假如我們開發(fā)一個(gè) Web 應(yīng)用钙态,一個(gè)進(jìn)程必須去監(jiān)聽某個(gè)端口,世界上所有的人都可以通過這個(gè)端口來訪問你的程序菇晃。我們將這種最底層的册倒,處理交互的東西叫做 Servlet(Java 中大名鼎鼎的 Servlet 容器有 Tomcat 和 Jetty)。Servlet 的功能就是把外界的HTTP請求封裝成一個(gè)對象交給上層的 WebApp(Java 中 WebApp 的事實(shí)標(biāo)準(zhǔn)就是 Spring)磺送,WebApp 處理完后交給 Servlet驻子,然后 Servlet 幫你發(fā)送出去。所謂的 Spring MVC 就是支持 Servlet 和網(wǎng)絡(luò)請求的這么一個(gè) Spring 程序册着,換一句話說拴孤,Spring MVC 就是在 Spring 容器之上的一個(gè)功能更強(qiáng)大的應(yīng)用
  • Spring Boot 集成度和?動化程度更?。(Spring Boot 內(nèi)嵌了一個(gè) Servlet 容器甲捏,操作更加傻瓜了)Spring Boot 是近年來才火起來的,高度集成化鞭执。給我們開發(fā)人員的直觀感受就是司顿,減少了很多配置工作,但是我們喪失了對于底層的所有控制權(quán)兄纺。
  • 總的來說大溜,Spring 的出現(xiàn)解放了我們的雙手。

Spring 容器核心概念

下面我們演示一個(gè)最精簡的 Spring 程序估脆,模擬一個(gè)簡單的訂單服務(wù)钦奋。
OrderDao 主要完成一些數(shù)據(jù)庫操作。

public class OrderDao {
    public void select() {
        System.out.println("select from database");
    }
}

OrderSerive 提供對外的服務(wù)疙赠。

public class OrderService {
    private OrderDao orderDao;

    public void doSomething() {
        System.out.println("do some thing");
    }
}

首先需要引入一個(gè)第三方類庫 Spring Context(這里對于 context 比較好的解釋應(yīng)該是“環(huán)境”付材,“上下文”總覺得怪怪的)。現(xiàn)在圃阳,用的比較多的是 Spring Boot厌衔,在上古年代,人們使用 spring xml 來配置 Spring 容器捍岳。在 src/main/resource 下新建 config.xml 文件富寿。我們從官網(wǎng)抄來了配置文件睬隶,根據(jù)自己的需求,添加了兩個(gè) bean∫承欤現(xiàn)在解釋一下 bean 是什么苏潜。程序員都是非常浪漫的,Java 是咖啡变勇,而 Bean 是咖啡豆恤左。一個(gè) Bean 可以去依賴其他 Bean,本質(zhì)上贰锁,Bean 就是一個(gè) Java 對象(其實(shí) Spring 整個(gè)程序赃梧,也是一個(gè) Java 對象,只不過容器給我們的概念是一個(gè)很大的盒子)豌熄。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- bean definitions here -->
    <bean id="orderDao" class="com.github.lazyben.OrderDao"/>
    <bean id="orderService" class="com.github.lazyben.OrderService"/>
</beans>

容器是什么呢授嘀?其實(shí)就是 BeanFactory。現(xiàn)在我們可以去完成一些操作了锣险。發(fā)現(xiàn)了沒有整個(gè)程序我們沒有手工去處理依賴關(guān)系蹄皱。

public class SpringMain {
    public static void main(String[] args) {
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:config.xml");
        final OrderService orderService = (OrderService) beanFactory.getBean("orderService");
        final OrderDao orderDao = (OrderDao) beanFactory.getBean("orderDao");
        System.out.println(orderService);
        orderService.doSomething();
    }
}

此時(shí)報(bào)了一個(gè)空指針異常。因?yàn)榇藭r(shí) orderService 中的 orderDao 是 null芯肤。怎么辦呢巷折?

com.github.lazyben.OrderService@57f23557
Exception in thread "main" java.lang.NullPointerException
    at com.github.lazyben.OrderService.doSomething(OrderService.java:7)
    at com.github.lazyben.SpringMain.main(SpringMain.java:11)

最簡單的就是基于注解的,我們可以添加注解 @Inject(注入)或 @Resource(資源)或 @Autowired(自動裝配)崖咨。為什么我們添加一個(gè)注解就能完成注入依賴的工作呢锻拘?這個(gè)注解其實(shí)本身沒什么卵用,只是在運(yùn)行時(shí)被掃描到有這個(gè)注解击蹲,并完成了工作署拟。這里就不多贅述了,這不是本篇所要討論的內(nèi)容歌豺。另外推穷,我們還需要在 config.xml 中加入一行配置 <context:annotation-config/>。多說一句类咧,這里比較推薦使用 @Inject 注解馒铃,不僅Spring能識別它,Google 的 Guice 也能識別它痕惋。
Spring 也支持構(gòu)造器注入区宇。我們可以把這些注解寫在構(gòu)造器上(稱為構(gòu)造器注入),在屬性上寫注解有一個(gè)很要命的問題血巍,一旦脫離 Spring 就無法工作了萧锉,而寫在構(gòu)造器上我們可以在沒有 Spring 的情況下通過構(gòu)造器的方式傳入屬性(就和寫原生 Java 代碼一樣),寫單元測試也更加的方便述寡。構(gòu)造器注入是一種更好的實(shí)踐柿隙。

public class OrderService {
//    @Inject
//    @Resource
    @Autowired
    private OrderDao orderDao;

    public void doSomething() {
        orderDao.select();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <context:annotation-config/>
    <!-- bean definitions here -->
    <bean id="orderDao" class="com.github.lazyben.OrderDao"/>
    <bean id="orderService" class="com.github.lazyben.OrderService"/>
</beans>

打上斷點(diǎn)叶洞,神奇的事情發(fā)生了,orderService 中的 orderDao禀崖,和 orderDao 居然還是同一個(gè)對象衩辟。為什么上文說 Spring 解放了我們的雙手?道理就在這里波附。



這里我們回顧一下上文中提到的一些 Spring 的核心概念:

  • Bean
    容器中的最??作單元艺晴,通常為?個(gè) Java 對象霞掺。
  • BeanFactory/ApplicationContext
    它們都是容器本身對應(yīng)的Java對象滔金。
    容器本身對應(yīng)的 Java 對象。
  • 依賴注? Dependency Injection(DI)
    容器負(fù)責(zé)注?所有的依賴节预。
  • 控制反轉(zhuǎn) Inverse of Control(IoC)
    ?戶將控制權(quán)交給了容器仅财。

手寫一個(gè)最精簡的 IoC 容器

無非是以下幾個(gè)步驟狈究。定義Bean、加載 Bean 的定義盏求、實(shí)例化 Bean抖锥、查找依賴實(shí)現(xiàn)自動注入。

  1. 為了簡單碎罚,我們使用 .properties 格式文件作為配置文件磅废,在 src/main/resources 下創(chuàng)建配置文件 ioc.properties。propweties 使用等號劃分一個(gè)鍵值對荆烈。
orderDao=com.github.lazyben.OrderDao
orderService=com.github.lazyben.OrderServicew
  1. 使用一個(gè)古老的 Java 類 Properties 可以解析 properties 文件拯勉,于是我們拿到了配置文件。
public class MyIoCContainer {
    public static void main(String[] args) throws IOException {
        Properties properties = new Properties();
        properties.load(MyIoCContainer.class.getResourceAsStream("/ioc.properties"));
        System.out.println("properties = " + properties);
    }
}
  1. 根據(jù)拿到的配置憔购,使用反射生成實(shí)例谜喊。
Map<String, Object> beans = new HashMap<>();
properties.forEach((beanName, beanFullyQualifiedClassName) -> {
    try {
        Class<?> klass = Class.forName((String) beanFullyQualifiedClassName);
        Object newInstance = klass.getConstructor().newInstance();
        beans.put((String) beanName, newInstance);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
});
  1. 依賴注入。對于每一個(gè)實(shí)例倦始,獲取它的所有帶 @Autowired 注解的屬性,在 beans 中找到相應(yīng)的實(shí)例山卦,并注入依賴鞋邑。值得注意的是 private 屬性,我們不能直接用反射直接設(shè)置账蓉,需要設(shè)置權(quán)限 field.setAccessible(true);枚碗。
beans.forEach((beanName, beanInstance) -> dependencyInject(beanInstance, beans));

private static void dependencyInject(Object beanInstance, Map<String, Object> beans) {
    final List<Field> fieldsToBeAutoWired = Stream.of(beanInstance.getClass().getDeclaredFields())
            .filter(bean -> bean.getAnnotation(Autowired.class) != null)
            .collect(Collectors.toList());

    fieldsToBeAutoWired.forEach(field -> {
        try {
            final Object dependencyBeanInstance = beans.get(field.getName());
            field.setAccessible(true);
            field.set(beanInstance, dependencyBeanInstance);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    });
 }
  1. 源碼
public class MyIoCContainer {
    public static void main(String[] args) throws IOException {
        Properties properties = new Properties();
        properties.load(MyIoCContainer.class.getResourceAsStream("/ioc.properties"));

        Map<String, Object> beans = new HashMap<>();
        properties.forEach((beanName, beanFullyQualifiedClassName) -> {
            try {
                Class<?> klass = Class.forName((String) beanFullyQualifiedClassName);
                Object newInstance = klass.getConstructor().newInstance();
                beans.put((String) beanName, newInstance);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });

        beans.forEach((beanName, beanInstance) -> dependencyInject(beanInstance, beans));

        final OrderService orderService = (OrderService) beans.get("orderService");
        orderService.doSomething();
    }

    private static void dependencyInject(Object beanInstance, Map<String, Object> beans) {
        final List<Field> fieldsToBeAutoWired = Stream.of(beanInstance.getClass().getDeclaredFields())
                .filter(bean -> bean.getAnnotation(Autowired.class) != null)
                .collect(Collectors.toList());

        fieldsToBeAutoWired.forEach(field -> {
            try {
                final Object dependencyBeanInstance = beans.get(field.getName());
                field.setAccessible(true);
                field.set(beanInstance, dependencyBeanInstance);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        });
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市铸本,隨后出現(xiàn)的幾起案子肮雨,更是在濱河造成了極大的恐慌,老刑警劉巖箱玷,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怨规,死亡現(xiàn)場離奇詭異陌宿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)波丰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門壳坪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人掰烟,你說我怎么就攤上這事爽蝴。” “怎么了纫骑?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵蝎亚,是天一觀的道長。 經(jīng)常有香客問我先馆,道長发框,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任磨隘,我火速辦了婚禮缤底,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘番捂。我一直安慰自己个唧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布设预。 她就那樣靜靜地躺著徙歼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鳖枕。 梳的紋絲不亂的頭發(fā)上魄梯,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機(jī)與錄音宾符,去河邊找鬼酿秸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛魏烫,可吹牛的內(nèi)容都是我干的辣苏。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼哄褒,長吁一口氣:“原來是場噩夢啊……” “哼稀蟋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起呐赡,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤退客,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體萌狂,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡档玻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粥脚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窃肠。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖刷允,靈堂內(nèi)的尸體忽然破棺而出冤留,到底是詐尸還是另有隱情,我是刑警寧澤树灶,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布纤怒,位于F島的核電站,受9級特大地震影響天通,放射性物質(zhì)發(fā)生泄漏泊窘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一像寒、第九天 我趴在偏房一處隱蔽的房頂上張望烘豹。 院中可真熱鬧,春花似錦诺祸、人聲如沸携悯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽憔鬼。三九已至,卻和暖如春胃夏,著一層夾襖步出監(jiān)牢的瞬間轴或,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工仰禀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留照雁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓答恶,卻偏偏與公主長得像囊榜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子亥宿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容