spring与springboot原理浅析

本文详细解读了SpringBoot应用启动流程,包括run方法的应用、各阶段事件监听机制(如ApplicationRunner和CommandLineRunner),以及初始化和销毁方式(@PostConstruct,InitializingBean,initMethod,@PreDestroy等)。同时介绍了DefaultSingletonBeanRegistry的三级缓存和aware接口在启动阶段的使用。
摘要由CSDN通过智能技术生成


该文章是学习黑马的满一航老师的spring高级视频做的笔记。。。讲的很有深度,推荐大家学习。 https://www.bilibili.com/video/BV1P44y1N7QG

springboot启动流程-run方法

run方法源码如下:

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
    ConfigurableApplicationContext context = null;
    this.configureHeadlessProperty();
    //1.获取事件发布器
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    //发布ApplicationStartingEvent事件,应用开始启动
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        //2.将args参数进行封装,可以区分选项参数和非选项参数(选项参数为键值对形式,以--开头)
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //配置并获取环境对象
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        //7.打印图案
        Banner printedBanner = this.printBanner(environment);
        //8.创建spring容器
        context = this.createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        //配置容器
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
       	//11.执行beanfactory后处理器,bean后处理器,初始化单例
        this.refreshContext(context);
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }
		//发布ApplicationStartedEvent事件
        listeners.started(context);
        //12.执行ApplicationRunner和CommandLineRunner
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, listeners);
        throw new IllegalStateException(var10);
    }

    try {
        //发布ApplicationReadyEvent事件
        listeners.running(context);
        return context;
    } catch (Throwable var9) {
        //如果出现异常,则会发布一个ApplicationFailedEvent事件
        this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}

prepareEnvironment方法源码如下:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    //根据当前的web环境获取对应的环境实现类 servlet、reactive
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    //3.环境增强 将命令行的数据来源添加到环境中
    this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
    //4.环境增强 使环境可以解析不同格式的参数 如使用下划线、减号、驼峰的参数 会统一成减号
    ConfigurationPropertySources.attach((Environment)environment);
    //5.环境增强 通过加载spring.factories文件中定义的EnvironmentPostProcessor,使环境可以解析properties文件和随机数 
    //发布ApplicationEnvironmentPreparedEvent事件
    listeners.environmentPrepared(bootstrapContext, (ConfigurableEnvironment)environment);
    DefaultPropertiesPropertySource.moveToEnd((ConfigurableEnvironment)environment);
    Assert.state(!((ConfigurableEnvironment)environment).containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
    //6.环境增强 可以将数据绑定到类字段上 这里将环境变量的值绑定到SpringApplication类的字段上
    this.bindToSpringApplication((ConfigurableEnvironment)environment);
    if (!this.isCustomEnvironment) {
        environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
    }

    ConfigurationPropertySources.attach((Environment)environment);
    return (ConfigurableEnvironment)environment;
}

prepareContext方法源码如下:

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    this.postProcessApplicationContext(context);
    //9.执行初始化器,对容器进行增强
    this.applyInitializers(context);
    //发布ApplicationContextInitializedEvent事件
    listeners.contextPrepared(context);
    bootstrapContext.close(context);
    if (this.logStartupInfo) {
        this.logStartupInfo(context.getParent() == null);
        this.logStartupProfileInfo(context);
    }

    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }

    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }

    if (this.lazyInitialization) {
        context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
	//10.得到所有的beandefinition源(组件扫描、xml等)
    Set<Object> sources = this.getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    //把beanDefinition加载到容器中
    this.load(context, sources.toArray(new Object[0]));
    //发布ApplicationPreparedEvent事件
    listeners.contextLoaded(context);
}
应用启动各阶段事件监听

以下事件都是继承了SpringApplicationEvent抽象类的与spring应用程序相关的事件,在spring程序启动中,实际上还会涉及到与web、servlet相关的事件

  1. ApplicationStartingEvent 程序开始启动的监听,在除了注册监听器和初始化器的任何处理之前执行

  2. ApplicationEnvironmentPreparedEvent 程序环境就绪的监听(读取配置文件、系统参数等),会在Environment已知,应用上下文创建之前执行

  3. ApplicationContextInitializedEvent 应用上下文就绪的监听,在bean定义被夹在之前执行

  4. ApplicationPreparedEvent bean定义加载完后的监听,在refresh方法之前执行

  5. ApplicationStartedEvent 程序启动完毕的监听,在任何应用程序和命令行运行程序被调用前执行

  6. ApplicationReadyEvent 在ApplicationRunner和CommandLineRunner被调用后执行

    接着会发布一个AvailabilityChangeEvent事件,将程序的状态设置为ReadinessState.ACCEPTING_TRAFFIC,表示程序可以开始处理请求

  7. ApplicationFailedEvent 程序启动失败的监听

SpringApplication springApplication = new SpringApplication(ClientStarter.class);
springApplication.setBannerMode(Banner.Mode.OFF);//关闭banner图案
springApplication.addListeners((ApplicationListener<ApplicationStartingEvent>) event -> System.out.println("程序开始启动"));
springApplication.addListeners((ApplicationListener<ApplicationEnvironmentPreparedEvent>) event -> System.out.println("环境准备就绪"));
springApplication.addListeners((ApplicationListener<ApplicationContextInitializedEvent>) event -> System.out.println("应用上下文初始化完毕"));
springApplication.addListeners((ApplicationListener<ApplicationPreparedEvent>) event -> System.out.println("程序已准备"));
springApplication.addListeners((ApplicationListener<ApplicationStartedEvent>) event -> System.out.println("程序启动完毕"));
springApplication.addListeners((ApplicationListener<ApplicationReadyEvent>) event -> System.out.println("程序已就绪,可开始处理请求..."));
springApplication.addListeners((ApplicationListener<ApplicationFailedEvent>) event -> {
    System.out.println("应用启动失败,原因如下:");
    System.out.println(event.getException().getMessage());
    System.out.println("================================================ ");
});
springApplication.run(args);
CommandLineRunner与ApplicationRunner

