1. 程式人生 > >IOC實現原理

IOC實現原理

IOC 也就是“控制反轉”了,不過更流行的叫法是“依賴注入”(DI - Dependency Injection)。聽起來挺高深,其實實現起來並不複雜。下面就看看如何來實現這個輕量級 IOC 框架。

從例項出發,先看看以下 Action 程式碼。

@Bean
public class ProductAction extends BaseAction {
    @Inject
    private ProductService productService;

    @Request("GET:/product/{id}")
    public Result getProductById
(long productId) { if (productId == 0) { return new Result(ERROR_PARAM); } Product product = productService.getProduct(productId); if (product != null) { return new Result(OK, product); } else { return new Result(ERROR_DATA); } } }

以上使用了兩個自定義註解:@Bean 與 @Inject。

在 ProductAction 類上標註了 @Bean 註解,表示該類會交給“容器”處理,以便加入依賴注入框架。
在 produceService 欄位上標註了 @Inject 註解,表示該欄位將會被注入進來,而無需 new ProductServiceImpl(),實際上 new 這件事情不是我們做的,而是框架做的,也就是說控制權正好反過來了,所以“依賴注入(DI)”也稱作“控制反轉(IoC)”。

那麼,應該如何實現依賴注入框架呢?首先還是看看下面的 BeanHelper 類吧。

