【黑马】Spring底层原理第二讲

【黑马】Spring技术原理第二讲

BeanFactory与他的后处理器小伙伴们

我们在第一讲的时候,已经了解到关于BeanFactory中的方法,也知道了BeanFactoryspring容器中最基本的接口,那么关于spring中众多的注解以及其他功能是谁来处理的呢?

首先:介绍一下关于BeanFactory的实现类DefaultListableBeanFactory,通过下面的代码可以得到ConfigurableApplicationContext接口实现类中的BeanFactory的实现类型.

ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// class org.springframework.beans.factory.support.DefaultListableBeanFactory 
System.out.println(beanFactory.getClass());

也可以跳转到ConfigurableApplicationContext实现类中观察。

请添加图片描述

首先我们需要明确BeanFactory的功能

beanFactory 不会做的事

  1. ​ 不会主动调用BeanFactory后处理器
  2. ​ 不会主动添加Bean后处理器
  3. ​ 不会主动初始化单例
  4. ​ 不会解析,BeanFactory不会解析 ${}和#{}

那么谁来做这些事情呢?答案就是后处理器小伙伴们。

首先看一段代码:

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AbstractBeanDefinition singleton = BeanDefinitionBuilder
        .genericBeanDefinition(Config.class)
        .setScope("singleton").getBeanDefinition();
beanFactory.registerBeanDefinition("config", singleton);

Config

@Configuration
static class Config{
    @Bean
    public Bean1 bean1(){
        return new Bean1();
    }
    @Bean
    public Bean2 bean2(){
        return new Bean2();
    }
}

static class Bean1{
    public Bean1() {
        System.out.println("构造bean1");
    }
    @Autowired
    private Bean2 bean2;

    public Bean2 getBean2(){
        return bean2;
    }
}
static class Bean2{
    public Bean2() {
        System.out.println("构造bean2");
    }
}

输出

for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
    System.out.println(beanDefinitionName);
}
// config

解释:

DefaultListableBeanFactoryBeanFacotry的实现类,所以第一句话是创建了一个bean工厂

AbstractBeanDefinitionBeanDefinition的实现类,第一讲时,我们介绍过关于BeanDefinition,简单理解为容器中的Bean对象的数据,通过genericBeanDefinition方法和getBeanDefinition获取到关于这个beanBeanDefinition

beanFactory.registerBeanDefinition("config", singleton);BeanDefinition放入bean工厂之中。

至此bean工厂中已经包含了我们创建的Config类了

说到这里,还是没有提到BeanFactory的后处理器小伙伴,那么这些后处理器到底是用来做什么的呢?

我们可以回过头去看看之前定义的Config类,可以看到里面包含了@Bean注解以及@Autowired ,不是说已经将Config类塞进BeanFactory中了吗?为什么依赖注入和@Bean注解没有生效呢?

 Map<String, BeanFactoryPostProcessor> beansProcessor = 
     beanFactory.getBeansOfType(BeanFactoryPostProcessor.class);

beansProcessor这个map集合中,里面包含了BeanFactory的后处理器小伙伴

// 输出
//org.springframework.context.annotation.internalConfigurationAnnotationProcessor
//org.springframework.context.annotation.internalAutowiredAnnotationProcessor
//org.springframework.context.annotation.internalCommonAnnotationProcessor
//org.springframework.context.event.internalEventListenerProcessor
//org.springframework.context.event.internalEventListenerFactory

通过遍历,我们将后处理器与bean工厂建立连接

beansOfType.values().stream().forEach(bean -> {
    bean.postProcessBeanFactory(beanFactory);
});
beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);

至此,我们的后处理小伙伴就加入了bean工厂,他们带来的效果则是处理了@Bean注解以及@Autowired注解等等。

后处理器也是有优先级的,例如我们的@Autowired@Resource@Autowired注解是在internalConfigurationAnnotationProcessor后处理器中处理的,而@Resource则是在internalCommonAnnotationProcessor后处理器中处理的,那么他们的优先级是多少呢?【优先级越小 优先级越高】

CommonAnnotationBeanPostProcessor源码中可以看出,order2147483644

请添加图片描述

@Autowired是在AutowiredAnnotationBeanPostProcessor中解析的,它的优先级则为2147483645

