SpringBoot扩展点剖析框架启动原理

        自2014年4月Pivotal团队发布全新开源的轻量级框架SpringBoot的第一个版本,迅速红遍大江南北,各个公司都用SpringBoot作为开发项目的必选项。SpringBoot从最初的1.2.x版本发展到最新的3.1.x,JDK从最初的6到最新的JDK17、JDK20,Spring Framework从4.1.x到最新的6.0.x,体现了SpringBoot强大的生命力。之前写过SpringBoot相关的文章,例如《第一个Spring Boot程序》,大家用SpringBoot已经驾轻就熟,今天从另外一个角度剖析SpringBoot框架,希望对大家有所帮助。

        SpringBoot作为一款开源轻量级框架为什么能极大的提升我们开发的生产力,我觉得和下面的一些特性是分不开的:

  • 内置Web容器(Tomcat、Jetty)
  • 很多内置的Starter项目,还支持自定义的Starter项目
  • 自动配置
  • 监控检查
  • 开箱即用(去掉原来繁杂的xml配置,改用注解)

        今天通过讲解SpringBoot(下面的示例代码基于spring-boot-2.3.1.RELEASE)提供的扩展点分析SpringBoot通过main方法启动后做了哪些事情,我们如果利用这些扩展点实现自己的业务应用,通过这些讲解希望大家了解自动配置,了解引入的Starter包是如果启动加载的。

1.初始化器ApplicationContextInitializer

        SpringBoot项目都是通过main开始启动加载的,下面是一个例子:

public class MyFirstApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyFirstApplication .class, args);
    }
}

        切入到SpringApplication类里面会看到这个类有构造方法,可以通过new一个对象来启动,因此上面的代码可以改写为:

public class MyFirstApplication {
    public static void main(String[] args) {
        new SpringApplication().run(MyFirstApplication .class, args);
    }
}

        直接上SpringApplication的源码:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

        这段代码设置了两种类型的扩展点,setInitializers设置的是初始化器,setListeners设置的是事件监听器。先讲初始化器,事件监听器放到第二节讲。

        在SpringApplication的下面方法设置断点:

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// 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;
	}

        启动项目后,查看instances的值,会发现有7个实例类,这些类都实现了 ApplicationContextInitializer 接口。

        如果要像这些类一样,在容器启动的时候就加载,是不是实现ApplicationContextInitializer即可,写个代码试试:

public class MyApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        System.out.println("My first ApplicationContextInitializer");
    }
}

        重启项目,定位到之前的断点处,查看instances的值,会发现还是7个实例类,没有自己的MyApplicationContextInitializer ,哪里出问题了?仔细研究代码发现这个类不是一个Bean,肯定不能自动加载,那SpringBoot自身默认的那些类是怎么加载的,这里就需要看文章前面介绍的SpringBoot特性“自动配置”,通过SPI机制可以让SpringBoot查找到该实现类,需要在reources/META-INF文件夹下新增一个spring.fatories文件,在里面加入:

org.springframework.context.ApplicationContextInitializer=com.example.init.MyApplicationContextInitializer

        重启项目,定位到之前的断点处,查看instances的值,会发现还是8个实例类:

        在MyApplicationContextInitializer ​​​​​​类里可以实现业务逻辑,这些事情可以在Spring容器建立前做,例如可以排除哪些Starter组件加载。

2.事件监听器ApplicationListener

        在第一节中介绍了初始化器,这一节介绍事件监听器,事件监听器是通过setListeners方法设置的。同理,实现事件监听器首先实现ApplicationListener接口:

public class MyStartingApplicationListener implements ApplicationListener<ApplicationStartingEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartingEvent applicationStartingEvent) {
        System.out.println("My event starting");
    }
}

        在spring.fatories文件里面加入:

org.springframework.context.ApplicationListener=com.example.init.MyStartingApplicationListener

        在org.springframework.boot.context.event包下有8个事件可以用:

  • ApplicationContextEvent
  • ApplicationFailedEvent
  • ApplicationContextInitializedEvent
  • ApplicationEnvironmentPreparedEvent
  • ApplicationPreparedEvent
  • ApplicationReadyEvent
  • ApplicationStartedEvent
  • ApplicationStartingEvent

