微服务架构Day08-SpringBoot原理总结

几个重要的事件回调机制

ApplicationContextInitializer

  • ApplicationContextInitializer来源于Spring框架
    • 主要作用就是在ConfigurableApplicationContext类型或者子类型的ApplicationContextrefresh之前
    • 允许对ConfigurableApplicationContext的实例做进一步的设置和处理
  • ApplicationContextInitializer接口:
    • 是在Spring容器刷新之前执行的一个回调函数
    • 是在ConfigurableApplicationContextrefresh() 方法之前,即在Spring框架内部执行ConfigurableApplicationContextrefresh() 方法或者SpringBootrun() 方法之前调用
    • 作用是初始化SpringConfigurableApplicationContext的回调接口
  • 通常用于需要对应用上下文进行初始化的web应用程序中: 比如根据上下文环境注册属性或者激活概要文件
使用分析
  • ApplicationContextInitializer接口的典型应用场景:
    • web应用程序的应用上下文进行初始化
    • 比如:
      • 注册属性源property sources
      • 针对上下文的环境信息environment激活相应的profile
  • 在一个SpringBoot的应用程序中:
    • classpath上有很多jar包,有些jar包需要在ConfigurableApplicationContextrefresh() 方法调用之前对应用上下文做一些初始化动作
    • 因此会提供自己的ApplicationContextInitializer实现类,然后配置在自己的META-INF/spring.factories属性文件中
    • 这样相应的ApplicationContextInitializer实现类就会被SpringApplicationinitialize() 方法发现
  • SpringApplicationinitialize() 方法,在SpringApplication的构造函数内执行,从而确保在SpringApplicationrun() 方法之前完成
  • 然后在应用上下文创建之后,应用上下文刷新之前的准备阶段被调用
SpringBoot内置的ApplicationContextInitializer
  • 使用SpringBoot web应用默认使用的ApplicationContextInitializer的实现:
    • DelegatingApplicationContextInitializer:
      • 使用环境属性context.initializer.classes指定的初始化容器initializer进行初始化工作,如果没有指定则不进行任何操作
      • 使得可以在application.properties中可以自定义实现类配置
    • ContextIdApplicationContextInitializer:
      • 参照环境属性,设置Spring应用上下文的ID
      • ID值的设置会参照环境属性:
        • spring.application.name
        • vcap.application.name
        • spring.config.name
        • spring.application.index
        • vcap.application.instance_index
        • 如果这些属性都没有 ,ID使用application
    • ConfigurationWarningApplicationContextInitializer:
      • 对于一般配置错误在日志中做出警告
    • ServerPortInfoApplicationContextInitializer:
      • 将内置servlet容器实际使用的监听端口写入到environment环境属性中
      • 这样属性local.server.port就可以直接通过 @Value注入到测试中或者通过环境属性environment获取
    • SharedMetadataReaderFactoryContextInitializer:
      • 创建一个SpringBootConfigurationClassPostProcessor共用的CachingMetadataReaderFactory对象
      • 实现类为ConcurrentReferrenceCachingMetadataReaderFactory
    • ConditionEvaluationReportLoggingListener:
      • ConditionEvaluationReport写入日志
  • ApplicationContextInitializerSpring中允许在上下文刷新之前做自定义操作,如果需要对Spring的上下文进行深度整合,可以借助ApplicationContextInitializer进行很好的实现
  • spring-test包里有一个注解org.springframework.test.context.ContextConfiguration中有一个属性可以指定ApplicationContextInitializer辅助集成测试时自定义对上下文进行预处理
扩展实现方式
编程方式
  • 先定义ApplicationContextInitializer:
// @Order(66) - @Order的值越小就越早执行. 标注在类上, 不是方法上
@Order(66)
public class customerApplicationContextInitializer implements ApplicationContextInitializer {
	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		// 输出容器中有多少个bean
		System.out.println("Bean的数量为: " + applicationContext.getBeanDefinitionCount());
		/*
		 * 输出容器中所有bean的beanName
		 */
		 System.out.println(applicationContext.getBeanDefinitionCount + "个Bean的名称:");
		 String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
		 for (String beanName : beanDefinitionNames) {
		 	System.out.println(beanName);
		 }
	}
}
  • 在启动类中手动增加initializer:
@SpringBootApplication
@EnableConfigServer
@EnableDiscoveryClient
public class ConfigServer {
	public static void mian(String[] args) {
		SpringApplication springApplication = new SpringApplication(ConfigServer.class);
		// 添加自定义的ApplicationContextInitializer实现类的实例用来注册ApplicationContextInitializer
		springApplication.addInitializers(new customerApplicationContextInitializer());
		ConfigurableApplicationContext applicationContext = springApplication.run(args);
		applicationContext.close();
	}
}
添加配置方式
  • 添加配置的方式是通过DelegatingApplicationContextInitializer初始化类中的initialize() 方法获取到application.propertiescontext.initializer.class对应的类并执行对应的initialize() 方法
  • 只需要将实现了ApplicationContextInitializer的类添加到application.properties即可
    • 先定义一个实现了ApplocationContextInitializer的类
    • 然后在application.properties中定义:
    context.initializer.class= com.oxford.customerApplicationContextInitializer
    
spring.factories方式
  • SpringApplicationRunListener
    • ApplicationContextInitializer,SpringApplicationRunListener需要配置在META-INF/spring.factories中
  • ApplicationRunner
  • CommandLineRunner
    • ApplicationRunner,CommandLineRunner需要放在IOC容器中

启动流程

  • 创建SpringApplication对象
    • 调用initialize(sources)方法创造对象
      • 保存主配置类
      • 判断当前是否为一个web应用
      • 从类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer,然后保存起来
      • 从从类路径下找到META-INF/spring.factories配置的所有ApplicatListener
      • 从多个配置类中找到有main方法的主配置类
  • 运行run方法