public class BeanHelper
{
private static final Map<Class<?>, Object> beanMap = new HashMap<Class<?>, Object>(); static { try { // 獲取並遍歷所有的 Bean(帶有 @Bean 註解的類) List<Class<?>> beanClassList = ClassHelper.getClassListByAnnotation(Bean.class); for (Class<?> beanClass : beanClassList) { // 建立 Bean 例項 Object beanInstance = beanClass.newInstance(); // 將 Bean 例項放入 Bean Map 中(鍵為 Bean 類,值為 Bean 例項) beanMap.put(beanClass, beanInstance); } // 遍歷 Bean Map for (Map.Entry<Class<?>, Object> beanEntry : beanMap.entrySet()) { // 獲取 Bean 類與 Bean 例項 Class<?> beanClass = beanEntry.getKey(); Object beanInstance = beanEntry.getValue(); // 獲取 Bean 類中所有的欄位(不包括父類中的方法) Field[] beanFields = beanClass.getDeclaredFields(); if (ArrayUtil.isNotEmpty(beanFields)) { // 遍歷所有的 Bean 欄位 for (Field beanField : beanFields) { // 判斷當前 Bean 欄位是否帶有 @Inject 註解 if (beanField.isAnnotationPresent(Inject.class)) { // 獲取 Bean 欄位對應的介面 Class<?> interfaceClass = beanField.getType(); // 獲取該介面所有的實現類 List<Class<?>> implementClassList = ClassHelper.getClassListByInterface(interfaceClass); if (CollectionUtil.isNotEmpty(implementClassList)) { // 獲取第一個實現類 Class<?> implementClass = implementClassList.get(0); // 從 Bean Map 中獲取該實現類對應的實現類例項 Object implementInstance = beanMap.get(implementClass); // 設定該 Bean 欄位的值 beanField.setAccessible(true); // 必須使該欄位可訪問 beanField.set(beanInstance, implementInstance); } } } } } } catch (Exception e) { e.printStackTrace(); } } public static Map<Class<?>, Object> getBeanMap() { return beanMap; } @SuppressWarnings("unchecked") public static <T> T getBean(Class<T> cls) { return (T) beanMap.get(cls); } }

其實很簡單,依賴注入其實分為兩個步驟:1. 通過反射建立例項;2. 獲取需要注入的介面實現類並將其賦值給該介面。以上程式碼中的兩個 for 迴圈就是幹這兩件事情的。
依賴注入框架實現完畢!

請大家給出評價,謝謝!

有些網友對如何尋找介面的實現類的演算法有些疑問,如果一個介面存在兩個實現類,應該獲取哪一個實現類呢?我之前的做法是,只獲取第一個實現類。而 Spring 的做法是,直接報錯,應用都起不來。經過反覆思考,我做了一個慎重的決定,就是在介面上使用 @Impl 註解來強制指定哪個實現類。而 BeanHelper 在做依賴注入的時候,會首先判斷介面上是否有 @Impl 註解,如果有就獲取這個強制指定的實現類例項,否則就獲取所有實現類中的第一個實現類,仍然不會像 Spring 那樣讓應用報錯。下面是對 BeanHelper 類中部分程式碼的修改:

...
// 判斷當前 Bean 欄位是否帶有 @Inject 註解
if (beanField.isAnnotationPresent(Inject.class)) {
    // 獲取 Bean 欄位對應的介面
    Class<?> interfaceClass = beanField.getType();
    // 判斷介面上是否標註了 @Impl 註解
    Class<?> implementClass = null;
    if (interfaceClass.isAnnotationPresent(Impl.class)) {
        // 獲取強制指定的實現類
        implementClass = interfaceClass.getAnnotation(Impl.class).value();
    } else {
        // 獲取該介面所有的實現類
        List<Class<?>> implementClassList = ClassHelper.getClassListByInterface(interfaceClass);
        if (CollectionUtil.isNotEmpty(implementClassList)) {
            // 獲取第一個實現類
            implementClass = implementClassList.get(0);
        }
    }
    // 若存在實現類,則執行以下程式碼
    if (implementClass != null) {
        // 從 Bean Map 中獲取該實現類對應的實現類例項
        Object implementInstance = beanMap.get(implementClass);
        // 設定該 Bean 欄位的值
        beanField.setAccessible(true); // 必須使該欄位可訪問
        beanField.set(beanInstance, implementInstance);
    }
}
...

在介面中是這樣使用的:

@Impl(ProductServiceImpl2.class)
public interface ProductService {

    Product getProduct(long productId);
}

假設這個介面的實現類是 ProductServiceImpl2。
這個解決方案相信大家還是滿意的吧?

大家上面看到的 BeanHelper 類,其實兼任了兩種職責:1.初始化所有的 Bean 類;2.實現依賴注入。

這違法了設計模式中的“單一責任原則”,所有有必要將其重構一下,現在的 BeanHelper 類更加苗條了,只是負責初始化 Bean 類而已。程式碼如下:

public class BeanHelper {

    // Bean 類 => Bean 例項
    private static final Map<Class<?>, Object> beanMap = new HashMap<Class<?>, Object>();

    static {
        try {
            // 獲取並遍歷所有的 Bean(帶有 @Bean 註解的類)
            List<Class<?>> beanClassList = ClassHelper.getClassListByAnnotation(Bean.class);
            for (Class<?> beanClass : beanClassList) {
                // 建立 Bean 例項
                Object beanInstance = beanClass.newInstance();
                // 將 Bean 例項放入 Bean Map 中(鍵為 Bean 類,值為 Bean 例項)
                beanMap.put(beanClass, beanInstance);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Map<Class<?>, Object> getBeanMap() {
        return beanMap;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getBean(Class<T> cls) {
        return (T) beanMap.get(cls);
    }
}

那麼,依賴注入功能放哪裡呢?我搞了一個 IOCHelper,用這個類來實現 IOC 功能。程式碼如下:

public class IOCHelper {

    static {
        try {
            // 獲取並遍歷所有的 Bean 類
            Map<Class<?>, Object> beanMap = BeanHelper.getBeanMap();
            for (Map.Entry<Class<?>, Object> beanEntry : beanMap.entrySet()) {
                // 獲取 Bean 類與 Bean 例項
                Class<?> beanClass = beanEntry.getKey();
                Object beanInstance = beanEntry.getValue();
                // 獲取 Bean 類中所有的欄位(不包括父類中的方法)
                Field[] beanFields = beanClass.getDeclaredFields();
                if (ArrayUtil.isNotEmpty(beanFields)) {
                    // 遍歷所有的 Bean 欄位
                    for (Field beanField : beanFields) {
                        // 判斷當前 Bean 欄位是否帶有 @Inject 註解
                        if (beanField.isAnnotationPresent(Inject.class)) {
                            // 獲取 Bean 欄位對應的介面
                            Class<?> interfaceClass = beanField.getType();
                            // 判斷介面上是否標註了 @Impl 註解
                            Class<?> implementClass = null;
                            if (interfaceClass.isAnnotationPresent(Impl.class)) {
                                // 獲取強制指定的實現類
                                implementClass = interfaceClass.getAnnotation(Impl.class).value();
                            } else {
                                // 獲取該介面所有的實現類
                                List<Class<?>> implementClassList = ClassHelper.getClassListByInterface(interfaceClass);
                                if (CollectionUtil.isNotEmpty(implementClassList)) {
                                    // 獲取第一個實現類
                                    implementClass = implementClassList.get(0);
                                }
                            }
                            // 若存在實現類,則執行以下程式碼
                            if (implementClass != null) {
                                // 從 Bean Map 中獲取該實現類對應的實現類例項
                                Object implementInstance = beanMap.get(implementClass);
                                // 設定該 Bean 欄位的值
                                if (implementInstance != null) {
                                    beanField.setAccessible(true); // 取消型別安全檢測(可提高反射效能)
                                    beanField.set(beanInstance, implementInstance); // beanInstance 是普通例項,或 CGLib 動態代理例項(不能使 JDK 動態代理例項)
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

可見,IOCHelper 是依賴於 BeanHelper 的。這樣分離,還有一個好處,就是方便實現 ServiceHelper 與 AOPHelper。也就是說,首先通過 BeanHelper 初始化所有的 Bean 類,然後依次初始化 ServiceHelper、IOCHelper、AOPHelper,這個順序不能搞錯。因為在 ServcieHelper 中,對 Servcie 實現類進行了動態代理,所有保證了 IOC 注入進來的是代理類,而並非目標類。

相關推薦

Spring IOC實現原理

spring ioc實現原理一、IOC 容器:最主要是完成了完成對象的創建和依賴的管理註入等等。 所謂控制反轉,就是把原先我們代碼裏面需要實現的對象創建、依賴的代碼,反轉給容器來幫忙實現。那麽必然的我們需要創建一個容器,同時需要一種描述來讓容器知道需要創建的對象與對象的關系。這個描述最具體表現就是我們可配置

spring原始碼學習之路---IOC實現原理(二)

上一章我們已經初步認識了BeanFactory和BeanDefinition,一個是IOC的核心工廠介面,一個是IOC的bean定義介面,上章提到說我們無法讓BeanFactory持有一個Map package org.springframework.beans.factory.supp

淺談spring ioc 實現原理

**概念** spring ioc其實就是一個容器(控制反轉) 其中放入了大量的bean和類 **spring ioc如何操作的** ioc對配置檔案進行掃面,掃面到bean標籤下面的包,將沒有的實現類new出來 **spring ioc四大核心註解** @Service @Controller @

Spring AOP IOC 實現原理,面試問到如何回答

IOC:控制反轉也叫依賴注入,IOC利用java反射機制,AOP利用代理模式。所謂控制反轉是指,本來被呼叫者的例項是有呼叫者來建立的,這樣的缺點是耦合性太強,IOC則是統一交給spring來管理建立,將物件交給容器管理,你只需要在spring配置檔案總配置相應的

IOC實現原理

IOC 也就是“控制反轉”了,不過更流行的叫法是“依賴注入”(DI - Dependency Injection)。聽起來挺高深,其實實現起來並不複雜。下面就看看如何來實現這個輕量級 IOC 框架。 從例項出發,先看看以下 Action 程式碼。 @Be

Spring原始碼學習之IOC實現原理(二)-ApplicationContext

一.Spring核心元件結構      總的來說Spring共有三個核心元件,分別為Core,Context,Bean.三大核心元件的協同工作主要表現在 :Bean是包裝我們應用程式自定義物件Object的,Object中存有資料,而Context就是為了這些資料存放提供一個生存環境,儲存各個 bean之間的

番外 01:Spring IoC 實現原理簡析,Java的反射機制,通過類名建立物件

轉載請註明來源 賴賴的部落格 前景概要 在 01 走進Spring,Context、Bean和IoC 中,我們看到了強大的Spring通過ApplicationContext實現了bean工廠(也就是物件工廠),那究竟是怎麼實現的呢,本次給大家寫一個小D

Spring原始碼學習之路---IOC實現原理(三)

原文地址:https://blog.csdn.net/zuoxiaolong8810/article/details/8548478          上一章我們已經初步認識了BeanFactory和BeanDefinition,一個是IOC的核心工廠介面,一個是IOC的be

Spring IoC 容器的設計與實現原理

上一篇文章講解的是IOC的原理,這一篇文章主要講解Spring IoC 容器的設計與實現原理   1.spring的IOC容器 在 Spring IoC 容器的設計中,容器有兩個系列,可以看成是容器的具體表現形式: BeanFactory 簡單容器:實現了容器的基本

Spring系列之IOC原理及手動實現

導語 Spring是一個分層的JavaSE/EE full-stack(一站式) 輕量級開源框架。也是幾乎所有Java工作者必須要掌握的框架之一,其優秀的設計思想以及其程式碼實現上的藝術也是我們需要掌握的。要學習Spring,除了在我們的專案中使用之外,也需要對它的原始碼進行研讀,但是Spring的實現涵蓋

Spring IOC/BeanFactory/ApplicationContext的工作流程/實現原理/初始化/依賴注入原始碼詳解

Spring的工作流程/實現原理之基石IOC/BeanFactory/ApplicationContext 更新1:2017/11/23更新2:2018/1/30(截圖)一、什麼是IOC容器?IOC(Inversion of Control)、控制反轉亦稱依賴注入.IOC容器

(一)Spring IoC原始碼-3.其他特性的實現原理-01lazy-init與預例項化

前面的文章也提到過,IOC容器初始化過程一般不包含Bean載入的實現。Bean載入一般發生在應用第一次通過getBean向容器索取Bean的時候。但有一個例外:如果在XML檔案中為Bean定義了lazy-init屬性,那麼Bean的載入在IOC容器初始化時就

(一)Spring IoC原始碼-3.其他特性的實現原理-02迴圈依賴的解決

引言:迴圈依賴就是N個類中迴圈巢狀引用,如果在日常開發中我們用new 物件的方式發生這種迴圈依賴的話程式會在執行時一直迴圈呼叫,直至記憶體溢位報錯。下面說一下Spring是如果解決迴圈依賴的。 第一種:構造器引數迴圈依賴 Spring容器會將每一個正

(精)Spring IOC核心原始碼學習III:bean標籤和自定義標籤實現原理

本文將解析spring bean定義標籤和自定義標籤的解析實現原理。這裡說的標籤僅限於以xml作為bean定義描述符的spring容器,繼承AbstractXmlApplicationContext的一些子 容器,如XmlApplicationContext、ClassPat

Spring5原始碼之IOC容器實現原理

1、 2、 3、定位、載入、註冊 定位:資源配置import,classpath,url 載入:解析配置檔案,把bean包裝成BeanDefinition物件 註冊:把已經初始化的BeanDefinition物件放到IOC容器中

Spring原始碼學習之IOC容器實現原理(一)-DefaultListableBeanFactory

從這個繼承體系結構圖來看,我們可以發現DefaultListableBeanFactory是第一個非抽象類,非介面類。實際IOC容器。所以這篇部落格以DefaultListableBeanFactoryIOC容器為基準進行IOC原理解析。 一.兩個重要介面 前面已經分析了BeanFactor,它的三個直接子

Spring IOC AOP 簡易程式碼實現原理程式碼

現在很多人都處於使用spring的開發 為了瞭解裡面的原理,我去看了黃勇老師寫的《架構探險 從零開始寫JAVA WEB框架》 並編寫好了一套老師講的程式碼,可執行,可除錯。不復雜。 一般除錯完一遍後就能瞭解了spring的IOC、AOP的實現原理了。 也懂得了spri

Spring IOC和Spring AOP的實現原理(原始碼主線流程)

寫在前面      正本文參考了《spring技術內幕》和spring 4.0.5原始碼。本文只描述原理流程的主線部分,其他比如驗證,快取什麼可以具體參考原始碼理解。Spring IOC一、容器初始化      容器的初始化首先是在對應的構造器中進行,在application

IOC底層實現原理

1.基本概念 Ioc:控制反轉,建立物件的方式由傳統的new方式的建立轉變成交給spring容器進行管理。 2.底層實現 2.1 為什麼會出現ioc 且看一段程式碼 Class User{ Public void add(){}; } 傳統呼

死磕Spring之IoC篇 - @Autowired 等註解的實現原理

> 該系列文章是本人在學習 Spring 的過程中總結下來的,裡面涉及到相關原始碼,可能對讀者不太友好,請結合我的原始碼註釋 [Spring 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring 版