Spring之基于注解方式实例化BeanDefinition(1)

最近开始读Spring源码,读着读着发现里面还是有很多很好玩的东西在里面的,里面涉及到了大量的设计模式以及各种PostProcessor注入的过程,很好玩,也很复杂,本文就是记录一下我学习过程中的主干流程。

在开始我们源码解读之前,我们先大致的归纳一下解析的全过程。

1. 首先就是每个spring的jar包都会提供各自的配置文件,包括注解,标签,处理类。

2. 然后就是根据我们的配置信息去解读我们自己的配置文件spring.xml,主要分为自定义标签和默认标签(<bean> 就是默认标签)

3. 最后就是解读这些信息,封装成 BeanDefinition对象,进行注册。

4. 最后介绍一下此阶段注册的几个PostProcessor接口,这部分内容是为我们实例化bean和DI注入做准备的,下一章我们会重点分析。

在我们逐步分解实例化beanDefinition之前,我先贴出时大体流程图,后面解读的过程会参照这张图进行讲解。

进入主题:

1. 每一个Spring的jar包都会配置好各种各样的处理类,这些类都提供了各种各样的处理器功能。比如说,spring想要管理maybatis,那么在spring管理mabatis的jar包中肯定会定义一些处理类,如下图:

而本篇我们是将spring基于注解方式实例化BeanDefinition的,那么我们都会基于扫描的方式进行

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:jack="http://www.xiangxueedu.com/schema/mytags"
       xsi:schemaLocation="
	http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
    http://www.xiangxueedu.com/schema/mytags
    http://www.xiangxueedu.com/schema/mytags.xsd"
    default-lazy-init="false">

    <context:component-scan base-package="com.xiangxue.jack" />

</beans>

我们知道,扫描是基于 http://www.springframework.org/schema/context进行的,那么我们就打开spring的context相关 jar包,找到对应的处理类。

 点进去以后,我们可以看到这个处理类只有一个方法,就是注册基于context标签下的各个标签的处理类,有点拗口,看一下代码就明白了

2. 上面步骤是一个基本的了解过程,下面我们就开始自己逐步debug分解过程,首先就是生成一个applicationContext的操作;

3. 进入ClassPathXMLApplicationContext的refresh()方法,这个方式是spring的核心

4. 我们关注一下obtainFreshBeanFactory()方法,就在refresh()内部调用。这个方法做了很多的事情,下方第一张图片有详细的comments进行解释

 

 

 总结一下,其实就是把spring.xml文件生成一个document对象,然后进行逐步解析。

5.  解析document对象的时候,我们会判断是默认标签,还是自定义标签。比如在spring.xml中配置<bean id="" class=""></bean>, 这就是默认标签。 而<context:component-scan base-package="com.xiangxue.jack" />就是自定义标签。 本文针对的是基于注解的方式分析,所以关注的是自定义标签。

6. 我们首先会根据component-scan 标签,在spring..xml 中找到对应的URI

 7. 基于上一步获取到的URI,拿到对应的上下文处理器,这个处理器就是我们第1步说的,在spring.handers文件中配置的信息,也就是 ContextNamespaceHandler。这一步我们需要进行详细的分析一下,进入到这个resolve方法中看一下:

 感兴趣的话可以逐个点进去看一下具体。此处我需要再次强调一下初始化init方法,注册component-scan的流程。点进去看一下,我们发现

 我们确认了,它就是初始化各个标签的扫描类的,并且会把这些扫描类放到缓存中,缓存是一个Map, 在它的父类NameSpaceHandlerSupport中

8.最后,我们会返回现在已经准备好的上下文处理器类,即ContextNamespaceHandler。进入主流程

 9.  上一步,我们拿到了上下文处理器,即ContextNamespaceHandler。并且通过这个处理器实例化了各个子标签的处理类放在Map中。接下来会调用这个ContextNamespaceHandler的parase方法,进行解析操作。 这个方法是重点方法,我们需要点进去看

a. 首先会根据<context:component-scan base-package="com.xiangxue.jack" />配置,拿到base-package配置的目录。

b. 创建注解扫描器 ClassPathBeanDefinitionScanner,来扫描base-package配置的路径。 扫描到对应的class文件,生成beanDefinition进行注册。这一步是核心

b-1: 扫描到有注解的类并封装成BeanDefinition对象

 b-1-1: 进入findCandidateComponents方法:它就是递归的方法,根据配置的路径找到所有的class文件,不管是有注解的,还是没有注解的。只要是这个路径下的class文件,统统找出来

b-1-2: 然后逐个遍历,判断当前的class文件是否有符合要求的注解。如果是符合要求的class, 就生成BeanDefinition对象进行收集

