一、Spring学习 : 容器---->BeanFactory+ApplicationContext 的多种容器实现

发布时间 2023-12-05 23:40:22作者: VayneBeSelf

BeanFactory实现的特点

我们来着重讲一下DefaultListableBeanFactory这个实现类:

点击查看完整代码
package com.itvayne.springbootcloudstudy.beanfactory01;

import com.sun.org.slf4j.internal.Logger;
import com.sun.org.slf4j.internal.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @program: cloud
 * @description:
 * @author: Vayne
 * @create: 2023-12-05
 **/
public class TestBeanFactory01 {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        //我们需要对bean的定义,描述一个bean的类型,初始化方法,销毁方法,以及我们常说的单例还是多例等信息
        //我们下方的Bean1和Bean2其实依赖于我们的Config类,那我们先来定义一个Config类的定义类

        //定一个Config类的描述定义类(有点拗口,各自理解一下)
        AbstractBeanDefinition beanDefinition =
                BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
        //将这个描述类交给beanFactroy制作出一个Config类对象
        beanFactory.registerBeanDefinition("config", beanDefinition);

        //我们此时可以遍历beanFactory,查看beanFactory中有哪些bean了
        for (String bean : beanFactory.getBeanDefinitionNames()) {
            System.out.println(bean);
        }
        //通过观察我们发现,没有Bean1和Bean2,这是为什么?,说明BeanFactory并不具备解析我们@bean的能力
        //那怎么让他具备呢?
        //通过下面这个工具类中的registerAnnotationConfigProcessors添加注解配置的后处理器,对BeanFactory的解析能力进行增强
        System.out.println("============1.解析Bean1和Bean2,加入后处理器==============");
        AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
        //此时我们再来观察BeanFactory
        for (String bean : beanFactory.getBeanDefinitionNames()) {
            System.out.println(bean);
        }
        /**
         * 发现多了这五个类,我们来说下他们分别是干嘛的
         * org.springframework.context.annotation.internalConfigurationAnnotationProcessor,处理@Configuration,以及@Bean注解的后处理器
         * org.springframework.context.annotation.internalAutowiredAnnotationProcessor,处理@Autowired以及@value注解的后处理器
         * org.springframework.context.annotation.internalCommonAnnotationProcessor,处理@Resource注解
         * org.springframework.context.event.internalEventListenerProcessor
         * org.springframework.context.event.internalEventListenerFactory
         */

        System.out.println("===========2.解析Bean1和Bean2===============");

        //那为什么有了后处理器我们依然看不到Bean1和Bean2呢???
        //我们还需要对后处理器进行调用
        //根据beanFactory拿到里面所有的后处理器,然后遍历,将每个后处理器执行在BeanFactory上,然后再次打印观察
        beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().stream().forEach(beanFactoryPostProcessor -> {
            beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
        });
        beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream()
                .sorted(beanFactory.getDependencyComparator())
                .forEach(beanPostProcessor -> {
                    System.out.println(">>>>>>>>>" + beanPostProcessor);
                    beanFactory.addBeanPostProcessor(beanPostProcessor);
                });
        beanFactory.preInstantiateSingletons();
        for (String bean : beanFactory.getBeanDefinitionNames()) {
            System.out.println(bean);
        }
        System.out.println("==========================");
        //通过输出结果我们看到了Bean1和Bean2,此时我们来看看@Autowired有没有生效
        //System.out.println(beanFactory.getBean(Bean1.class).getBean2());
        //查看结果,我们看到虽然Bean1和Bean2通过Config类的@Bean识别出来了,但是Bean1中注入的Bean2缺没有生效,说明@Autowired没有识别
        //我们上面看到的Config类中识别出了Bean1和Bean2其实是利用了BeanFactory的后处理器,BeanFactoryPostProcessor
        //而此时我们需要一个对于Bean的后处理器来帮我们识别Bean的各个生命周期中的注解,比如@Autowired,@Resource等等


        System.out.println("============查看Bean的加载时机==============");
        System.out.println(beanFactory.getBean(Bean1.class).getBean2());
        //此时我们仍然获取不到,因为在上面我们循环获取的时候,Bean已经初始化完成,所以要将Bean后处理器放在前面
        //beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);放在Bean加载之前

