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

从零开始实现一个简易的Java MVC框架(三)--实现IOC的更多相关文章

  1. 通过注解实现一个简易的Spring mvc框架

    1.首先我们来搭建架构,就建一个普通的javaweb项目就OK了,具体目录如下: 对于小白来说可以细看后面web.xml的配置,对javaweb有点研究可以忽略而过后面的web.xml配置. 2.先上 ...

  2. 从零开始写一个武侠冒险游戏-0-开发框架Codea简介

    从零开始写一个武侠冒险游戏-0-开发框架Codea简介 作者:FreeBlues 修订记录 2016.06.21 初稿完成. 2016.08.03 增加对 XCode 项目文件的说明. 概述 本游戏全 ...

  3. Java MVC框架性能比较

    Java MVC框架性能比较 - by zvane 现在各种MVC框架很多,各框架的优缺点网络上也有很多的参考文章,但介绍各框架性能方面差别的文章却不多,本人在项目开发中,感觉到采用了struts2框 ...

  4. Node.js简单介绍并实现一个简单的Web MVC框架

    编号:1018时间:2016年6月13日16:06:41功能:Node.js简单介绍并实现一个简单的Web MVC框架URL :https://cnodejs.org/topic/4f16442cca ...

  5. 依赖注入[5]: 创建一个简易版的DI框架[下篇]

    为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在<依赖注入[4]: 创建一个简易版的DI框架[上篇]> ...

  6. 依赖注入[4]: 创建一个简易版的DI框架[上篇]

    本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章(<控制反转>.<基于IoC的设计模式>和< 依赖注入模式>)从纯理论的角度 ...

  7. .NET CORE学习笔记系列(2)——依赖注入[4]: 创建一个简易版的DI框架[上篇]

    原文https://www.cnblogs.com/artech/p/net-core-di-04.html 本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章从 ...

  8. 使用EF Code First搭建一个简易ASP.NET MVC网站,允许数据库迁移

    本篇使用EF Code First搭建一个简易ASP.NET MVC 4网站,并允许数据库迁移. 创建一个ASP.NET MVC 4 网站. 在Models文件夹内创建Person类. public ...

  9. 使用 js 实现一个简易版的 vue 框架

    使用 js 实现一个简易版的 vue 框架 具有挑战性的前端面试题 refs https://www.infoq.cn/article/0NUjpxGrqRX6Ss01BLLE xgqfrms 201 ...

随机推荐

  1. 函数节流之debounce

    浏览器中某些计算和处理要比其他的昂贵很多.例如, DOM 操作比起非 DOM 交互需要更多的内存和 CPU 时间.连续尝试进行过多的 DOM 相关操作可能会导致浏览器挂起,有时候甚至会崩溃.尤其在 I ...

  2. TP5模型belongsTo和hasOne这两个方法的区别

    在使用tp5模型的ORM的时候出现belongsTo和hasOne都有表示一对一的关系,但是二者并不相同.以下举例说明两者的区别: 首先有user表 字段 id name password字段 然后有 ...

  3. day30 OSI七层协议

    网络编程 什么是网络编程? 网络编程就是编写基于网络传输数据的应用程序 为什么需要网络编程? 在我们以前的编程中,所有的数据都是存在于本地,且只能由我们自己使用,不能进行跨电脑通讯,但是在实际的生活中 ...

  4. Linux_目录基本操作_常用命令【详解】

    Linux_常用命令 Linux文件系统的目录树结构:[Linux世界里一切皆文件]:说白了,就是文件和文件夹(目录)之间的操作. 普通用户kkb所有文件及文件夹,其实都位于root用户的 /home ...

  5. Spring Boot常用的注解以及含义<持续更新>

    1.@RestController和@RequestMapping注解 @RestController 和 @RequestMapping 注解是Spring MVC注解(它们不是Spring Boo ...

  6. sqlserver中分页的方式

    1.使用top进行: 1.select top 页大小 * from 表名where id not in(select top 页大小*(查询第几页-1) id from 表名 order by id ...

  7. n*n矩阵 每行每列XOR为0(思维)

    题意:https://codeforc.es/contest/1208/problem/C 如题:就是给定一个数n,要你求一个n×n的矩阵,矩阵中的元素是 0 ~ n2-1 ,使得矩阵每一行和每一列的 ...

  8. Phython-守护线程

    import threading,time def run(n): print("task is ",n) time.sleep(2) print("task done& ...

  9. 服务端相关知识学习(六)Zookeeper client

    Zookeeper的client是通过Zookeeper类提供的.前面曾经说过,Zookeeper给使用者提供的是一个类似操作系统的文件结构,只不过这个结构是分布式的.可以理解为一个分布式的文件系统. ...

  10. 异常-JDK7针对多个异常的处理方案

    package cn.itcast_02; /* * JDK7出现了一个新的异常处理方案: * try{ * * }catch(异常名1 | 异常名2 | ... 变量 ) { * ... * } * ...