Spring底层原理分析-二(容器的具体实现)

106 阅读8分钟

  上一节简单叙述了容器原始接口的相关的基本知识,这一节继续来探讨,容器的那些具体的实现都有哪些,都有什么功能。

DefaultListableBeanFactory

  这个实现前面简单的提过,他是BeanFactory最重要的实现,那就从这里继续开始吧。先来看段代码。

public class DemoBeanFactory {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        AbstractBeanDefinition beanDefinition =
                BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
        beanFactory.registerBeanDefinition("config", beanDefinition);
        String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
    @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构造函数执行");
        }
    }
}

输出结果:
config

   分析一下这段代码的含义,每个步骤的作用如下:

  • 先创建DefaultListableBeanFactory容器的对象,后面所有的bean实例都是由它来进行创建的;
  • 但是创建之前,该容器并不知道谁是bean,需要生成什么样子的bean,这里出现一个新名词,BeanDefinition,翻译一下的意思就是bean定义的意思。正如其名,它内部的各种参数方法来实现对bean的定义。比如,bean是什么类型、scope属性是什么、初始化和销毁方法是什么等等。
  • 创建了一个测试bean的类型Config,里面随意写了两个通过@Bean注解的bean对象;
  • 通过BeanDefinitionBuilder的genericBeanDefinition方法声明bean类型为Config,并且设置singleton参数表明为单例模式;
  • 将该BeanDefinition加入容器中,并指定bean的名字为config,这样启动的时候就会生成该定义下的bean实例;
  • 获取容器的所有bean实例,打印验证,结束;

AnnotationConfigUtils

  我们看到打印结果为config,好像没什么太大问题,把我们定义好的Config类型bean生成了出来。但是注意,Config里面有两个被注解的bean,好像并没有被创建出来,这好像和我们之前理解的不太一样。原因就是,我们使用的DefaultListableBeanFactory并没有给我们提供解析这些注解的功能,我们使用SpringBoot框架通过SpringApplication的run方法启动的容器已经配置好了该功能,所以我们使用@Bean也好,还是说@Controller、@Service等都可以加入容器中。

  原因找到了,模拟其他具备该功能的容器加入该功能即可,我们在获取容器所有bean实例之前,加入如下代码段。这段代码的意思就是在创建好的beanFactory中注册一部分后处理器,通过方法名也可以看到,注册的就是注解配置相关的后处理器。

AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

  加入后,我们再次打印观看结果。

image.png   看到除了config之外,多了五个后置处理器bean实例,通过名字不难看出,第一个就是处理@Configuration注解后处理器,第二个是处理@Autowired后处理器,第三个是@Resource相关注解的后处理器,以及后面两个事件监听相关的注解后处理器。

  那么问题又来了,这处理注解的处理器都加上了,两个bean怎么还是没有,这点其实有点绕,我们上面那一行代码,只是把处理器加入容器中,但是并没有运行,我们再在刚才那一行代码中加入以下代码片段。这段含义就是,先从容器中根据类型找到BeanFactoryPostProcessor类型的所有bean,我们刚才加入的注解配置相关处理器就是属于BeanFactoryPostProcessor,所以继续便利这些bean,挨个执行每一个后处理器。

BeanFactoryPostProcessor

//获取并循环执行BeanFactoryPostProcessor
beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values()
        .forEach(beanFactoryPostProcessor -> {
            beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
        });

  继续看执行后的结果

image.png

  可以看到两个bean被加载到了容器中,我们再次回顾上面的代码,好像没有遗漏什么了。我在Bean1类里面使用@Autowired注解注入了Bean2类型的bean,根据我们加入并执行的后置处理器中可以看到,有用来处理该注解的处理器,那么不出意外是注入没问题的。我们使用下面一段代码打印一下该属性。

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

image.png

  en。。。又出问题了,bean1是被成功创建了,也执行了构造方法。但是无法从容器中的bean1中获取到成员变量bean2的值,那就是并没有注入成功。

BeanPostProcessor

  再看AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);这一行代码。这是我们给beanFactory加入注解相关的后置处理器,为了解析那些加了注解的bean的加载。但是Bean1中的@Autowired并不是类本身的注解,这是它内部一个成员变量的一个注解。所以它需要自身的后置处理器,也就是BeanPostProcessors,即BeanFactoryPostProcessors只管理bean本体。所以它不会留意到Bean1类中的@Autowired。

  问题找到了,那就给它加上相关的BeanPostProcessors就好了,其实,这一部分,就在上面那五个之中,第二个第三个分别是解析@Autowired和@Resource的BeanPostProcessors。ok,那我们就照着再写一个获取并循环执行的代码。