        //我们看到这个Bean其实是在我们getBean的时候才加载的,如果需要一开始就加载好就需要
        //beanFactory.preInstantiateSingletons();
    }

    @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()");
        }
    }
}

public class TestBeanFactory01 {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        //我们需要对bean的定义,描述一个bean的类型,初始化方法,销毁方法,以及我们常说的单例还是多例等信息
        //我们下方的Bean1和Bean2其实依赖于我们的Config类,那我们先来定义一个Config类的定义类

        //定一个Config类的描述定义类(有点拗口,各自理解一下)
        AbstractBeanDefinition beanDefinition =
                BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
        //将这个描述类交给beanFactroy制作出一个Config类对象
        beanFactory.registerBeanDefinition("config", beanDefinition);

        //我们此时可以遍历beanFactory,查看beanFactory中有哪些bean了
        String[] beans = beanFactory.getBeanDefinitionNames();
        for (String bean : beans) {
            System.out.println(bean);
        }
        
    }

    @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("Kiti 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("Miti Bean2()");
        }
    }
}

通过观察我们发现,没有Bean1和Bean2,这是为什么?,说明BeanFactory并不具备解析我们@bean的能力

//那怎么让他具备呢?
        //通过下面这个工具类中的registerAnnotationConfigProcessors添加注解配置的后处理器,对BeanFactory的解析能力进行增强
        System.out.println("==========================");
        AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
        //此时我们再来观察BeanFactory
        for (String bean : beanFactory.getBeanDefinitionNames()) {
            System.out.println(bean);
        }
        /**
         * 发现多了这五个类,我们来说下他们分别是干嘛的
         * org.springframework.context.annotation.internalConfigurationAnnotationProcessor,处理@Configuration,以及@Bean注解的后处理器
         * org.springframework.context.annotation.internalAutowiredAnnotationProcessor,处理Autowired注解的后处理器
         * org.springframework.context.annotation.internalCommonAnnotationProcessor
         * org.springframework.context.event.internalEventListenerProcessor
         * org.springframework.context.event.internalEventListenerFactory
         */

        System.out.println("==========================");

        //那为什么有了后处理器我们依然看不到Bean1和Bean2呢???
        //我们还需要对后处理器进行调用
        //根据beanFactory拿到里面所有的后处理器,然后遍历,将每个后处理器执行在BeanFactory上,然后再次打印观察
        beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().stream().forEach(beanFactoryPostProcessor -> {
            beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
        });
        for (String bean : beanFactory.getBeanDefinitionNames()) {
            System.out.println(bean);
        }
beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().stream().forEach(beanFactoryPostProcessor -> {
            beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
        });
        beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);
        beanFactory.preInstantiateSingletons();
        for (String bean : beanFactory.getBeanDefinitionNames()) {
            System.out.println(bean);
        }
        System.out.println("==========================");
        //通过输出结果我们看到了Bean1和Bean2,此时我们来看看@Autowired有没有生效
        //System.out.println(beanFactory.getBean(Bean1.class).getBean2());
        //查看结果,我们看到虽然Bean1和Bean2通过Config类的@Bean识别出来了,但是Bean1中注入的Bean2缺没有生效,说明@Autowired没有识别
        //我们上面看到的Config类中识别出了Bean1和Bean2其实是利用了BeanFactory的后处理器,BeanFactoryPostProcessor
        //而此时我们需要一个对于Bean的后处理器来帮我们识别Bean的各个生命周期中的注解,比如@Autowired,@Resource等等

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

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>");
        System.out.println(beanFactory.getBean(Bean1.class).getBean2());
        //此时我们仍然获取不到,因为在上面我们循环获取的时候,Bean已经初始化完成,所以要将Bean后处理器放在前面
        //beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);放在Bean加载之前

        //我们看到这个Bean其实是在我们getBean的时候才加载的,如果需要一开始就加载好就需要
        //beanFactory.preInstantiateSingletons();