这两个接口用于在spring程序启动后执行一些特定的代码,如将数据缓存到redis中,当有多个这两个接口的实现类,可以通过@Order指定执行顺序

ApplicationRunner对参数进行了封装,他可以区分选项参数和非选项参数,选项参数例如:–server.port=8080 非选项参数例如:debug

这两个接口在springboot程序启动时的执行源码如下:

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList();
    //可以看到,默认情况下会先执行ApplicationRunner,再执行CommandLineRunner
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());//将所有ApplicationRunner添加到集合中
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());//将所有CommandLineRunner添加到集合中
    AnnotationAwareOrderComparator.sort(runners);//根据Order指定的顺序排序
    Iterator var4 = (new LinkedHashSet(runners)).iterator();
    while(var4.hasNext()) {//遍历集合 执行调用
        Object runner = var4.next();
        if (runner instanceof ApplicationRunner) {
            this.callRunner((ApplicationRunner)runner, args);
        }

        if (runner instanceof CommandLineRunner) {
            this.callRunner((CommandLineRunner)runner, args);
        }
    }
}
DefaultSingletonBeanRegistry 三级缓存

DefaultSingletonBeanRegistry中定义了三个HashMap,即三级缓存

  • singletonObject:一级缓存,存放完全实例化且属性赋值完的bean,可直接使用
  • earlySingletonObject:二级缓存,存放早期bean的引用,尚未装配属性的bean
  • singletoneFactroies:三级缓存,存放实例化完成的beanfactory对象

除了三级缓存之外,DefaultSingletonBeanRegistry中还定义了其他两个缓存,都是set集合

  • singletonesCurrentlyInCreation:用于存储当前正在创建过程中的bean,创建完成则会移出
  • alreadyCreated:存放被创建过的bean,用于标记bean是否被创建过
spring配置设置方式

常用如下:

  1. SpringApplication.setDefaultProperties添加配置

    SpringApplication springApplication = new SpringApplication(ClientStarter.class);
    springApplication.setDefaultProperties(MapUtil.toHashMap("jdk.version", "1.8"));
    
  2. properties、yml文件配置

  3. 操作系统环境变量

  4. 通过@PropertySource加载配置文件

  5. java系统参数

  6. 命令行参数

    java -jar 1.jar --server.port=8888
    

    参数以–开头,spring会读取参数并添加到Environment中

    禁止该功能:

    springApplication.setAddCommandLineProperties(false);
    
三种初始化方式
1.@PostConstruct

用于修饰在方法上,会在构造方法和依赖注入完成后执行

Constructor -> @Autowired -> @PostConstruct

在执行某些初始化操作时,如果这些操作依赖于注入的bean,那么就无法在构造方法中实现,因此,可以使用@PostConstruct注解来进行初始化操作

注意

  • 一个类中只有一个非静态方法能使用该注解
  • 被注解的方法不能有参数和返回值
  • 被注解的方法不能抛出已检查异常
  • 被注解的方法只会被执行一次
2.InitializingBean

在spring bean的生命周期中,实例化-》生成对象-》属性填充之后,如果实现了InitializingBean接口,则会执行afterPropertiesSet方法,可进行一些初始化操作

3.initMethod
@Bean(initMethod = "init")//指定初始化方法
public Test test() {
    return new Test();
}
public void init() {
    System.out.println("init");
}
各初始化方式执行顺序

构造器 -> 依赖注入 -> @PostConstruct -> InitializingBean -> initMethod

三种销毁方式
1.@PreDestroy

在一个bean生命周期即将结束并将被移出容器时会执行被该注解修饰的销毁方法,通常进行一些资源清理、日志记录的操作

2.DisposableBean

实现DisposableBean接口,在bean销毁时会执行destroy方法

3.@Bean指定destroyMethod

通过destroyMethod指定销毁时要执行的方法

各销毁方式执行顺序

@PreDestroy -> DisposableBean -> destroyMethod

aware接口

spring为开发者提供了aware接口以获取spring的特定资源

  • 通过实现ApplicationContextAware接口获取spring容器
  • 通过实现EnvironmentAware接口获取spring环境
  • 通过实现ApplicationEventPublisherAware接口获取事件发布器
  • 通过实现BeanFactoryAware接口获取bean工厂
  • 等等…

注意:实际上,ApplicationContext继承了大部分与spring资源相关的接口,所以他包含Environment、ApplicationEventPublisher、BeanFactory等的功能

在spring启动阶段添加初始化器
SpringApplication application = new SpringApplication(RunTest.class);
application.addInitializers(applicationContext -> {
    if (applicationContext instanceof GenericApplicationContext gac) {
        gac.registerBean("bean1", Bean1.class);
    }
});
application.run();

在spring启动时,可以执行一些操作如手动注册bean、添加监听器等

SpringBoot主类推断

SpringApplication中包含一个方法deduceMainApplicationClass,用于获取主类的类对象

反射调用该方法获取主类:

Method deduceMainApplicationClass = SpringApplication.class.getDeclaredMethod("deduceMainApplicationClass");
deduceMainApplicationClass.setAccessible(true);
Object o = deduceMainApplicationClass.invoke(new SpringApplication());
System.out.println(o);
  • 28
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值