Java教程

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

本文主要是介绍SpringBoot成长记5:Spring容器的创建,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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。

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中标签的定义,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对象的定义,最终帮我们管理对象。

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

这篇关于SpringBoot成长记5:Spring容器的创建的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!