本文的注解版IoC框架跟其他手写IoC框架的不同之处在与:在实现了 @Component 和 @Autowired 的同时还实现了@Qualifier,并解决单例模式下循环依赖的问题,以上3个注解的使用效果参照 Spring 。
项目 Github 地址为:https://github.com/linshenkx/winter-core
相关文章地址:
从零写Spring注解版框架系列 IoC篇 (2)实现 @Component、@Autowired、@Qualifier注解
一 设计思想
1 IoC的定义
IoC 全称为 Inversion of Control,翻译为 “控制反转”,它还有一个别名为 DI(Dependency Injection),即依赖注入。
所谓 IoC ,就是由 Spring IoC 容器来负责对象的生命周期和对象之间的关系
2 Spring中的 IoC
模块结构
传统xml模式下的Spring实现IoC由几个模块组成:
- Resource和ResourceLoader
Spring 将资源的定义和资源的加载区分开:- Resource:统一资源,为 Spring 框架所有资源的抽象和访问接口
- ResourceLoader:统一资源定位,主要应用于根据给定的资源文件地址,返回对应的 Resource
- BeanDefinition
用来描述 Spring 中的 Bean 对象 - BeanDefinitionReader
资源解析器,用于读取 Spring 的配置文件的内容,并将其转换成 Ioc 容器内部的数据结构 :BeanDefinition - BeanFactory
一个非常纯粹的 bean 容器,它是 IoC 必备的数据结构,其中 BeanDefinition 是它的基本结构。BeanFactory 内部维护着一个BeanDefinition map ,并可根据 BeanDefinition 的描述进行 bean 的创建和管理。 - ApplicationContext
Spring 容器,也叫应用上下文,与我们应用息息相关。它继承 BeanFactory ,是 BeanFactory 的扩展升级版
Spring IoC 的每一个模块都自成体系,设计精妙但也确实复杂繁重,初学者难免觉得巍然不可近。
单从原理思路出发的话则简单很多,Spring IoC 无非就是替你管理了类的实例,在你需要的时候给你注入进去。
工作原理
IoC 初始化
IoC初始化阶段负责从用户指定路径获取bean的描述和配置信息,将bean的描述信息收归 BeanFactory 的HashMap管理。
这个阶段的操作目标是 bean的描述信息(BeanDefinition)而不是 Bean本身
代码:
ClassPathResource resource = new ClassPathResource("bean.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
- 资源定位
用户给定一个资源路径(如xml的路径,用file或者url等),ResourceLoader 根据策略从该路径获取到 Resource - 装载
新建 IoC 容器 BeanFactory,根据 BeanFactory 创建一个 BeanDefinitionReader 对象对 Resource 进行解析,获取到 BeanDefinition - 注册
将得到的 BeanDefinition 注入到一个 HashMap 容器中,IoC 容器就是通过这个 HashMap 来维护这些 BeanDefinition 的
加载Bean
默认使用懒加载策略,即等到用户需要这个 Bean 的时候再根据 BeanDefinition 完成 Bean 的加载
这里的功能扩展比较多,但最主要解决的是 Bean的属性填充(@Value)和依赖处理(@Autowired)
另外Bean的加载主要有单例(Singleton)和原型(prototype)两种模式
需要注意的有 循环依赖 问题,对于单例模式出现的循环依赖Spring会进行分析检测,对于原型模式则不管,由用户负责。
二 设计思路
-
核心:@Component和@Autowired
上面的分析是基于xml的,如果是使用注解的话还会简单一些,免去了xml 读取、解析的操作。
核心功能通过 @Component 和 @Autowired 两个注解即可完成。
其中,@Component 负责将 Bean 交由 IoC 容器处理,@Autowired 负责将 IoC 容器管理的 Bean 注入的对应属性中 -
@Qualifier
另外,@Autowired 默认是按照类型匹配的,Java官方还有一个@Resource注解是按照名称匹配的,这里我们还是使用主流的@Autowired注解,同时,为了证明不是偷懒我还会实现 @Qualifier 注解来指定名称。 -
单例 VS 原型
现在先完成单例模式,原型模式用得不多,不也推荐使用,对于有状态的Bean推荐使用 ThreadLocal 来对状态进行屏蔽。 -
IoC 容器
首先我们的功能核心放在一个 ApplicationContext 下,其实就只是一个 BeanFactory 而已。
Bean的描述信息BeanDefinition放在一个Map(beanDefinationFactory)里面,因为我们用的是注解,BeanDefinition直接用对应类的 Class 对象就好。
Bean实例化后的单例对象也放在一个Map里面(singletonbeanFactory)。 -
确定加载的Bean
由于我们可以在按照类型寻找的前提下根据用户指定名进行匹配,所以不管是使用@Component进行Bean发现还是使用@Autowired进行Bean注入,要确定一个 Bean 的实例都需要两个信息:type(类型)和 beanId(名字)这里比较麻烦的问题是父类可以由子类注入,接口可以由实现类注入。而且类型和名字并不是独立匹配的,是在类型匹配的前提下进行名字识别,以获取唯一bean
为了达到匹配效果,我使用了这种结构来描述bean信息(beanDefinationFactory) :Map<String,Map<String,Class>>
,即全称类名(type)+自定义类名(beanId)==> 真类信息为了达到每个bean只实例化一次,我用全称类名来确定唯一bean实例(singletonbeanFactory):Map<String,Object>
-
加载流程
同一个bean可能有多个类型(比方说其父类、接口等),所以在beanDefinationFactory中多个描述信息可能对应的是一个Class
首先根据type和beanId获取Class对象
再根据Class对象从singletonbeanFactory中获取单例
三 功能目标
根据上文的设计思路,我们这个简单的 IoC 注解版框架的 最基本功能是实现以下几个注解:
1 注解设计
- @Component
将注解的 Bean 纳入 IoC框架管理,同时可以通过@Component的value指定该Bean的名称(beanId),不指定则为类名首字母小写,type为所有超类、接口和自身 - @Autowired
将 IoC 框架管理的 Bean 注入注解标记的属性 - @Qualifier
搭配 @Autowired 实现在类型匹配的前提下按名称匹配。
这么说是不是很简单?下面再来细化以下规则
2 规则细化
- IoC框架对于一个单例类只实例化一次
- 只使用@Autowired注解的情况下默认按类型匹配
- 如果 IoC 框架中仅有一个该类型的 Bean,则实例化该Bean
- 如果 IoC 框架中有多个该类型的 Bean,则产生歧义。此时根据 @Autowired 标记的属性名(注意不是类型名)来识别唯一的Bean
- 如果在该类型的多个Bean找得到名称匹配的Bean,则实例化该bean
- 如果找不到,则抛出异常,提醒 存在多个同类不同名Bean,无法识别
- 如果在使用@Autowired的同时使用@Qualifier注解,则根据@Qualifier的value作为Bean的名称在该类型下寻找匹配的bean
- 如果找得到,则实例化该Bean
- 如果没找到,则抛出异常,提醒该名称的Bean不存在。注意,这时即使该类型下只有一个Bean,也会抛出异常。
3 应用举例
有类图如下,其中HelloController、HelloServiceImpl、HelloServiceImpl1、HelloServiceImpl2都使用了@Component注解。
各类如下,以下注入可正常完成。
@Component
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "Hello!"+name;
}
}
@Component("myHelloService1")
public class HelloServiceImpl1 implements HelloService {
@Override
public String sayHello(String name) {
return "Hello1!"+name;
}
}
@Component("myHelloService2")
public class HelloServiceImpl2 implements HelloService {
@Override
public String sayHello(String name) {
return "Hello2!"+name;
}
}
@Component
public class HelloController {
//根据属性名匹配,匹配到 HelloServiceImpl
@Autowired
private HelloService helloService;
//根据属性名匹配,匹配到 HelloServiceImpl2
@Autowired
private HelloService myHelloService2;
//根据指定名匹配,匹配到 HelloServiceImpl1
@Autowired
@Qualifier("myHelloService1")
private HelloService helloService1;
//根据指定名匹配,匹配到 HelloServiceImpl2
@Autowired
@Qualifier("myHelloService2")
private HelloService helloService2;
//根据类型匹配,匹配到 HelloServiceImpl2
@Autowired
private HelloServiceImpl2 helloService22;
public String helloDefault(String name){
return helloService.sayHello(name);
}
public String hello1(String name){
return helloService1.sayHello(name);
}
public String hello2(String name){
return myHelloService2.sayHello(name);
}
}