3

SpringBoot成长记5:Spring容器的创建

 2 years ago
source link: https://segmentfault.com/a/1190000040797516
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

SpringBoot成长记5:Spring容器的创建

发布于 今天 01:04

file

前面你熟悉了SpringBoot的扩展点SpringApplicationRunListeners的设计,配置文件ConfigurableEnvironment的抽象封装。其实这些都还不是它最核心的,最最核心的时Spring的容器的创建和准备,自动配置的装配,tomcat的容器的启动。

这一节我们就来开始研究Spring的容器相关的逻辑,看看它有什么抽象的设计和扩展点,又主要做了哪一些事情呢?

public ConfigurableApplicationContext run(String... args) {
   //扩展点 SpringApplicationRunListeners listeners.starting();
   //配置文件的处理和抽象封装 ConfigurableEnvironment
   //容器相关处理
   context = createApplicationContext();
   exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                          new Class[] {ConfigurableApplicationContext.class }, context);
    prepareContext(context, environment, listeners, applicationArguments,printedBanner);
    refreshContext(context);
   //其他逻辑
}

容器相关的逻辑主要由三个方法组成:createApplicationContext()、prepareContext()、refreshContext()。

这三个方法,每一个都做了很多事情,我们一个个来分析下。

Spring容器的创建时的核心组件

createApplicationContext容器创建的逻辑,其实非常简单,就是通过反射创建了一个容器的实现类,默认创建AnnotationConfigServletWebServerApplicationContext。

代码如下:

public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
            + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                }
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

你可能第一次看这个方法,只是这样知道了反射创建了一个容器的实现类可能就结束了。

你其实可以看下这个类反射创建也是会走构造函数的,所以可以简单分析下创建时,初始化了那些组件。

你跟着成长记,分析了很多对象的创建,你会发现,分析这个类的脉络,或者组件,画一个图,基本都已经形成一个固定的分析套路了。

    //自身构造函数
    public AnnotationConfigServletWebServerApplicationContext() {
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }
    //父类构造函数
    public GenericApplicationContext() {
        this.beanFactory = new DefaultListableBeanFactory();
    }

主要创建的是DefaultListableBeanFactory,Reader和一个Scanner。

file

之前普及过一个Spring基本术语,大家还记得么?容器通常称为ApplicationContext或者BeanFactory,context也简称为容器。ApplicationContext包装了BeanFactory,封装更高级的API而已,有各种实现类。

所以DefaultListableBeanFactory这个就是真正的容器对象。

术语普及BeanDefinition、BeanDefinitionRegistry、InternalBean

这里涉及到了新的术语普及BeanDefinition、BeanDefinitionRegistry

BeanDefinition表示了其实就是我们交给对Spring容器管理对象,也就是Bean的描述和定义,比如这个Bean是否是单例、是否有依赖其他Bean、依赖了那些Bean、Bean的名称、别名等等。

BeanDefinitionRegistry封装了对BeanDefinition常见操作的接口,容器默认实现了这个接口,所以一般它也代表了容器。容器BeanFactory实现了它,可以通过实现的方法,维护List<BeanDefinition>。

InternalBean 其实是指Spring自己注入的相关Bean,用来实现SpringBoot的核心功能的。

Context的构造函数除了创建DefaultListableBeanFactory容器,而Reader和一个Scanner他们各自有自己的创建流程。我简单画个图给大家概括下:

file

上图概括了Reader和Scanner的组件,其中最最核心的是内部Bean的加载,其他的组件都是为了容器服务的而已。

总而言之,可以看到容器创建时候,Reader和一个Scanner是负责扫描和解析注解的,可以扫描所有相关Bean定义的注解,并且自己还会补充内部提前定义的一些Bean。并且这些internalBean都是默认写死的一些BeanDefination,隐藏在构造函数汇中加载的一步,这一步非常关键,SpringBoot核心功能的实现点都是靠写bean来实现的,后面我们会看到的。

从Reader和Scanner开始思考下Spring容器的抽象设计

知道这个AnnotationConfigServletWebServerApplicationContext容器的核心组件后,我们可以思考下,这些组件是用来做什么的?之间的关系是啥,主要抽象出来是为了做什么呢?

你可以想象下,Spring容器的目的是为了IOC,也就是帮我们维护大量Bean对象的创建和管理。那么核心问题就是,这些Bean是怎么找到,并且创建到容器中的呢?

这就关系到了很关键的抽象设计。它就是从Resource->ClassLoader->Reader/Scanner->BeanDefination的设计了。

Resource

我来具体解释下,Resource顾名思义,表示的是各种资源。包括了各种Bean描述文件,如java类中@Bean @Configuration的定义,@Service和@Controller的定义,又比如xml中<bean/>标签的定义,groovy中bean的定义等等。这些够可以统称为资源。

ResourceLoader(ClassLoader)

这些资源都是在classpath下面的,只要通过ClassLoader我们可以扫描到和加载到的。

Reader

但是由于这些资源的定义bean的格式各种各样,此时就需要一个抽象,来统一解析这些资源,所以就有了Reader和Scanner了,不同的Reader解析不同的文件,如xmlReader,groovyReader等,注解定义的Bean通过Scanner来找到,并且解析识别。

BeanDefination

最终识别为Bean的一个抽象定义BeanDefination。容器会维护相应的集合,记录所有的Bean的定义。

这就是Spring容器非常好的一个抽象设计,很值得我们借鉴学习。

整体可以可以概括如下图所示:

file

创建了容器,容器内部,有一些组件可以帮助容器识别到Bean,并且统一解析为BeanDefination。希望你可以领悟到这个,这个对你理解Spring容器非常关键的。

至于具体每一个Reader或Scanner如何通过ClassLoader扫描和查找资源的,创建容器这里还没有涉及,之后如果可以的话,大家再仔细分析这些组件就可以了。

最后提一点,创建容器后,有一段exceptionReporters的获取,这个从名字就可以猜出来,是处理异常统计和汇报的组件,不是很关键,我们抓大放小,过就可以了。

public ConfigurableApplicationContext run(String... args) {
   //扩展点 SpringApplicationRunListeners listeners.starting();
   //配置文件的处理和抽象封装 ConfigurableEnvironment
   //容器相关处理,核心分析了它的核心组件脉络和抽象设计
   context = createApplicationContext();
   //处理异常统计和汇报的组件,不重要,抓大放小,过就可以了。
   exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                          new Class[] {ConfigurableApplicationContext.class }, context);
    prepareContext(context, environment, listeners, applicationArguments,printedBanner);
    refreshContext(context);
   //其他逻辑
}

今天我们主要分析了容器的创建。核心分析了它的核心组件脉络和抽象设计

1)核心组件Reader和Scanner,还有最关键的DefaultListableBeanFactory。并且熟悉了BeanDefinition、BeanDefinitionRegistry的概念。

2)容器关键的抽象设计,从Resource->ClassLoader->Reader/Scanner->BeanDefination。Spring通过这样的设计来获取到Bean对象的定义,最终帮我们管理对象。

下一节我们来一起看下有了容器对象,会为容器准备和设置哪些其他的内容,在这个过程中,容器又有会执行哪一些扩展点。

本文由博客一文多发平台 OpenWrite 发布!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK