IOC实现原理

来源:互联网 发布:房地产知识销售员必知 编辑:程序博客网 时间:2024/05/18 02:00

本文转载自开源中国的黄勇老师(跨平台这个好伤啊555,求大神教诀窍),文章地址:https://my.oschina.net/huangyong/blog/158992

IOC 也就是“控制反转”了,不过更流行的叫法是“依赖注入”(DI - Dependency Injection)。听起来挺高深,其实实现起来并不复杂。下面就看看如何来实现这个轻量级 IOC 框架。

从实例出发,先看看以下 Action 代码。

@Beanpublic 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 注入进来的是代理类,而并非目标类。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 2岁宝宝反复呕吐怎么办 1岁半幼儿拉肚子怎么办 宝宝打嗝呕吐胃难受怎么办 3岁宝宝发烧还吐怎么办 孩子喝水都吐怎么办啊 宝宝吃多了呕吐怎么办 3岁宝宝吐怎么办才好 儿童受凉肚子疼发热呕吐怎么办 两岁宝宝半夜呕吐怎么办 两岁宝宝吐了怎么办 2岁宝宝发烧吐怎么办 2岁多宝宝呕吐是怎么办 2周岁宝宝中暑了怎么办 2岁半宝宝着凉呕吐怎么办 3岁宝宝吐了几次怎么办 一岁宝宝恶心吐怎么办 9个月宝宝一直吐怎么办 晚上冻着了呕吐怎么办 2岁宝宝一直吐怎么办 两岁宝宝门牙龋齿怎么办 两岁宝宝得龋齿怎么办 两岁宝宝长龋齿怎么办 宝宝2岁不吃饭怎么办 两岁宝宝总是吐怎么办 3岁儿童受凉呕吐怎么办 两岁宝宝四天没拉大便怎么办 两岁宝宝发烧吐怎么办 四岁宝宝吐了怎么办啊 3岁宝宝突然吐了怎么办 宝宝撑着了吐拉怎么办 2岁宝宝体温37.5怎么办 宝宝2岁乳牙烂了怎么办 孕40周还没入盆怎么办 孕妇脸上长斑了怎么办 七个月宝宝大便干怎么办 两月大婴儿不拉大便怎么办 周岁宝宝大便出血了怎么办 十一个月宝宝大便干燥怎么办 8个月宝宝大便干燥怎么办 7个月宝宝大便干燥怎么办 11个月宝宝大便干燥怎么办