public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();		
        stopWatch.start();		// 停止监听
        ConfigurableApplicationContext context = null;		// 声明一个IOC容器
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);		
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }
  • 获取SpringApplicationRunListeners,从类路径下META-INF/spring.factories
  • 回调所有的获取SpringApplicationRunListener.starting()方法
  • 封装命令行参数
  • 准备环境prepareEnvironment,创建环境完成后回调SpringApplicationRunListeners.environmentPrepared():表示环境准备完成
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        if (!this.isCustomEnvironment) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
        }

        ConfigurationPropertySources.attach((Environment)environment);
        return (ConfigurableEnvironment)environment;
    }
  • 创建ApplicationContext:决定创建web的IOC还是普通的IOC
protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                }
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }

        return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    }
  • 准备上下文环境prepareContext:将environment保存到IOC中,并且调用applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法.然后回调SpringApplicationRunListener的contextPrepared方法
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        this.postProcessApplicationContext(context);
        this.applyInitializers(context);
        listeners.contextPrepared(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);
        }

        Set<Object> sources = this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        this.load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }
  • prepareContext运行完成以后回调所有的SpringApplicationRunListeners的contextLoaded()方法
  • 刷新容器refreshContext,IOC容器初始化.在web应用中还会创建嵌入式的tomcat.在refreshContext,是扫描,创建.加载所有组件的地方(配置类,组件,自动配置)
private void refreshContext(ConfigurableApplicationContext context) {
        this.refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            } catch (AccessControlException var3) {
            }
        }

    }
  • 调用callRunner()从IOC容器中获取所有的ApplicationRunner和CommandLineRunner.先回调ApplicationRunner,后回调CommandLineRunner
  • 最后回调SpringApplicationRunListeners的listeners.running(context)
  • 整个SpringBoot应用启动完成以后返回启动的IOC容器

事件监听机制

  • ApplicationContextInitializer
public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext > {
    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        System.out.println("ApplicationContextInitializer...initialize"+configurableApplicationContext);
    }
}

  • SpringApplicationRunListener
public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {
   @Override
   public void starting() {
       System.out.println("SpringApplicationRunListener...starting...");
   }

   @Override
   public void environmentPrepared(ConfigurableEnvironment environment) {
       Object o=environment.getSystemProperties().get("os.name");
       System.out.println("SpringApplicationRunListener...environmentPrepared...");
   }

   @Override
   public void contextPrepared(ConfigurableApplicationContext context) {
       System.out.println("SpringApplicationRunListener...contextPrepared...");
   }

   @Override
   public void contextLoaded(ConfigurableApplicationContext context) {
       System.out.println("SpringApplicationRunListener...contextLoaded...");
   }

   @Override
   public void started(ConfigurableApplicationContext context) {
       System.out.println("SpringApplicationRunListener...started...");
   }

   @Override
   public void running(ConfigurableApplicationContext context) {
       System.out.println("SpringApplicationRunListener...running...");
   }

   @Override
   public void failed(ConfigurableApplicationContext context, Throwable exception) {
       System.out.println("SpringApplicationRunListener...failed...");
   }
}
  • ApplicationContextInitializer,SpringApplicationRunListener需要配置在META-INF/spring.factories中
org.springframework.context.ApplicationContextInitializer=\
com.web.springboot.listener.HelloApplicationContextInitializer

org.springframework.context.SpringApplicationRunListener=\
com.web.springboot.listener.HelloSpringApplicationRunListener
  • ApplicationRunner
@Component			// 容器中的类
public class HelloApplicationRunner implements ApplicationRunner {
   @Override
   public void run(ApplicationArguments args) throws Exception {
       System.out.println("ApplicationRunner...run...");
   }
}

CommandLineRunner

@Component			// 容器中的类
public class HelloCommandLineRunner implements CommandLineRunner {
   @Override
   public void run(String... args) throws Exception {
       System.out.println("CommandLineRunner...run"+ Arrays.asList(args));
   }
}
  • ApplicationRunner,CommandLineRunner需要放在IOC容器中-@Component

SpringBoot自定义starter

  • starter:
    • 这个场景需要的依赖是什么?
    • 如何编写自动配置?
    @Configuration			// 指定这个类是一个自动配置类
    @ConditionalOnXxx()		// 在指定条件成立的情况下自动配置类生效
    @AutoConfigureOrder() 	// 指定自动配置类的顺序
    @AutoConfigureAfter()	// 指定自动配置在特定的类之后
    		@Bean			// 给容器中添加组价
    ( @ConfigurationProperties 结合相关 XxxProperties类来绑定相关的配置 )
    @EnableConfigurationProperties	// 让XxxProperties类生效加入到容器中
    
    • 配置自动装配Bean:将标注@Configuration的自动配置类,放在classpath下的META-INF/spring.factories文件中才能加载
    org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
    org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
    
    • 模式:
      • 启动器: 启动器是一个空jar文件,仅提供辅助性依赖管理,依赖导入,这些依赖用于自动装配或者其它类库.
      官方命名空间:
      		- 前缀: spring-boot-starter-
      		- 模式: spring-boot-starter-模块名
      自定义命名空间:
      	    - 前缀: -spring-boot-starter-
      		- 模式: 模块名-spring-boot-starter
      
      • 专门写一个自动配置模块
      • 启动器依赖自动配置模块,使用时只需要引入启动器( starter )

    总结

    • 官方文档
    • 源码
posted @ 2021-02-21 15:52  攻城狮Chova  阅读(60)  评论(0编辑  收藏  举报