SpringBoot构造流程源码分析:Web应用类型推断

Web应用类型推断

完成变量赋值之后,在 SpringApplication 的构造方法中便调用了 WebApplication Type 的deduceFromClasspath 方法来进行 Web 应用类型的推断。SpringApplication 构造方法中的相关代码如下。

public SpringApplication(ResourceLoader resourceLoader, Class... primary
Sources) {
this . webApplicationType = WebApplicat ionType . deduceFromClasspath();
}

该行代码调用了 WebApplicationType 的 deduceFromClasspath 方法,并将获得的 Web 应用类型赋值给私有成员变量 webApplicationType.

WebApplicationType 为枚举类, 它定义了可能的 Web 应用类型,该枚举类提供了三类定义:枚举类型、推断类型的方法和用于推断的常量。枚举类型包括非 Web 应用、基于 SERVLET 的 Web 应用和基于 REACTIVE 的 Web 应用,代码如下。

public enum WebApplicationType {
//非 web 应用类型
NONE,
//基于 SERVLET 的 Web 应用类 型
SERVLET ,
//基 FREACTIVE 的 eb 应用类型
REACTIVE;
}

WebApplicationType 内针对 Web 应用类型提供了两个推断方法:

deduceFromClasspath 方法和
deduceFromApplicationContext 方法。在此我们使用了deduceFromClasspath 方法,下面重 点分析该方法的实现。

