从零写Spring注解版框架系列 IoC篇 (1) 框架设计

本文的注解版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由几个模块组成:

  1. Resource和ResourceLoader
    Spring 将资源的定义和资源的加载区分开:
    • Resource:统一资源,为 Spring 框架所有资源的抽象和访问接口
    • ResourceLoader:统一资源定位,主要应用于根据给定的资源文件地址,返回对应的 Resource
  2. BeanDefinition
    用来描述 Spring 中的 Bean 对象
  3. BeanDefinitionReader
    资源解析器,用于读取 Spring 的配置文件的内容,并将其转换成 Ioc 容器内部的数据结构 :BeanDefinition
  4. BeanFactory
    一个非常纯粹的 bean 容器,它是 IoC 必备的数据结构,其中 BeanDefinition 是它的基本结构。BeanFactory 内部维护着一个BeanDefinition map ,并可根据 BeanDefinition 的描述进行 bean 的创建和管理。
  5. 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); 
  1. 资源定位
    用户给定一个资源路径(如xml的路径,用file或者url等),ResourceLoader 根据策略从该路径获取到 Resource
  2. 装载
    新建 IoC 容器 BeanFactory,根据 BeanFactory 创建一个 BeanDefinitionReader 对象对 Resource 进行解析,获取到 BeanDefinition
  3. 注册
    将得到的 BeanDefinition 注入到一个 HashMap 容器中,IoC 容器就是通过这个 HashMap 来维护这些 BeanDefinition 的
ResourceLoader
BeanDefinitionReader
统一管理
资源路径
Resource
BeanDefinition
BeanDefinition的HashMap
加载Bean

默认使用懒加载策略,即等到用户需要这个 Bean 的时候再根据 BeanDefinition 完成 Bean 的加载
这里的功能扩展比较多,但最主要解决的是 Bean的属性填充(@Value)和依赖处理(@Autowired)
另外Bean的加载主要有单例(Singleton)和原型(prototype)两种模式
需要注意的有 循环依赖 问题,对于单例模式出现的循环依赖Spring会进行分析检测,对于原型模式则不管,由用户负责。

二 设计思路

  1. 核心:@Component和@Autowired
    上面的分析是基于xml的,如果是使用注解的话还会简单一些,免去了xml 读取、解析的操作。
    核心功能通过 @Component 和 @Autowired 两个注解即可完成。
    其中,@Component 负责将 Bean 交由 IoC 容器处理,@Autowired 负责将 IoC 容器管理的 Bean 注入的对应属性中

  2. @Qualifier
    另外,@Autowired 默认是按照类型匹配的,Java官方还有一个@Resource注解是按照名称匹配的,这里我们还是使用主流的@Autowired注解,同时,为了证明不是偷懒我还会实现 @Qualifier 注解来指定名称。

  3. 单例 VS 原型
    现在先完成单例模式,原型模式用得不多,不也推荐使用,对于有状态的Bean推荐使用 ThreadLocal 来对状态进行屏蔽。

  4. IoC 容器
    首先我们的功能核心放在一个 ApplicationContext 下,其实就只是一个 BeanFactory 而已。
    Bean的描述信息BeanDefinition放在一个Map(beanDefinationFactory)里面,因为我们用的是注解,BeanDefinition直接用对应类的 Class 对象就好。
    Bean实例化后的单例对象也放在一个Map里面(singletonbeanFactory)。

  5. 确定加载的Bean
    由于我们可以在按照类型寻找的前提下根据用户指定名进行匹配,所以不管是使用@Component进行Bean发现还是使用@Autowired进行Bean注入,要确定一个 Bean 的实例都需要两个信息:type(类型)和 beanId(名字)

    这里比较麻烦的问题是父类可以由子类注入,接口可以由实现类注入。而且类型和名字并不是独立匹配的,是在类型匹配的前提下进行名字识别,以获取唯一bean

    为了达到匹配效果,我使用了这种结构来描述bean信息(beanDefinationFactory) :Map<String,Map<String,Class>>
    ,即全称类名(type)+自定义类名(beanId)==> 真类信息

    为了达到每个bean只实例化一次,我用全称类名来确定唯一bean实例(singletonbeanFactory):Map<String,Object>

  6. 加载流程
    同一个bean可能有多个类型(比方说其父类、接口等),所以在beanDefinationFactory中多个描述信息可能对应的是一个Class
    首先根据type和beanId获取Class对象
    再根据Class对象从singletonbeanFactory中获取单例

三 功能目标

根据上文的设计思路,我们这个简单的 IoC 注解版框架的 最基本功能是实现以下几个注解:

1 注解设计

  1. @Component
    将注解的 Bean 纳入 IoC框架管理,同时可以通过@Component的value指定该Bean的名称(beanId),不指定则为类名首字母小写,type为所有超类、接口和自身
  2. @Autowired
    将 IoC 框架管理的 Bean 注入注解标记的属性
  3. @Qualifier
    搭配 @Autowired 实现在类型匹配的前提下按名称匹配。

这么说是不是很简单?下面再来细化以下规则

2 规则细化

  1. IoC框架对于一个单例类只实例化一次
  2. 只使用@Autowired注解的情况下默认按类型匹配
    1. 如果 IoC 框架中仅有一个该类型的 Bean,则实例化该Bean
    2. 如果 IoC 框架中有多个该类型的 Bean,则产生歧义。此时根据 @Autowired 标记的属性名(注意不是类型名)来识别唯一的Bean
      1. 如果在该类型的多个Bean找得到名称匹配的Bean,则实例化该bean
      2. 如果找不到,则抛出异常,提醒 存在多个同类不同名Bean,无法识别
  3. 如果在使用@Autowired的同时使用@Qualifier注解,则根据@Qualifier的value作为Bean的名称在该类型下寻找匹配的bean
    1. 如果找得到,则实例化该Bean
    2. 如果没找到,则抛出异常,提醒该名称的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);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值