那么到此我们发现BeanFactory有个初步的了解

  1. 不会主动调用BeanFactroy后处理器

  2. 不会主动添加Bean后处理器

  3. 不会主动初始化实例

  4. 不会解析BeanFactory,也不能解析 ${ } 和 #{}

那么我们即将学习的ApplicationContext将会给我们准备好这些东西

然后我们来看一下这个后处理器,那么后处理器的顺序决定了哪个注解先被解析

比如我们@Recourse 和 @Autowired 两个注解,如果一个接口有两个实现,那么我们利用@Autowired的先ByType再ByName的特性,注入Bean1,然后利用@Resource注入Bean2

那么到底会发生什么,其实这个注入Bean1还是Bean2,取决于Bean后处理器的加载顺序,这两个对应的Bean后处理器分别是:

org.springframework.context.annotation.internalAutowiredAnnotationProcessor ->@Autowired
org.springframework.context.annotation.internalCommonAnnotationProcessor->@Resource

那么我们在添加BeanPostProcessor的时候则决定了他的顺序,而顺序其实是通过实现了Order的接口来完成的,如果需要修改顺序优先级,可以在遍历添加的时候加入.sorted(beanFactory.getDependencyCmparator())

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

ApplicationContext的常见实现和用法

来说说四个很典型的实现类

ClassPathXmlApplicationContext();
FileSystemXmlApplicationContext();
AnnotationConfigApplicationContext();
AnnotationConfigServletWebServerApplicationContext();

来看看他们于ApplicationContext之间的关系

点击查看完整代码

package com.itvayne.springbootcloudstudy.applicationcontext01;

import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @program: cloud
 * @description:
 * @author: Vayne
 * @create: 2023-12-05
 **/
public class ApplicationContext01 {

    public static void main(String[] args) {
//        testClassPathXmlApplicationContext();
//        testFileSystemXmlApplicationContext();
//        testAnnotationConfigApplicationContext();
        testAnnotationConfigServletWebServerApplicationContext();
    }

    //软为经典的容器,基于 cLasspath下xmL 格式的配置文件来创建
    private static void testClassPathXmlApplicationContext() {
        ClassPathXmlApplicationContext ctx =
                new ClassPathXmlApplicationContext("b01.xml");
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>");
        for (String beanDefinitionName : ctx.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
        //发现这个容器想比于BeanFactory来讲直接就可以获取到
        System.out.println(ctx.getBean("bean2", Bean2.class).getBean1());
        System.out.println("<<<<<<<<<<<<<<<<<<<<<<<<<<<");

        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        System.out.println("读取前...");
        for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
        System.out.println("读取后...");
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions(new ClassPathResource("b01.xml"));
        for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
    }

    //基于磁盘路径下XmL 格式的配置文件来创建
    private static void testFileSystemXmlApplicationContext() {
        FileSystemXmlApplicationContext ctx =
                new FileSystemXmlApplicationContext(
                        "//Users/vayne/IdeaProjects/cloud/src/main/resources/b01.xml");

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>");
        for (String beanDefinitionName : ctx.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
        //发现这个容器想比于BeanFactory来讲直接就可以获取到
        System.out.println(ctx.getBean("bean2", Bean2.class).getBean1());
    }


    //较为经典的容器,基于 java 配置类来创建
    private static void testAnnotationConfigApplicationContext() {
        AnnotationConfigApplicationContext ctx =
                new AnnotationConfigApplicationContext(Config.class);
        for (String beanDefinitionName : ctx.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
        System.out.println(ctx.getBean("bean2", Bean2.class).getBean1());
    }

    private static void testAnnotationConfigServletWebServerApplicationContext() {
        AnnotationConfigServletWebServerApplicationContext ctx =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }

    @Configuration
    static class WebConfig {
        @Bean
        public ServletWebServerFactory servletWebServerFactory() {
            return new TomcatServletWebServerFactory();
        }

        @Bean
        public DispatcherServlet dispatcherServlet() {
            return new DispatcherServlet();
        }

        @Bean
        public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {
            return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        }

        @Bean("/hello")
        public Controller controller() {
            return (request, response) -> {
                response.getWriter().println("hello");
                return null;
            };
        }
    }


    @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;
        }
    }
}


<?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">

