欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

springboot系列4,SpringApplication准备阶段

程序员文章站 2022-07-13 13:36:25
...

关于SpringBoot,大致可分为基础技术和衍生技术。

基础技术主要是关于SpringFramework的,那SpringFramework中,我们可大致分为几块:

Spring 模式注解:例如@Service、@Component等,详情见之前文章。

Spring 应用上下文:无需多解释,核心组件,用来装配bean、相应的生命周期。

Spring 工厂加载机制:之前文章自动装配。

Spring 应用上下文初始化器:在spring上下文没有初始化之前做一些调整和变化等。

Spring Environment抽象接口:一个环境,统一所有环境,包括配置属性、profile等。

Spring 应用事件/监听器:扩展了java应用监听的方式。

衍生技术或者是SpringFramework的衍生技术,主要是SpringBoot的特性:

SpringApplication

SpringApplication Builder API:之前文章有用过,可以进行链式编写,比较方便。

SpringApplication运行监听器

SpringApplication参数

SpringApplication故障分析

SpringBoot应用事件/监听器

 

SpringApplication官方定义:SpringApplication类通过其中的main方法来引导Spring应用启动。很多情况下都可以使用SpringApplication.run的静态方法的方式启动。

所以SpringApplication是Spring应用的引导类,可以提供便利的自定义行为方法。

使用场景:嵌入式Web应用和非Web应用

拓展:web应用分为嵌入式场景和非嵌入式场景,springboot可以部署在Tomcat7、jetty7、servlet容器中,部署在servlet容器中时,SpringApplication就不能用了。

自定义SpringApplication

通过SpringApplication的API调整

SpringApplication springApplication = new SpringApplication(Application.class);
springApplication.setBannerMode(Banner.Mode.CONSOLE);
springApplication.setWebApplicationType(WebApplicationType.NONE);
springApplication.setAdditionalProfiles("prod");
springApplication.setHeadless(true);

通过SpringApplicationBuilder的API调整

new SpringApplicationBuilder(Application.class)
    .bannerMode(Banner.Mode.CONSOLE)
    .web(WebApplicationType.NONE)
    .profiles("prod")
    .headless(true)
    .run(args);

SpringApplication准备阶段

配置:Spring Bean来源

在Spring启动时,很多功能组件是以bean的方式承载的,而bean的配置需要来源,java配置class或xml上下文配置文件集合,用于springboot的BeanDefinitionLoader读取,并将配置源解析加载为Spring Bean定义。

java配置方式:SpringApplication就是一个配置源,@SpringApplication中包含@Component,所以这个类就是个源,在上面代码new SpringApplication时,把自身这个类配置进去。

对于SpringApplicationBuilder,看源码可以看出,来源可以不止一个。

springboot系列4,SpringApplication准备阶段

通常情况下,我们会用main方法启动,如下

/**
 * {@link SpringApplication} 引导类
 */
@SpringBootApplication
public class SpringApplicationBootstrap {
    public static void main(String[] args) {
        SpringApplication.run(SpringApplicationBootstrap.class, args);
    }
}

但其实,不是非得是main方法,例如

/**
 * {@link SpringApplication} 引导类
 */

public class SpringApplicationBootstrap {
    public static void main(String[] args) {
        SpringApplication.run(springMethod.class, args);
    }
    @SpringBootApplication
    public static class springMethod{

    }
}

一样可以运行成功

在配置源的时候,需要使用setSources的方式,源码:

springboot系列4,SpringApplication准备阶段

源码注释中,source可以是类名、包名或xml配置文件路径。

所以也可以变为:

/**
 * {@link SpringApplication} 引导类
 */
public class SpringApplicationBootstrap {
    public static void main(String[] args) {
        Set<String> sources = new HashSet();
        sources.add(springMethod.class.getName());

        SpringApplication springApplication = new SpringApplication();
        springApplication.setSources(sources);
        ConfigurableApplicationContext context = springApplication.run(args);
        System.out.println(context.getBean(springMethod.class));
    }

    @SpringBootApplication
    public static class springMethod{

    }
}

 运行结果:

springboot系列4,SpringApplication准备阶段

推断:分为Web应用类型和主引导类(Main Class)

推断web类型:

因为不同类型在运行时不同,例如普通类型运行后直接结束,而web类型会持续运行,有端口号等,所以需要判断类型。在有tomcat依赖的时候,类型会自动切换成web类型。根据当前应用 ClassPath 中是否存在相关实现类来推断 Web 应用的类型。包括:

Web Reactive: WebApplicationType.REACTIVE

Web Servlet: WebApplicationType.SERVLET

非 Web: WebApplicationType.NONE

SpringApplication类中的deduceWebApplicationType即推断过程,源码如下(每个版本都有调整,此处用2.2.2版本):springboot系列4,SpringApplication准备阶段

springboot系列4,SpringApplication准备阶段

