IoC全称是Inversion of Control,就是控制反转,他其实不是spring独有的特性或者说也不是java的特性,他是一种设计思想。而DI(Dependency Injection),即依赖注入就是Ioc的一种实现方式。关于Ioc和DI的具体定义和优缺点等大家可以自行查找资料了解一下,这里就不详细赘述,总之spring的IoC功能很大程度上便捷了我们的开发工作。
在实现我们的Ioc之前,我们先了解一下spring的依赖注入,在spring中依赖注入有三种方式,分别是:
@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功能了
先在zbw.ioc包下创建一个annotation包,然后再创建一个 Autowired
的注解。这个注解的Target只有一个 ElementType.FIELD
,就是只能注解在属性上。意味着我们目前只实现接口注入的功能。这样可以避免构造注入造成的循环依赖问题无法解决,而且接口注入也是用的最多的方式了。如果想要实现设值方式注入大家可以自己去实现,实现原理几乎都一样。
package com.zbw.ioc.annotation; import ... @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { } 复制代码
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功能的了。
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