b-1-3: 这里有必要看一下这个判断方法 isCandidateComponent。了解一下它是如何去判断那个类是符合spring管理的。点进去

 我们发现,只要这个类有与注解Component相关的信息,就是符合条件的class对象,就会被搜集起来。

 最后,我们解释一下,什么是和@Component相关的呢? 其实,@Controller, @Service, @Repository都是与@Component相关的. 如何判断呢?只要找到对应的注解,打开发现接口上方有@Component相关信息即可。

 

 b-1-4: 最后返回到所有搜集起来的符合条件的BeanDefinition的set集合

b2: 拿到这些有注解的,符合条件的beanDefinition,那么接下来就是处理这些BeanDefinition的事情了。

b2-1: 判断beanDefinition是否支持懒加载@Lazy @DependOn @Description @Primary等注解

其实,就是在BeanDefinition中,给对应的属性设置个值而已。当我们实例化对象的时候,我们在根据BeanDefinition中设置的这些值,做不同的逻辑判断而已。下一章章实例化的时候,我会分析一下这些注解 

 b2-2: 最后就是注册这些BeanDefinition了

b2-3: 进去看一下最终是如何注册的。

 最后,我们发现,所谓的注册BeanDefinition,其实就是把所有的name都放在一个list集合中,把BeanDefinition按照 name -->BeanDefinition放在一个Map中。这个垃圾,搞了半天,就搞了2个集合,这就完了。

上面就是基于注解的方式,实例化BeanDefinition的全部过程,概括就是根据自定义标签,拿到URI,再根据URI拿到对应的上下文处理器ContextNameSpaceHandler,而这个处理类会实例化context下的各个子标签的各个处理器,再根据字标签拿到属于自己的子处理器。

执行ContextNameSpaceHandler处理类的resolve方法和parse方法。resolve方法就是负责初始化子标签的处理器的。而parse就是根据子标签处理器找满足条件的class文件,然后把这些符合条件的类生成beanDefinition,最后进行注册。就是放在2个集合中。最后打完收工,回家睡觉。

最后补充一下:我们在parse的最后一行,有个registerComponents方法。它是负责注册一些PostProcessor接口的。

那么为啥要注册这些接口呢?  因为在我们实例化对象的时候,我们会使用很多注解进行依赖注入,比如说@Autowired @Resource等等。而这些注解,都是由这些PostProceseor接口提供的。具体如何提供的,我们在下一章会针对@Autowired @Resource这两个注解进行重点分析

此处, 我们只需要知道注册了一些PostProceseor即可,那具体注册了哪些PostProceseor呢?它们又具体负责哪些事呢?

 

每个PostTProcessor 作用,我们都可以点进去查看。如何进行查看呢?

下面我列出一些常用的注解支持类:

AutowiredAnnotationBeanPostProcessor  支持@Autowired  @Value
CommonAnnotationBeanPostProcessor  支持 @PostConstruct  @PreDestroy @Resource
ConfigurationClassPostProcessor   支持@Configuration, @Bean

我们都知道@PostConstruct相当于init-method,@PreDestroy相当于destory-method,其实,它们在beanDefinition中都是设置到相同的属性中的,这证明了它们就是一个东西

最后,分享一个甜点  BeanDefinitionRegistryPostProcessor

在第9步的b2-3中,我们谈到了注册BeanDefinition的过程

其实,如果一些对象,它们没有注解,也没有配置在spring.xml中,它们只是一个普通的java文件,或者是第三方jar提供的文件, 我们是否可以也交给spring进行管理呢?答案是可以的。

假设一个普通的java文件

package com.xiangxue.jack.postProcessor;

public class Dao2 {

    private String name;

    private String id;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "name :" + name + " id :" + id;
    }
}

实现了BeanDefinitionRegistryPostProcessor的实现类,这个类需要交给spring管理

package com.xiangxue.jack.postProcessor;

import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.stereotype.Component;

@Component
public class MyBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        //生成bean
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(Dao2.class);

        //给Dao2属性值赋值,这样实例化完成以后就会有值了.MutablePropertyValues有很多方法,
        //也就意味着我们即使配置的类有错误,只要实现这个接口,我们依旧可以在类实例化之前,通过
        //对beanDefinition进行修改,从而达到修改类的目的
        MutablePropertyValues pValues = beanDefinition.getPropertyValues();
        pValues.addPropertyValue("name", "yyds");
        pValues.addPropertyValue("id", "测试001");

        //我们给beanDefinition起了个 名字叫 dao2, 然后注册到spring容器中
        registry.registerBeanDefinition("dao2", beanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}

测试类:

package com.xiangxue.jack;

import com.xiangxue.jack.bean.MyTestBean2;
import com.xiangxue.jack.postProcessor.Dao2;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;

//@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring.xml"})
public class MyTest {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void myBean() {
        applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        MyTestBean2 service = (MyTestBean2) applicationContext.getBean("myTestBean2");
        service.system();
    }

    @Test
    public void myBean2() {
        applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Dao2 dao2 = (Dao2) applicationContext.getBean("dao2");
        System.out.println(dao2.toString());
    }
}

测试结果:

这个甜点的原理,我会在下一章进行分析

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值