3.Runner扩展点

        Runner的扩展点有两个接口:CommandLineRunnerApplicationRunner,这个是在Spring容器建立后才启动,因此不需要通过SPI机制,直接作为Bean启动。

        可以先看看源码:

private void callRunners(ApplicationContext context, ApplicationArguments args) {
   List<Object> runners = new ArrayList<>();
   runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
   runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
   AnnotationAwareOrderComparator.sort(runners);
   for (Object runner : new LinkedHashSet<>(runners)) {
      if (runner instanceof ApplicationRunner) {
         callRunner((ApplicationRunner) runner, args);
      }
      if (runner instanceof CommandLineRunner) {
         callRunner((CommandLineRunner) runner, args);
      }
   }
}

        业务代码:

@Component
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("My first ApplicationRunner");
    }
}

        如果有多个Runner,需要控制启动顺序,可以在类上加@Order(1)设置优先级,数字越小优先级越高。

4.BeanFactoryPostProcessor

        在SpringApplication的此方法:public ConfigurableApplicationContext run(String... args) 中有一步刷新容器:refreshContext(context),最终调用了父类的方法AbstractApplicationContext#refresh,展示下这个方法的核心代码:

try {
		// Allows post-processing of the bean factory in context subclasses.
		postProcessBeanFactory(beanFactory);

		// Invoke factory processors registered as beans in the context.
		invokeBeanFactoryPostProcessors(beanFactory);

		// Register bean processors that intercept bean creation.
		registerBeanPostProcessors(beanFactory);

		// Initialize message source for this context.
		initMessageSource();

		// Initialize event multicaster for this context.
		initApplicationEventMulticaster();

		// Initialize other special beans in specific context subclasses.
		onRefresh();

		// Check for listener beans and register them.
		registerListeners();

		// Instantiate all remaining (non-lazy-init) singletons.
		finishBeanFactoryInitialization(beanFactory);

		// Last step: publish corresponding event.
		finishRefresh();
}

        invokeBeanFactoryPostProcessors(beanFactory)就是一个扩展点,该扩展点对应的接口是BeanFactoryPostProcessor,执行对BeanFactory的后置处理。

自定义一个BeanFactoryPostProcessor,在这里面可以获取BeanDefinition

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("StudentController Bean:" + beanFactory.getBeanDefinition("studentController"));
        System.out.println("My first BeanFactoryPostProcessor");
    }
}

5.BeanPostProcessor

        在AbstractApplicationContext#refresh中的registerBeanPostProcessors(beanFactory)就是向上面建立成功的BeanFactory注册beanPostProcessor,用于后续Bean的处理。

        下面建立一个自己的BeanPostProcessor

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(beanName.equals("studentController")){
            System.out.println("开始加载Bean studentController");
        }
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(beanName.equals("studentController")){
            System.out.println("加载Bean studentController完成");
        }
        return null;
    }
}

6.扩展点加载顺序

相关推荐

  1. SpringBoot启动原理

    2024-01-26 17:22:05       15 阅读
  2. springboot常用扩展

    2024-01-26 17:22:05       36 阅读
  3. SpringBoot 原理深入及源码剖析

    2024-01-26 17:22:05       25 阅读
  4. springboot多线程的原理剖析

    2024-01-26 17:22:05       22 阅读
  5. Spring Boot 启动扩展深入解析

    2024-01-26 17:22:05       14 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-26 17:22:05       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-26 17:22:05       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-26 17:22:05       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-26 17:22:05       18 阅读

热门阅读

  1. 如何设计一个可靠UDP

    2024-01-26 17:22:05       29 阅读
  2. Acwing---788.逆序对的数量

    2024-01-26 17:22:05       31 阅读
  3. 常见的网络安全攻击类型

    2024-01-26 17:22:05       39 阅读
  4. 蓝桥杯练习题

    2024-01-26 17:22:05       35 阅读
  5. fbx格式转换

    2024-01-26 17:22:05       37 阅读
  6. Flutter插件和第三方库的区别以及共通

    2024-01-26 17:22:05       38 阅读
  7. spring boot分离打包

    2024-01-26 17:22:05       34 阅读