    <bean id="bean1"
          class="com.itvayne.springbootcloudstudy.applicationcontext01.ApplicationContext01$Bean1"/>
    <bean id="bean2"
          class="com.itvayne.springbootcloudstudy.applicationcontext01.ApplicationContext01$Bean2">
        <property name="bean1" ref="bean1"/>
    </bean>
</beans>

ClassPathXmlApplicationContext

我们先来看看ClassPathXmlApplicationContext();

ClassPathXmlApplicationContext ctx =
                new ClassPathXmlApplicationContext("b01.xml");
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>");
        for (String beanDefinitionName : ctx.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
        //发现这个容器想比于BeanFactory来讲直接就可以获取到
        System.out.println(ctx.getBean("bean2", Bean2.class).getBean1());

static class Bean1 {
    }

    static class Bean2 {
        private Bean1 bean1;

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

        public Bean1 getBean1() {
            return bean1;
        }
    }

其实我们写过原生spring框架的朋友可能再熟悉不过了,但是现在可能编写配置文件已经成为历史

FileSystemXmlApplicationContext

再来看看FileSystemXmlApplicationContext();不同的是从磁盘路径开始的,当然相对路径绝对路径都可以

private static void testFileSystemXmlApplicationContext() {
        FileSystemXmlApplicationContext ctx =
                new FileSystemXmlApplicationContext(
                        "//Users/vayne/IdeaProjects/cloud/src/main/resources/b01.xml");

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>");
        for (String beanDefinitionName : ctx.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
        //发现这个容器想比于BeanFactory来讲直接就可以获取到
        System.out.println(ctx.getBean("bean2", Bean2.class).getBean1());
    }

static class Bean1 {
    }

    static class Bean2 {
        private Bean1 bean1;

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

        public Bean1 getBean1() {
            return bean1;
        }
    }

那么两种根据路径实现的容器的简单使用讲完了,那么内部是怎么实现的从xml文件读取配置加载到容器中的呢?

手写一个简单实现:

 DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        System.out.println("读取前...");
        for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
        System.out.println("读取后...");
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions(new ClassPathResource("b01.xml"));
        for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }

再来看看spring的实现方式:

当然FileSystemXmlApplicationContext也是如此,只是直接获取的

通过前两个实现,我们知道ApplicationContext可以帮我们实例化单例,以及加载Bean的定义信息,这个信息可以来自配置文件

AnnotationConfigApplicationContext

简单使用

AnnotationConfigApplicationContext ctx =
                new AnnotationConfigApplicationContext(Config.class);
        for (String beanDefinitionName : ctx.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
        System.out.println(ctx.getBean("bean2", Bean2.class).getBean1());

@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;
        }
    }

AnnotationConfigServletWebServerApplicationContext

用于web环境的一种经典容器

想要使用它,其实并不简单,web容器中我们必须最少配置三个Bean,分别是Tomcat容器工厂,前处理器DipatcherServlet,将DipatcherServlet注册到Tomcat中的Bean,这三个bean是必须的,前两者在第三者中关联起来

private static void testAnnotationConfigServletWebServerApplicationContext() {
        AnnotationConfigServletWebServerApplicationContext ctx =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }

    @Configuration
    static class WebConfig {
        @Bean
        public ServletWebServerFactory servletWebServerFactory() {
            return new TomcatServletWebServerFactory();
        }

        @Bean
        public DispatcherServlet dispatcherServlet() {
            return new DispatcherServlet();
        }

        @Bean
        public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {
            return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        }

        @Bean("/hello")
        public Controller controller() {
            return (request, response) -> {
                response.getWriter().println("hello");
                return null;
            };
        }
    }

这里定义的第四个为web容器的控制器,有个隐藏规则,如果bean的名称是以"/"开头,则直接可以当作路径访问,启动之后可以使用接口调用访问到这个路径并返回"hello"信息

小结:

  1. 内嵌Tomcat如何工作的?

  2. 所有的请求都被前处理器dispatcherServlet拦截,然后经过前处理器进一步分发请求到了我们的控制器

  3. 其实我们这里没有使用springBoot,而其实springBoot内部的web环境也需要这样去工作,我们后面再进行学习