# SpringBoot 常用扩展点介绍容器启动源码分析

Posted MarlonBrando1998

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了# SpringBoot 常用扩展点介绍容器启动源码分析相关的知识,希望对你有一定的参考价值。

SpringBoot 常用扩展点介绍、容器启动源码分析

SpringApplication.run()

实例化一个SpringApplication

  • 创建一个SpringApplication实例
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) 
    return new SpringApplication(primarySources).run(args);

  • SpringApplication构造方法:属性赋值
// 收集所有的容器初始化组件对象:解析 spring.factories 文件获取容器自定义组件
private List<ApplicationContextInitializer<?>> initializers;

// 收集所有的监听器:扩展方式和上面的初始化组件方式差不多
private List<ApplicationListener<?>> listeners;

总结

  • SpringApplication的构建都是为了run()方法启动做铺垫,构造方法中总共就有几行代码,最重要的部分就是设置应用类型、设置初始化器、设置监听器。

spring-boot自定义容器初始化组件 Demo

定义组件
  • 实现ApplicationContextInitializer
public class CustomInitializer implements ApplicationContextInitializer 

    private static final Logger logger = LoggerFactory.getLogger(CustomInitializer.class);

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) 
        logger.info("====================CustomInitializer start======================");
        logger.info("CustomInitializer initialize方法被执行...");
        logger.info("ApplicationName: ", applicationContext.getApplicationName());
        logger.info("isActive: ", applicationContext.isActive());
        logger.info("====================CustomInitializer end======================");
    

配置容器组件
  • SpringBootSPI扩展 META-INF下面 spring.factories配置自定义组件的位置
org.springframework.context.ApplicationContextInitializer=com.li.springbootproject.spring.initializer.CustomInitializer
  • add方式添加
@SpringBootApplication
public class MySpringBootApplication 
    public static void main(String[] args) 
        SpringApplication application = new SpringApplication(MySpringBootApplication.class);
        application.addInitializers(new MyApplicationContextInitializer());
        application.run(args);
    

总结

  • ApplicationContextInitializerSpring 对外提供的扩展点之一,用于在 ApplicationContext 容器加载 Bean 之前对当前的上下文进行配置。
  • 重要的还是如何将这一组件和我们的具体业务相结合,实现我们具体的业务,这应该是需要思考的。

调用对象的 Run()

Run() 方法流程

public ConfigurableApplicationContext run(String... args) 
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
    	// 获取运行监听器
		SpringApplicationRunListeners listeners = getRunListeners(args);
    	// 启动运行监听器
		listeners.starting();
		try 
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			// 环境构建这一步加载了系统环境配置、用户自定义配置并且广播了ApplicationEnvironmentPreparedEvent事件,触发监听器。
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
            // 打印 Banner
			Banner printedBanner = printBanner(environment);
            // 依据是否为 web 环境创建 web 容器或者普通的 IOC 容器(只是创建) 见下面截图
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[]  ConfigurableApplicationContext.class , context);
            // IOC 容器的前置处理,为刷新容器之前做准备,关键操作(将启动类注入容器)
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			// 刷新容器,完成组件的扫描,创建,加载等
            refreshContext(context);
    		//  IOC容器的后置处理
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) 
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			
			listeners.started(context);
			callRunners(context, applicationArguments);
		
		catch (Throwable ex) 
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		

		try 
			listeners.running(context);
		
		catch (Throwable ex) 
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		
		return context;
	
  • 创建 BeanFactory
  • 初始化完成之后就进到了run方法,run方法完成了所有Spring的整个启动过程:准备Environment——发布事件——创建上下文、bean——刷新上下文——结束
  • 对比 ApplicationContext,ConfigurableApplicationContext提供了配置上下文的接口,如设置Environment、监听器、切面类、关闭上下文的钩子等。

容器启动过程中的 refreshContext()

源码refresh()

@Override
public void refresh() throws BeansException, IllegalStateException 
    synchronized (this.startupShutdownMonitor) 
        prepareRefresh();
        // 告诉子类去刷新bean工厂,这步完成后配置文件就解析成一个个bean定义,注册到BeanFactory(但是未被初始化,仅将信息写到了beanDefination的map中)
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        prepareBeanFactory(beanFactory);
        try 
             // 处理自定义的BeanFactoryPostProcess:允许BeanFactoryPostProcessor在容器实例化任何bean之前读取bean的定义(配置元数据)
            postProcessBeanFactory(beanFactory);
            //  调用BeanFactoryPostProcessor各个实现类的方法
            invokeBeanFactoryPostProcessors(beanFactory);
            
            // 注册 BeanPostProcessor 的实现类
            // 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization
         	// 两个方法分别在 Bean 初始化之前和初始化之后得到执行。
            registerBeanPostProcessors(beanFactory);
            
            // 国际化
            initMessageSource();
            
            initApplicationEventMulticaster();
            onRefresh();
            registerListeners();
            
            // 实例化所有剩余的非懒加载单例 bean
            finishBeanFactoryInitialization(beanFactory);
            
            finishRefresh();
        catch(Exception e)
            // ...
        
    

