ef 在此上下文中只支持基本类型或枚举类型_SpringBoot构造流程源码分析:Web应用类型推断

Web应用类型推断

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

public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) {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. springframework . 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.jerseyservlet. ServletContainer";//基于 classpath 的 web 应用类型推断,核心实现方法为 ClassUtils. isPresentstatic 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.

699bd291a78b8b5539fd5422cf6616d6.png

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,parameterTypeS,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 ionWarningsApplicationContextInitializer,org. springframework. boot . context . ContextIdApplicat ionContextInitializer,org. springframework . boot. context . config . Delegat ingApplicationContextInitializer, org. springframework . boot . rsocket . context . RSocketPortInfoApplicat ionContextInitializer,org. springframework. boot . web . context . ServerPortInfoApplicationContextInitializer

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

private  List createSpringFactoriesInstances(Class type,Class>[] parameterTypes, ClassLoader classLoader, Object[] args,Set names) {List instances = new ArrayList<>(names. size());//遍历加裁到的类名(全限定名)for (String name : names) {try {//获取 CassClass> instanceClass = ClassUtils. forName( name, classLoader);Assert. isAssignable(type, instanceClass);//获取有参构造器Constructor> constructor = instanceClass. getDeclaredConstructor (parameterTypes);//创建对象「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>,代码如下。

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

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

实例讲解

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

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

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

public class LearnApplicationContextInitializer implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext)//打印容器里面初始化了多少个 BeanSystem. out. println("容器中初始化 Bean 数量:”+ applicationContext . getBeanDefinitionCount());}}

上面就完成了一个最基础的 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. class, Person.class);//添加自定义 ContextInitializer,注意会覆盖掉默认配置的app. addInitializers(new LearnApplicationContextInitializer());app . run(args);}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值