static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {
        if (isAssignable("org.springframework.web.context.WebApplicationContext", applicationContextClass)) {
            return SERVLET;
        } else {
            return isAssignable("org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext"
                , applicationContextClass) ? REACTIVE : NONE;
        }
    }

spring会扫描目录,如果包含相关关键字,会根据不同的type,选择不同的应用类型。由此可看出,有mvc就不会用reacitive。

想把web类型变为普通类型,设置

springApplication.setWebApplicationType(WebApplicationType.NONE);

推断引导类:

在上面代码中有一个spring来源是单独写的类,main方法中SpringApplication加载的并不是main方法所在的类,那么这时我们并不知道main所在的类是哪个,这个时候需要找,上源码:

springboot系列4,SpringApplication准备阶段

springboot系列4,SpringApplication准备阶段

通过源码可知,系统会找到栈中的错误信息,通过遍历关键词“main”,找到main方法所在的类名并返回。

通过debug查看栈情况:

springboot系列4,SpringApplication准备阶段

 所以由此可知,不管是否传的是main方法,spring都会调用栈信息搜索main所在的类。

加载:应用上下文初始化器和应用事件监听器

加载应用上下文初始化器:

利用spring工厂加载机制,实例化ApplicationContextInitializer实现类,并排序对象集合。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<>(
    SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

实现类: org.springframework.core.io.support.SpringFactoriesLoader

SpringFactoriesLoader通过加载META-INF/spring.factories文件,加载初始化器,当然多个包可能会有多个文件,相对应的也会有多个初始化器,那么需要对其进行排序,即使用AnnotationAwareOrderComparator.sort(instances);进行,看源码可看出,有两个关键词,order注解、ordered接口,都是用来定义顺序的

springboot系列4,SpringApplication准备阶段

springboot系列4,SpringApplication准备阶段

当然,如果排序是非必须的,如果不排序,spring会按照默认规则进行加载。

 自定义应用上下文初始化器

@Order(Ordered.HIGHEST_PRECEDENCE)
public class HelloWorldApplicationContextInitializer<C extends ConfigurableApplicationContext> implements ApplicationContextInitializer<C> {
    @Override
    public void initialize(C configurableApplicationContext) {
        System.out.println("ConfigurableApplicationContext.id = " + configurableApplicationContext.getId());
    }
}
public class AfterHelloWorldApplicationContextInitializer<C extends ConfigurableApplicationContext> implements ApplicationContextInitializer<C> , Ordered {
    @Override
    public void initialize(C configurableApplicationContext) {
        System.out.println("after ConfigurableApplicationContext.id = " + configurableApplicationContext.getId());
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

META-INF/spring.factories中定义:

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
com.haozirou.springboot.context.AfterHelloWorldApplicationContextInitializer,\
com.haozirou.springboot.context.HelloWorldApplicationContextInitializer

 运行结果:

springboot系列4,SpringApplication准备阶段

加载应用事件监听器

ApplicationListener,原理同初始化器一样。

以autoconfigure为例,他的factories中配置的监听器为BackgroundPreinitializer

springboot系列4,SpringApplication准备阶段

 打开发现

springboot系列4,SpringApplication准备阶段

其中实现ApplicationListener,监听SpringApplicationEvent事件,该事件是springboot的事件,而非spring事件,其继承的ApplicationEvent事件才是spring事件,再往上追溯可找到EventObject接口,他是所有事件的源,

自定义事件监听器

/**
 * HelloWorld {@link ApplicationListener} 监听 {@link ContextRefreshedEvent} 事件
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HelloWorldApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        System.out.println("HelloWorld : " + contextRefreshedEvent.getApplicationContext().getId());
        System.out.println("and timestamp:" + contextRefreshedEvent.getTimestamp());
    }
}
/**
 * after HelloWorld {@link ApplicationListener} 监听 {@link ContextRefreshedEvent} 事件
 * 监听同一个事件,因为不同事件启动的时机可能不太一样,不好做比较
 */
@Order(Ordered.LOWEST_PRECEDENCE)
public class AfterHelloWorldApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        System.out.println("after HelloWorld : " + contextRefreshedEvent.getApplicationContext().getId());
        System.out.println("after and timestamp:" + contextRefreshedEvent.getTimestamp());
    }
}
# Application Listeners
org.springframework.context.ApplicationListener=\
com.haozirou.springboot.listener.AfterHelloWorldApplicationListener,\
com.haozirou.springboot.listener.HelloWorldApplicationListener

执行结果:

springboot系列4,SpringApplication准备阶段

 

至此,SpringApplication准备阶段完成,会看SpringApplication源码,

springboot系列4,SpringApplication准备阶段
先加载配置源,接下来推断web应用类型,在初始化上下文初始化器,接下来初始化事件监听器,最后推断引导类。

相关标签: 学习 springboot