public enum WebApplicationType {
..
private static final String[] SERVLET_ INDICATOR_ CLASSES = { "javax. servlet . Servlet",
"org. springf
ramework . web . context . ConfigurableWebApplicationContext" }
private static final String WEBMVC_ INDICATOR_ CLASS = "org. springframewor
+ "web. servlet . DispatcherServlet";
private static final String WEBFLUX_ INDICATOR_ _CLASS = "org.'
+ "springframework. web. reactive . DispatcherHandler";
private static final String JERSEY INDICATOR_ CLASS = "org. glassfish.jerse
yservlet. ServletContainer";
//基于 classpath 的 web 应用类型推断,核心实现方法为 ClassUtils. isPresent
static WebApplicationType deduceF romClasspath() {
if (ClassUtils. isPresent (WEBFLUX_ INDICATOR_ CLASS, null)
&& !ClassUtils . isPresent (WEBMVC_ INDICATOR_ CLASS, null)
x& !ClassUtils. isPresent(JERSEY INDICATOR_ CLASS, null)) {
return WebApplicationType . REACTIVE ;
for (String className : SERVLET_ INDICATOR_ CLASSES) {
if (!ClassUtils. isPresent(className, nul1)) {
return WebApplicationType . NONE;
return WebApplicationType . SERVLET;
}

方法 deduceFromClasspath 是基于 classpath 中类是否存在来进行类型推断的,就是判断指定的类是否存在于 classpath 下, 并根据判断的结果来进行组合推断该应用属于什么类型。deduceFromClasspath 在判断的过程中用到了 ClassUtils 的 isPresent 方法。isPresent方法的核心机制就是通过反射创建指定的类,根据在创建过程中是否抛出异常来判断该类是通过上面的源代码,我们可以看到 deduceFromClasspath 的推断逻辑如下。

.当 DispatcherHandler 存在,并且 DispatcherServlet 和 ServletContainer 都不存在,则返回类型为
WebApplicationType.REACTIVE.

.当 SERVLET 或
ConfigurableWebApplicationContext 任何一个不存在时,说明当前应用为非 Web 应用,返回 WebApplicationType NONE。

当应用不为 REACTIVE Web 应用,并且 SERVLET 和
ConfigurableWebApplicationContext都存在的情况下,则为 SERVLET 的 Web 应用,返回 WebApplicationType .SERVLET.

SpringBoot构造流程源码分析:Web应用类型推断_第1张图片

ApplicationContextlnitializer加载

源码解析


ApplicationContextlnitializer是SpringIOC 容器提供的一个接口,它是一个回调接口,主要目的是允许用户在 ConfigurableApplicationContext 类型(或其子类型)的 ApplicationContext做 refresh 方法调用刷新之前,对 ConfigurableApplicationContext 实例做进一步的设 置或处理。通常用于应用程序上下文进行编程初始化的 Web 应用程序中。

ApplicationContextlnitializer 接口只定义了一-个 initialize 方法,代码如下。
public interface ApplicationContextInitializer {
void initialize(C applicationContext);
}


ApplicationContextlnitializer 接口的 initialize 方法主要是为了初始化指定的应用上下文。而对应的上下文由参数传入,参数为 ConfigurableApplicationContext 的子类。

在完成了 Web 应用类型推断之后,
ApplicationContextlnitializer 便开始进行加载工作,该过程可分两步骤:获得相关实例和设置实例。对应的方法分别为 getSpringFactoriesInstances和 setlnitializers。

SpringApplication 中获得实例相关方法代码如下。

private  Collection getSpringF actoriesInstances(Class type) {
return getSpringFactoriesInstances(type, new Class[] {});
private  Collection getSpringF actoriesInstances(Class type,
Class[] parameterTypes, object... args) {
ClassLoader classLoader = getClassLoader();
//加载对应配置,这里采用 LinkedHashSet 和名称来确保加载的唯一 性
Set names = new LinkedHashSet<> (
SpringF actoriesLoader . loadF actoryNames (type, classLoader));
//创建实例
List instances = createSpringFactoriesInstances (type,parameterType
S,
classLoader, args, names);
//排序操作
AnnotationAwareOrderComparator . sort(instances);
return instances;}


getSpringFactorieslnstances 方 法 依 然 是 通 过 SpringFactoriesL oader 类 的loadFactoryNames 方法来获得 ME TA-INF/spring.factories 文件中注册的对应配置。在Spring Boot 2.2.1 版本中,该文件内具体的配置代码如下。

#应用程序上下文的初始化器配置

org. springframework. context . Applicat ionContextInitializer=\
org. springframework . boot . context . Configurat ionWarningsApplicationContextIni
tializer,\org. springframework. boot . context . ContextIdApplicat ionContextInitializer,\
org. springframework . boot. context . config . Delegat ingApplicationContextInitial
izer, \
org. springframework . boot . rsocket . context . RSocketPortInfoApplicat ionContextI
nitializer,\
org. springframework. boot . web . context . ServerPortInfoApplicationContextInitia
lizer

配置代码中等号后面的类为接口
ApplicationContextlnitializer 的具体实现类。当获取到这些配置类的全限定名之后,便可调用 createSpringFactoriesInstances 方法进行相应的实例化操作。

private  List createSpringFactoriesInstances(Class type,
Class[] parameterType
s, ClassLoader classLoader, Object[] args,
Set names) {
List instances = new ArrayList<>(names. size());
//遍历加裁到的类名(全限定名)
for (String name : names) {
try {
//获取 Cass
Class instanceClass = ClassUtils. forName( name, classLoader);
Assert. isAssignable(type, instanceClass);
//获取有参构造器
Constructor constructor = instanceClass. getDeclaredConstructor (par
ameterTypes);
//创建对象
「instance = (T) BeanUtils . instantiateClass( constructor, args);
instances . add(instance);
} catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type +”:
”+ name, ex);
}
return instances;
}

完成获取配置类集合和实例化操作之后,调用 setlnitializers 方法将实例化的集合添加到SprinaApplication的成员变量initializers中,类型为

List<
ApplicationContextlnitiali-zer>,代码如下。

private List> initializers;
public void setInitializers(
Collection> initializers) {
this. initializers = new ArrayList<>( initializers);
}

setlnitializers 方法将接收到的 initializers 作为参数创建了一一个新的 List,并将其赋值给SpringApplication 的 nitializers 成员变量。由于是创建了新的 List,并且直接赋值,因此该方法一-旦被调用, 便会导致数据覆盖,使用时需注意。

实例讲解

阅读完源代码,我们进行一些拓展,来自定义一个
ApplicationContextinitializer 接口的实现,并通过配置使其生效。

这里以实现
ApplicationContextlnitializer 接口,并在 initialize 方法中打印容器中初始化了多少个 Bean 对象为例来进行演示,代码如下。

@0rder(123) // @Order 的 vaLue 值越小越早执行

public class LearnApplicationContextInitializer implements ApplicationConte
xtInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext)
//打印容器里面初始化了多少个 Bean
System. out. println("容器中初始化 Bean 数量:”+ applicationContext . getBean
DefinitionCount());
}
}

上面就完成了一个最基础的
ApplicationContextInitializer 接口的实现类。

当我们定义好具体的类和功能之后,可通过 3 种方法调用该类。

第 一 种 方 法 就 是 参 考 Spring Boot 源 代 码 中 的 操 作 , 将 该 实 现 类 配 置 于META-INF/spring.factories 文件中,这种方法与上面讲到的源代码配置方法一致。

第二种方法是通过 application.properties 或 application.yml 文件进行配置,格式如下。

context . initializer . classes=com. secbro2 . learn. initializer. LearnApplicationCont-extInitial1ize

这种方法是通过
DelegatingApplicationContextnitializer 类中的 initialize 方法获取到配置文件中对应的 context.initializer.classes 的值,并执行对应的 initialize 方法。

第三种方法是通过 SpringApplication 提供的 addInitializers 方法进行追加配置,代码如下。

public static void main(String[] args) {SpringApplication app = new SpringApplication(SpringLearnApplication. clas
s, Person.class);
//添加自定义 ContextInitializer,注意会覆盖掉默认配置的
app. addInitializers(new LearnApplicationContextInitializer());
app . run(args);}

无论通过以上 3 种方法的哪一种,配置完成后,执行启动程序都可以看到控制台打印容器中Bean 数量的日志。

本文给大家讲解的内容是Web应用类型推断ApplicationContextlnitializer加载

  1. 下篇文章给大家讲解的是ApplicationListener加载和入口类推断、SpringApplication 的定制化配置;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!

你可能感兴趣的:(程序员,Java,编程,spring,boot,前端,java)