//获取并循环添加BeanPostProcessor
Collection<BeanPostProcessor> beanPostProcessors = beanFactory.getBeansOfType(BeanPostProcessor.class).values();
beanFactory.addBeanPostProcessors(beanPostProcessors);

  加入后置处理器的方式和BeanFactoryPostProcessors不太一样,这里直接调用容器的添加方法就可以。

image.png

  这里扩展一个知识点,我们看到运行结果可以看到,只有每次getBean的时候才会调用构造方法进行初始化创建对象,我们加入beanFactory.preInstantiateSingletons();,使单例对象直接创建出来。

后置处理器执行顺序

  我们在刚才的类里面接着加入Bean3和Bean4,并通过@Bean注解也加入到容器中,

interface TestInterface{}
static class Bean3 implements TestInterface{}
static class Bean4 implements TestInterface{}
@Autowired
private TestInterface testInterface;//这里注入的是bean3还是bean4
public TestInterface getTestInterface() {
    return testInterface;
}

  如图中所问,我们注入接口类,因为有两个实现的缘故,那么注入的到底是谁,我们先说结果,程序因分不清要注入谁,会报找到多个的错误。我们之前学过,这时候只要指定名字,或把testInterface改名为bean3或者bean4即可,这里我们先改成bean3,结果就会正确了。

  这里我们做个反常态的事情,再加一个@Resource(name = "bean4"),这时候我们调用我们刚加入的getTestInterface()方法,可以获取到实例是bean3。@Resource注解并没有起到什么作用,这就引入了后置处理器加载顺序的一个问题,在底层源码中,@Autowired的解析器在前,所先生效,而且生效后不再走后面的处理器。

   我们也可以在上面加入BeanPostProcessor后置处理器那一个环节,自定义顺序来进行控制。这里说一下,beanFactory提供的有一个排序规则getDependencyComparator,是按照每个后置处理器内的一个order参数来控制的,数越小优先级越靠前。这里@Resource的后处理器是比@Autowired的优先级更加靠前。

ApplicationContext

  容器基本的功能其实在上面BeanFactory方面已经叙述差不多了,ApplicationContext大题上是一种扩展,主要的实现分为以下几种。

ClassPathXmlApplicationContext和FileSystemXmlApplicationContext

   ClassPathXmlApplicationContext在早期使用是很频繁的,直接读取类路径下的xml文件,在xml文件里面配置bean的一系列信息。FileSystemXmlApplicationContext作用和它其实一样,只是读取的目录不同罢了,这个是读取系统文件的目录。

  但是他们内部,还是用的BeanFactory的一系列实现,这个在上一篇也提到过,那么它们是怎么运用的呢,如下代码所述,一样的声明一个DefaultListableBeanFactory,然后不同点来了,上面bean的定义是有一个AbstractBeanDefinition来处理,而这里改成了读取类路径下的xml文件。

public static void main(String[] args) {
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
    reader.loadBeanDefinitions("src/main/resources/bean.xml");
}

AnnotationConfigApplicationContext

  由名字可看出,和注解有关系,它就是以注解来进行一系列配置的容器,和上面的串一下,其实它就是底层做了添加各种后置处理器的逻辑。在上面读取xml的两个容器中也有相关的配置,就是我们之前写过的<context:annotation-config/>和<context:component-scan/>标签,这两个标签后者在扫描的过程中也包含了前者的后置处理器的加入。

AnnotationConfigServletWebServerApplicationContext

  还是从名字分析,也是一种注解配置的容器,但是多了ServletWebServer,是一种web环境下的容器。

public static void main(String[] args) {
    AnnotationConfigServletWebServerApplicationContext context =
            new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
}

@Configuration
static class WebConfig{
    //提供能够提供ServletWebServer的组件,这里选用Tomcat的内置组件。
    @Bean
    public ServletWebServerFactory servletWebServerFactory(){
        return new TomcatServletWebServerFactory();
    }
    //请求入口点,前控制器配置
    @Bean
    public DispatcherServlet dispatcherServlet(){
        return new DispatcherServlet();
    }
    //将DispatcherServlet注册到Tomcat服务器
    @Bean
    public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet){
        return new DispatcherServletRegistrationBean(dispatcherServlet,"/");
    }
    //定义控制器作为测试
    @Bean("/hi")
    public Controller controller(){
        return (request, response) -> {
            response.getWriter().println("hi");
            return null;
        };
    }
}

  当以上代码运行后,访问localhost:8080/hi就可以获取到一个显示“hi”的界面,整体流程为先确定ServletWebServer提供者,再定义请求入口“/”,即DispatcherServletRegistrationBean传入的参数,当有符合请求时传入到DispatcherServlet做路径分发,访问自定义的控制器。这里是一个web应用的简化,整体流程是这样,但是SpringBoot内部其实在该方面也做了很多的功能上的补充。

image.png