IOC實現原理

本文轉載自開源中國的黃勇老師(跨平臺這個好傷啊555,求大神教訣竅),文章地址:https://my.oschina.net/huangyong/blog/158992

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 注入進來的是代理類,而並非目標類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章