【探索Spring底层】2.容器的实现

导读:本篇文章讲解 【探索Spring底层】2.容器的实现,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

【探索Spring底层】容器的实现

1. BeanFactory实现的特点

BeanFactory提供的功能没有太丰富,它的一些功能的实现都是由BeanFactory后处理器和Bean后处理器提供的

下面就是模拟一下BeanFactory实现Bean的定义,以及@Autowired @Resource@Bean注解的实现

首先创建一个BeanFactory

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

这是新创建的BeanFactory,里面什么都没有,我们需要进行bean 的定义(class, scope, 初始化, 销毁)

下面是模拟的Config、Bean1和Bean2是模拟的对象,然后接下来将会将Config定义到BeanFactory中

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

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }
}
static class Bean1 {
    private static final Logger log = LoggerFactory.getLogger(Bean1.class);

    public Bean1() {
        log.debug("构造 Bean1()");
    }

    @Autowired
    private Bean2 bean2;

    public Bean2 getBean2() {
        return bean2;
    }
}

static class Bean2 {
    private static final Logger log = LoggerFactory.getLogger(Bean2.class);

    public Bean2() {
        log.debug("构造 Bean2()");
    }
}

进行bean的定义

使用BeanDefinitionBuilder的genericBeanDefinition方法将Config定义Bean,然后设置Bean标签范围为singleton(单例),接着使用beanFactory的registerBeanDefinition方法将config这个Bean注册到BeanFactory中

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

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

运行就可以发现有个config的bean,但是观察上面代码可以发现Config用@Bean注解也将Bean1以及Bean2也定义到BeanFactory中,但是这里输出却没有,这是因为@Bean以及@Configuration并没有生效!!!

在这里插入图片描述

因为解析@Bean以及@Configuration的功能BeanFactory并没有,因此需要给 BeanFactory 添加一些常用的后处理器

AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

添加BeanFactory 后处理器之后,就能发现BeanFactory 中多了几个后处理器。

BeanFactoryPostProcessor

  • internalConfigurationAnnotationProcessor对应的类是ConfigurationClassPostProcessor,它是用来对 @Controller @Component @Bean @Configuration @Service @ComponentScan@PropertySource @Import @ImportResource 进行注解扫描

BeanPostProcessor

  • internalAutowiredAnnotationProcessor 对应的类是AutowiredAnnotationBeanPostProcessor,它是用来对**@Autowired @Value**进行注解扫描,实现属性填充
  • internalCommonAnnotationProcessor 对应的类是 CommonAnnotationBeanPostProcessor,它是用来对**@Resource @PreDestory @PostConstruct** 进行扫描的,实现属性填充

在这里插入图片描述

接下来就可以通过BeanFactoryPostProcessor来对BeanFactory补充一下bean的定义

beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryPostProcessor -> {
    //执行bean工厂处理器,执行到注解的时候,
    beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
});

这时候就可以看到bean1和bean2了

在这里插入图片描述

然后尝试一下从bean1中获取@Autowired注入的Bean2,发现为null

System.out.println(beanFactory.getBean(Bean1.class).getBean2());

在这里插入图片描述

这是因为@Autowired注解没有生效,使用 BeanPostProcessor 针对 bean 的生命周期的各个阶段提供扩展, 例如 @Autowired @Resource

beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream()
        .forEach(beanPostProcessor -> {
            System.out.println(">>>>" + beanPostProcessor);
            beanFactory.addBeanPostProcessor(beanPostProcessor);
        });

这时候再获取Bean2就有值了

在这里插入图片描述

这些Bean的实例化,只有在getBean的时候才会实例化,如果想要提前实例化号,可以使用preInstantiateSingletons,提前准备好实例

beanFactory.preInstantiateSingletons(); // 准备好所有单例(预先实例化,否则只有在getBean才会实例化)

1.1 同时使用@Autowired 和 @Resource 注解会发生什么?

首先先改装一下上面的Bean1

static class Bean1 {
    private static final Logger log = LoggerFactory.getLogger(Bean1.class);

    public Bean1() {
        log.debug("构造 Bean1()");
    }

    @Autowired
    private Bean2 bean2;

    public Bean2 getBean2() {
        return bean2;
    }

    @Autowired 
    private Inter bean;

    public Inter getInter() {
        return bean3;
    }
}

