• 从零开始实现一个简易的Java MVC框架(三)--实现IOC


    
    Spring中的IOC
    IoC全称是Inversion of Control,就是控制反转,他其实不是spring独有的特性或者说也不是java的特性,他是一种设计思想。而DI(Dependency Injection),即依赖注入就是Ioc的一种实现方式。关于Ioc和DI的具体定义和优缺点等大家可以自行查找资料了解一下,这里就不详细赘述,总之spring的IoC功能很大程度上便捷了我们的开发工作。
    
    在实现我们的Ioc之前,我们先了解一下spring的依赖注入,在spring中依赖注入有三种方式,分别是:
    
    接口注入(Interface Injection)
    设值方法注入(Setter Injection)
    构造注入(Constructor Injection)
    @Component
    public class ComponentA {
        @Autowired // 1.接口注入
        private ComponentB componentB;
        
        @Autowired // 2.设值方法注入
        public void setComponentB(ComponentB componentB) {
            this.componentB = componentB;
        }
    
        @Autowired // 3.构造注入
        public ComponentA(ComponentB componentB) {
            this.componentB = componentB;
        }
    }
    循环依赖注入
    如果只是实现依赖注入的话实际上很简单,只要利用java的反射原理将对应的属性‘注入’进去就可以了。但是必须要注意一个问题,那就是循环依赖问题。循环依赖就是类之间相互依赖形成了一个循环,比如A依赖于B,同时B又依赖于A,这就形成了相互循环。
    
    // ComponentA
    @Component
    public class ComponentA {
        @Autowired
        private ComponentB componentB;
    }
    
    // ComponentB
    @Component
    public class ComponentB {
        @Autowired
        private ComponentA componentA;
    }
    那么在spring中又是如何解决循环依赖问题的呢,我们大致说一下原理。
    
    如果要创建一个类,先把这个类放进'正在创建池'中,通过反射等创建实例,创建成功的话就把这个实例放入创建池中,并移除'正在创建池'中的这个类。每当实例中有依赖需要注入的话,就从创建池中找对应的实例注入进去,如果没有找到实例,则先创建这个依赖。
    
    利用了这个正在创建的中间状态缓存,让Bean的创建的时候即使有依赖还没有实例化,可以先把Bean放进这个中间状态,然后跑去创建那个依赖,假如那个依赖的类又依赖与这个Bean,那么只要在'正在创建池'中再把这个Bean拿出来,注入到这个依赖中,就可以保证Bean的依赖能够实例化完成。再回头来把这个依赖注入到Bean中,那么这个Bean也实例化完成了,就把这个Bean从'正在创建池'移到'创建完成池'中,就解决了循环依赖问题。
    
    虽然spring巧妙的避免了循环依赖问题,但是事实上构造注入是无法避免循环依赖问题的。因为在实例化ComponentA的构造函数的时候必须得到ComponentB的实例,但是实例化ComponentB的构造函数的时候又必须有ComponentA的实例。这两个Bean都不能通过反射实例化然后放到'正在创建池',所以无法解决循环依赖问题,这时候spring就会主动抛出BeanCurrentlyInCreationException异常避免死循环。
    
    * 注意,前面讲的这些都是基于spring的单例模式下的,如果是多例模式会有所不同,大家有兴趣可以自行了解。
    
    实现IOC
    现在可以开始实现IOC功能了
    
    增加注解
    
    先在zbw.ioc包下创建一个annotation包,然后再创建一个Autowired的注解。这个注解的Target只有一个ElementType.FIELD,就是只能注解在属性上。意味着我们目前只实现接口注入的功能。这样可以避免构造注入造成的循环依赖问题无法解决,而且接口注入也是用的最多的方式了。如果想要实现设值方式注入大家可以自己去实现,实现原理几乎都一样。
    
    package com.zbw.ioc.annotation;
    
    import ...
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Autowired {
    }
    
    实现IOC类
    
    package com.zbw.ioc;
    
    import ...
    
    @Slf4j
    public class Ioc {
    
        /**
         * Bean容器
         */
        private BeanContainer beanContainer;
    
        public Ioc() {
            beanContainer = BeanContainer.getInstance();
        }
    
        /**
         * 执行Ioc
         */
        public void doIoc() {
            for (Class<?> clz : beanContainer.getClasses()) { //遍历Bean容器中所有的Bean
                final Object targetBean = beanContainer.getBean(clz);
                Field[] fields = clz.getDeclaredFields();
                for (Field field : fields) { //遍历Bean中的所有属性
                    if (field.isAnnotationPresent(Autowired.class)) {// 如果该属性被Autowired注解,则对其注入
                        final Class<?> fieldClass = field.getType();
                        Object fieldValue = getClassInstance(fieldClass);
                        if (null != fieldValue) {
                            ClassUtil.setField(field, targetBean, fieldValue);
                        } else {
                            throw new RuntimeException("无法注入对应的类,目标类型:" + fieldClass.getName());
                        }
                    }
                }
            }
        }
    
        /**
         * 根据Class获取其实例或者实现类
         */
        private Object getClassInstance(final Class<?> clz) {
            return Optional
                    .ofNullable(beanContainer.getBean(clz))
                    .orElseGet(() -> {
                        Class<?> implementClass = getImplementClass(clz);
                        if (null != implementClass) {
                            return beanContainer.getBean(implementClass);
                        }
                        return null;
                    });
        }
    
        /**
         * 获取接口的实现类
         */
        private Class<?> getImplementClass(final Class<?> interfaceClass) {
            return beanContainer.getClassesBySuper(interfaceClass)
                    .stream()
                    .findFirst()
                    .orElse(null);
        }
    
    }
    
    在知道IOC的原理之后发现其实真的是非常简单,这里用了几十行的代码就实现了IOC的功能。
    
    首先在Ioc类构造的时候先获取到我们之前已经单例化的BeanContainer容器。
    
    然后在doIoc()方法中就是正式实现IOC功能的了。
    
    遍历在BeanContainer容器的所有Bean
    对每个Bean的Field属性进行遍历
    如果某个Field属性被Autowired注解,则调用getClassInstance()方法对其进行注入
    getClassInstance()会根据Field的Class尝试从Bean容器中获取对应的实例,如果获取到则返回该实例,如果获取不到,则我们认定该Field为一个接口,我们就调用getImplementClass()方法来获取这个接口的实现类Class,然后再根据这个实现类Class在Bean容器中获取对应的实现类实例。
    测试用例
    
    为了测试我们的Ioc和之前写的BeanContainer编写正确,我们写一下测试用例测试一下。
    
    先在pom.xml添加junit的依赖
    
    <properties>
        ...
        <junit.version>4.12</junit.version>
    </properties>
    <dependencies>
        ...
        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    然后在test包下添加DoodleController、DoodleService、DoodleServiceImpl三个类方便测试
    
    // DoodleController
    package com.zbw.bean;
    @Controller
    @Slf4j
    public class DoodleController {
        @Autowired
        private DoodleService doodleService;
    
        public void hello() {
            log.info(doodleService.helloWord());
        }
    }
    
    // DoodleService
    package com.zbw.bean;
    public interface DoodleService {
        String helloWord();
    }
    
    // DoodleServiceImpl
    package com.zbw.bean;
    @Service
    public class DoodleServiceImpl implements DoodleService{
        @Override
        public String helloWord() {
            return "hello word";
        }
    }
    再编写IocTest的测试用例
    
    package com.zbw.ioc;
    
    import ...
    
    @Slf4j
    public class IocTest {
        @Test
        public void doIoc() {
            BeanContainer beanContainer = BeanContainer.getInstance();
            beanContainer.loadBeans("com.zbw");
            new Ioc().doIoc();
            DoodleController controller = (DoodleController) beanContainer.getBean(DoodleController.class);
            controller.hello();
        }
    }
    
    
    看到在DoodleController中输出了DoodleServiceImpl的helloWord()方法里的字符串,说明DoodleController中的DoodleService已经成功注入了DoodleServiceImpl。那么我们的IOC的功能也完成了。

    源码地址:doodle

    原文地址:从零开始实现一个简易的Java MVC框架(三)--实现IOC

  • 相关阅读:
    剑指offer-最小的k个数
    剑指offer-数组中出现次数超过一半的数字
    android开发------响应用户事件
    android开发------初识Activity
    android开发------编写用户界面之相对布局
    android开发------编写用户界面之线性布局(补充知识)
    android开发------编写用户界面之线性布局
    android开发------第一个android程序
    加密狗的工作原理
    克隆加密狗、复制加密狗、破解加密狗的定义区别
  • 原文地址:https://www.cnblogs.com/piwefei/p/12071853.html
Copyright © 2020-2023  润新知