SpringBoot 容器 启动流程

常用技巧

初始化类的属性

@PostConstruct

初始化类变量
  • 首先这个注解是由Java提供的,它用来修饰一个非静态的void方法。它会在服务器加载Servlet的时候运行,并且只运行一次。

  • 它的作用在于声明一个Bean对象初始化完成后执行的方法。

  • Bean初始化中的执行顺序:

    Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)

@Component
public class InitStaticVariableThree 

    private static final Logger logger = LoggerFactory.getLogger(InitStaticVariableThree.class);

    /**
     * 静态成员变量
     */
    public static RedissonClient redissonClientOne;

    /**
     * 注入 redissonClient
     */
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 使用 PostConstruct 给静态成员变量赋值
     */
    @PostConstruct
    public void init() 
        redissonClientOne = redissonClient;
        logger.info(String.valueOf(redissonClient));
        logger.info(String.valueOf(redissonClientOne));
    



注册 Bean

  • 除了注解、Java配置和XML配置的方式来创建Bean,还有另外一种方式来创建我们的BeanDefinition。通过 BeanDefinitionRegistryPostProcessor可以创建一个特别的后置处理器,来将BeanDefinition添加到BeanDefinitionRegistry中。
  • BeanDefinition 是对 Bean 的定义,其保存了 Bean 的各种信息,如属性、构造方法参数、是否单例、是否延迟加载等。这里的注册 Bean 是指将 Bean 定义成 BeanDefinition,之后放入 容器中

BeanDefinitionRegistryPostProcessor

  • BeanPostProcessor不同,BeanPostProcessor只是在Bean初始化的时候有个钩子让我们加入一些自定义操作;而BeanDefinitionRegistryPostProcessor可以让我们在BeanDefinition中添加一些自定义操作。这就跟类与类实例之间的区别类似。
执行时机
  • Bean定义没有被加载,bean实例还没有被初始化时候。
使用
  • 注册PersonBean:MyBeanDefinitionRegistryPostProcessor.java
@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor 

    private static final Logger logger = LoggerFactory.getLogger(MyBeanDefinitionRegistryPostProcessor.class);

    /**
     * 注册 Bean
     *
     * @param registry
     * @throws BeansException
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException 
        logger.info(LogConst.LOG_SUCCESS_PREFIX + "MyBeanDefinitionRegistryPostProcessor 中的 postProcessBeanDefinitionRegistry 方法");
        logger.info(LogConst.LOG_SUCCESS_PREFIX + "bean 定义的数据量:" + registry.getBeanDefinitionCount());
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(PersonBean.class);
        registry.registerBeanDefinition("personbean", rootBeanDefinition);
    

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException 
        logger.info(LogConst.LOG_SUCCESS_PREFIX + "MyBeanDefinitionRegisterPostProcessor中的postProcessBeanFactory方法");
        logger.info(LogConst.LOG_SUCCESS_PREFIX + "bean 定义的数据量:" + beanFactory.getBeanDefinitionCount());
    

  • 使用PersonBean:直接注入使用

实现 Bean 的初始化

实现InitializingBean

  • 定义 Bean 实现 InitializingBean
public class InitializingBeanExample implements InitializingBean 

    private static final Logger logger = LoggerFactory.getLogger(InitializingBeanExample.class);

    private int id;

    private String name;

    @Override
    public void afterPropertiesSet() 
        logger.info(LogConst.LOG_SUCCESS_PREFIX + " 初始化前的值为:", this);
        logger.info(LogConst.LOG_SUCCESS_PREFIX + " 我要对 PeopleBean 进行初始化!");
        this.id = 100;
        this.name = "李四";
        logger.info(LogConst.LOG_SUCCESS_PREFIX + " 初始化后的值为:", this);
    

  • 注册 Bean
@Configuration
public class InitializingBeanExampleTest 

    private static final Logger logger = LoggerFactory.getLogger(InitializingBeanExampleTest.class);

    @Bean
    public InitializingBeanExample initializingBeanExample() 
        InitializingBeanExample initializingBeanExample = new InitializingBeanExample();
        initializingBeanExample.setId(1);
        initializingBeanExample.setName("test1");
        logger.info(LogConst.LOG_SUCCESS_PREFIX + initializingBeanExample);
        return initializingBeanExample;
    

使用initMethod方法

  • 定义 Bean
@Bean(initMethod = "initBean")
@Lazy
public InitMethodBean initMethodBean() 
    InitMethodBean initMethodBean = new InitMethodBean();
    logger.info(LogConst.LOG_SUCCESS_PREFIX + "实例化 InitMethodBean 信息为:" + initMethodBean);
    return initMethodBean;

  • 定义 Beaninti-method 方法
public class InitMethodBean 

    private static final Logger logger = LoggerFactory.getLogger(InitMethodBean.class);

    private UUID id;

    private String name;

    private void initBean() 
        this.id = UUID.randomUUID();
        this.name = "InitMethodBean";
        logger.info(LogConst.LOG_SUCCESS_PREFIX + "执行 InitMethodBean 的 initBean():" + this);
    
    
    // ...

@PostConstruct

  • 看前面的讲解

总结

  • 初始化 Bean 的顺序

Constructor > @PostConstruct > InitializingBean > init-method


获取 Bean 修改 Bean 信息

实现 BeanPostProcessor 接口

实现BeanPostProcessor 接口,重写postProcessBeforeInitialization()postProcessAfterInitialization(),在重写的方法中,可以获得Bean的属性,对Bean进行相关的操作。

@Component
public class UserBeanPostProcessor implements BeanPostProcessor 

    private static final Logger logger = Logger.getLogger(String.valueOf(UserBeanPostProcessor.class));

	// 在这里可以拿到bean的相关信息,进行相关操作
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException 
        final String name = "myUser";
        if(name.equals(beanName))
            User user = (User) bean;
            user.setName("李四");
            user.setDate(new Date());
            logger.info("=====> postProcessBeforeInitialization():"+ JSONObject.toJSONString(user));
        
        return bean;
    

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException 
        return bean;
    

执行时机
  • postProcessBeforeInitialization() 方法:在 Bean 实例化、属性注入后,初始化前调用。
  • postProcessAfterInitialization() 方法:在 Bean 实例化、属性注入、初始化都完成后调用。

SpringBoot 启动后执行操作

  • springboot给我们提供了两种方式:ApplicationRunnerCommandLineRunner

  • 这两种方法提供的目的是为了满足,在项目启动的时候立刻执行某些方法。我们可以通过实现ApplicationRunnerCommandLineRunner,来实现,他们都是在SpringApplication 执行之后开始执行的。

Order 注解使用

  • Spring容器启动后可以加载一些资源或者做一些业务操作
  • 实现 CommandLineRunner
@Component
@Order(1)
public class OrderTestOne implements CommandLineRunner 

    private static final Logger logger = LoggerFactory.getLogger(OrderTestOne.class);

    /**
     * 执行
     *
     * @param args 参数
     * @throws Exception
     */
    @Override
    public void run(String... args) throws Exception 
        logger.info("OrderTestOne...");
    

  • order的值越小,优先级越高
  • order如果不标注数字,默认最低优先级,因为其默认值是int最大值