static class Bean3 implements Inter {

}

static class Bean4 implements Inter {

}

interface Inter {

}

这样的话,肯定报错,因为Bean3和Bean4都实现了Inter接口,Spring无法得知要注入的是Bean3还是Bean4

但是我们把private Inter bean改为private Inter bean3,这样@Autowired在发现Bean3和Bean4都实现了Inter的前提下,会先查找名字一样的,然后就会找到Bean3

那如果加了Resource注解呢?

@Autowired
@Resource(name = "bean4") 
private Inter bean3;

这样这个实例化的是Bean3还是Bean4???

System.out.println(beanFactory.getBean(Bean1.class).getInter());

在这里插入图片描述

emmm这里实例化的是Bean3,这是为什么呢?

那是因为Bean后处理会有排序逻辑,哪个后处理器的优先级高,那么就谁先解析这个注解

在前面添加bean后处理器的地方输出bean后处理器

beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream()
        .forEach(beanPostProcessor -> {
    System.out.println(">>>>" + beanPostProcessor);
    beanFactory.addBeanPostProcessor(beanPostProcessor);
});

在这里插入图片描述

可以发现第一个bean后处理器是用来解析@Autowired,所以先解析@Autowired,注入的就是Bean3了

那这个顺序可以改变吗?当然可以,通过比较器即可

beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream()
        .sorted(beanFactory.getDependencyComparator())
        .forEach(beanPostProcessor -> {
    System.out.println(">>>>" + beanPostProcessor);
    beanFactory.addBeanPostProcessor(beanPostProcessor);
});

在这里插入图片描述

现在就变成了先解析@Resource,所以注入的是Bean4

那么比较器是怎么设置的呢???

在前面添加BeanFactory的时候,AnnotationConfigUtils里面有一个Set< BeanDefinitionHolder >方法会给这些BeanFactory设置比较器

// 给 BeanFactory 添加一些常用的后处理器(还未运行)
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

在这里插入图片描述

那么Bean后处理器的顺序哪里来呢??

在每个Bean后处理器中都会有相应的get/set方法

public void setOrder(int order) {
   this.order = order;
}

@Override
public int getOrder() {
   return this.order;
}
public CommonAnnotationBeanPostProcessor() {
   setOrder(Ordered.LOWEST_PRECEDENCE - 3);
   setInitAnnotationType(PostConstruct.class);
   setDestroyAnnotationType(PreDestroy.class);
   ignoreResourceType("javax.xml.ws.WebServiceContext");
}

在这里插入图片描述

在这里就设置了顺序,明显可以看出AutowiredAnnotationBeanPostProcessor比较前,因为它只是-2,而CommonAnnotationBeanPostProcessor是减3


2. ApplicationContext的常见实现和用法

ApplicationContext有几个较为经典的容器

  • ClassPathXmlApplicationContext
  • FileSystemXmlApplicationContext
  • AnnotationConfigApplicationContext
  • AnnotationConfigServletWebServerApplicationContext

下面是一些准备数据

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

    @Bean
    public Bean2 bean2(Bean1 bean1) {
        Bean2 bean2 = new Bean2();
        bean2.setBean1(bean1);
        return bean2;
    }
}

static class Bean1 {
}

static class Bean2 {

    private Bean1 bean1;

    public void setBean1(Bean1 bean1) {
        this.bean1 = bean1;
    }

    public Bean1 getBean1() {
        return bean1;
    }
}

2.1 ClassPathXmlApplicationContext

该容器是基于 classpath 下 xml 格式的配置文件来创建,用来读取xml文件

不过这种读取xml获得bean的方法用得很少了,因为现在大多是SpringBoot开发,这些都自动装配好了