请添加图片描述

ApplicationContext的实现

  • ClassPathXmlApplicationContext: 基于classpathxml 格式的配置文件来创建
  • FileSystemXmlApplicationContext: 基于磁盘路径下 xml 格式的配置文件来创建
  • AnnotationConfigApplicationContext: 基于java配置类来创建
  • AnnotationConfigServletWebServerApplication: 基于java配置类来创建,并且还可以用于web环境

b01.xml

<?xml version="1.0" encoding="UTF-8"?>
<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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config />
    <bean id="bean1" class="com.qiao.Bean1" />
    <bean id="bean2" class="com.qiao.Bean2">
        <property name="bean1" ref="bean1"/>
    </bean>
</beans>

ClassPathXmlApplicationContext代码

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("b01.xml");
for (String name : context.getBeanDefinitionNames()) {
    System.out.println(name);
}
System.out.println(context.getBean(Bean2.class).getBean1());

FileSystemXmlApplicationContext代码

FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("src\\main\\resources\\b01.xml");
for (String name : context.getBeanDefinitionNames()) {
    System.out.println(name);
}
System.out.println(context.getBean(Bean2.class).getBean1());

AnnotationConfigApplicationContext代码

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
for (String name : context.getBeanDefinitionNames()) {
    System.out.println(name);
}
System.out.println(context.getBean(Bean2.class).getBean1());

AnnotationConfigServletWebServerApplication代码,模拟了 springboot web项目内嵌Tomcat的工作原理

AnnotationConfigServletWebServerApplicationContext context = 
    new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
// 防止程序终止
System.in.read();

配置类:

@Configuration
class WebConfig {
    @Bean
    // 1. WebServer工厂
    public ServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }

    @Bean
    // 2. web项目必备的DispatcherServlet
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    @Bean
    // 3. 将DispatcherServlet注册到WebServer上
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {
        return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    }
	// 如果bean中的内容为/ 开头,则为路径
    @Bean("/hello")
    public Controller controller1() {
        return (request, response) -> {
            response.getWriter().println("hello");
            return null;
        };
    }
}

Spring技术内幕补充

IoC容器的初始化过程

IoC容器的初始化是由refresh()方法来启动的,其中包含了Resouce定位、载入和注册三个基本的过程。

ClassPathResource res = new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);

第一个过程是Resouce定位过程。可以理解为BeanDefinition的资源定位

ClassPathResource res = new ClassPathResource("beans.xml");这段代码的作用是,让spring在类路径中寻找以文件形式存在的BeanDefinition信息。而spring是通过BeanDefinitionReader来对这些信息进行处理的,当我们再回头查看ApplicationContext的实现类时,我们发现,每个实现类都实现了ResourceLoader接口,用于资源定位

下面是ClassPathXmlApplicationContext的构造器源码,可以看出调用了refresh方法出发了整个BeanDefinition的定位过程

public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz, @Nullable ApplicationContext parent) throws BeansException {
    super(parent);
    Assert.notNull(paths, "Path array must not be null");
    Assert.notNull(clazz, "Class argument must not be null");
    this.configResources = new Resource[paths.length];

    for(int i = 0; i < paths.length; ++i) {
        this.configResources[i] = new ClassPathResource(paths[i], clazz);
    }

    this.refresh();
}

第二个过程是BeanDefinition的载入。把用户定义号的Bean表示成IoC容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition。这个BeanDefinition实际上就是类对象在IoC容器中的抽象,通过BeanDefinition,使IoC容器更好的管理Bean。

而在上面的ClassPathXmlApplicationContext源码中,我们并没有看到关于beanDefinitionReaderBeanDefinition的读入器)的定义,因为在设计的时候将读入器相关内容放在了ClassPathXmlApplicationContext的基类中

请添加图片描述

上面的代码片段中,可以清晰的看到XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);这段代码,同时下面也将读入器放到了DefaultListableBeanFactory中以及对BeanDefinition的载入

第三个过程是向IoC容器注册这些BeanDefinition。通过调用BeanDefinitionRegistry接口的实现来完成的。

值得注意的是,bean定义的载入,是不包含依赖注入的,这也说明了之前我们在不使用后处理器的时候,只注册了Config

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值