实现 ApplicationRunner、Ordered 接口

@Component
public class OrderTestTwo implements ApplicationRunner, Ordered 

    private static final Logger logger = LoggerFactory.getLogger(OrderTestTwo.class);

    /**
     * 执行
     *
     * @param args 参数
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception 
        logger.info("OrderTestTwo...");
    

    /**
     * 定义执行的顺序
     *
     * @return int
     */
    @Override
    public int getOrder() 
        return 2;
    


获取 ApplicationContext 容器

直接注入

  • 直接在使用的地方注入使用
@Autowired
private ApplicationContext applicationContextBean;

实现ApplicationContextAware接口

  • 实例代码SpringContextUtils,定义全局的获取上下文的工具类
@Component
public class SpringContextUtils implements ApplicationContextAware 
    
    private static final Logger logger = LoggerFactory.getLogger(SpringContextUtils.class);

    /**
     * 上下文对象实例
     */
    private static ApplicationContext applicationContext = null;


    private SpringContextUtils() 
    

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException 
        if (Objects.isNull(SpringContextUtils.applicationContext)) 
            logger.info(LogConst.LOG_SUCCESS_PREFIX + "ApplicationUtils初始化...");
            SpringContextUtils.applicationContext = applicationContext;
        
        logger.info(LogConst.LOG_SUCCESS_PREFIX + "ApplicationUtils初始化成功!");
    

    /**
     * 获得当前的ApplicationContext
     *
     * @return ApplicationContext
     */
    public static ApplicationContext getApplicationContext() 
        return applicationContext;
    

    /**
     * 根据名称拿到 Bean
     *
     * @param name Bean 的名称
     * @return Object
     */
    @SuppressWarnings("all")
    public static Object getBean(String name) 
        return getApplicationContext().getBean(name);
    

    /**
     * 从ApplicationContext中获得Bean并且转型
     *
     * @param tClass 类
     * @param <T>    T
     * @return T
     */
    public static <T> T getBean(Class<T> tClass) 
        return getApplicationContext().getBean(tClass);
    

作为参数获取 ApplicationContext Bean

  • Spring在初始化AutoConfiguration时会自动传入ApplicationContext,这时我们就可以使用下面的方式来获取ApplicationContext
@Configuration
public class TestConfig 

    private static final Logger logger = LoggerFactory.getLogger(TestConfig.class);

    /**
     * 作为参数获取

以上是关于# SpringBoot 常用扩展点介绍容器启动源码分析的主要内容,如果未能解决你的问题,请参考以下文章

掌握这些 Spring Boot 启动扩展点,已经超过 90% 的人了

掌握这些 Spring Boot 启动扩展点,已经超过 90% 的人了

三万字盘点 SpringBoot 的那些常用扩展点

三万字盘点 SpringBoot 的那些常用扩展点

一起学习springboot源码(系统初始化器)

SpringBoot注解介绍