private static void testClassPathXmlApplicationContext() {
    ClassPathXmlApplicationContext context =
            new ClassPathXmlApplicationContext("a02.xml");

    for (String name : context.getBeanDefinitionNames()) {
        System.out.println(name);
    }

    System.out.println(context.getBean(Bean2.class).getBean1());
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 控制反转, 让 bean1 被 Spring 容器管理 -->
    <bean id="bean1" class="com.itheima.a02.A02.Bean1"/>

    <!-- 控制反转, 让 bean2 被 Spring 容器管理 -->
    <bean id="bean2" class="com.itheima.a02.A02.Bean2">
        <!-- 依赖注入, 建立与 bean1 的依赖关系 -->
        <property name="bean1" ref="bean1"/>
    </bean>
</beans>

在这里插入图片描述


2.2 FileSystemXmlApplicationContext

FileSystemXmlApplicationContext基于磁盘路径下 xml 格式的配置文件来创建

用法和ClassPathXmlApplicationContext一样,就是读取的是相对路径和绝对路径

private static void testFileSystemXmlApplicationContext() {
    FileSystemXmlApplicationContext context =
        new FileSystemXmlApplicationContext(
        "src\\main\\resources\\a02.xml");
    for (String name : context.getBeanDefinitionNames()) {
        System.out.println(name);
    }

    System.out.println(context.getBean(Bean2.class).getBean1());
}

2.3 结合BeanFactory

可以创建一个新的BeanFactory

利用XmlBeanDefinitionReader读取xml文件中的Bean,构建参数为BeanFactory对象

然后将xml中的Bean加载到BeanFactory中

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
System.out.println("读取之前...");
for (String name : beanFactory.getBeanDefinitionNames()) {
    System.out.println(name);
}
System.out.println("读取之后...");
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(new FileSystemResource("src\\main\\resources\\a02.xml"));
for (String name : beanFactory.getBeanDefinitionNames()) {
    System.out.println(name);
}

2.4 AnnotationConfigApplicationContext

这种也是经典容器之一,是基于Java配置类创建的

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

    @Bean
    public Bean2 bean2(Bean1 bean1) {
        Bean2 bean2 = new Bean2();
        bean2.setBean1(bean1);
        return bean2;
    }
}
private static void testAnnotationConfigApplicationContext() {
    AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext(Config.class);

    for (String name : context.getBeanDefinitionNames()) {
        System.out.println(name);
    }

    System.out.println(context.getBean(Bean2.class).getBean1());
}

读取配置Config类,即可获取到其中的bean

在这里插入图片描述

这其中的一些Bean后处理器,也会自动添加上,这就是ApplicationContext比BeanFactory的好处

不需要手动人为添加后处理器

假如使用FileSystemXmlApplicationContext和ClassPathXmlApplicationContext就需要我们手动添加这些Bean后处理器

在SSM学习阶段,我们都需要在xml文件中假如以下标签

<context:annotation-config/>

这个标签就是帮助我们将这些后处理器加上,所以学习SSM的时候才没感觉那么麻烦


2.5 AnnotationConfigServletWebServerApplicationContext

AnnotationConfigServletWebServerApplicationContext也是基于Java配置类来创建的,主要用于Web环境

我们可以内嵌容器,像传统的SSM开发,必须依赖Tomcat服务程序才可以跑起来,而SpringBoot项目的话,已经内嵌容器了,所以不需要依赖外部的Tomcat也能将程序跑起来

代码中的ServletWebServerFactory就是内嵌容器Tomcat

而DispatcherServlet是前端控制器,也就是,浏览器的请求和响应都是经过前端控制器来处理的

在这里插入图片描述

除了这两个组件还是不够的

还需要DispatcherServletRegistrationBean注册DispatcherServlet到tomcat服务器

有了这三个基本的组件,一个内嵌服务的web服务就算完成了

除此之外,Controller一般通过@Controller来实现,这里通过org.springframework.web.servlet.mvc.Controller这个接口来实现原生的接口,直接用@Bean注解来实现,这里@Bean有个规定,就是以/开头,/后面的名称作为这个接口的访问路径,当访问hello就会进行controller1方法里面的逻辑

因为Controller接口是函数式接口,所以可以简写

@Configuration
static class WebConfig {
    @Bean
    public ServletWebServerFactory servletWebServerFactory(){
        return new TomcatServletWebServerFactory();
    }
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }
    @Bean
    public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
        return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    }
    @Bean("/hello")
    public Controller controller1() {
        return (request, response) -> {
            response.getWriter().print("hello");
            return null;
        };
    }
}
private static void testAnnotationConfigServletWebServerApplicationContext() {
    AnnotationConfigServletWebServerApplicationContext context =
            new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    for (String name : context.getBeanDefinitionNames()) {
        System.out.println(name);
    }
}

在这里插入图片描述

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/94994.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!