• Spring/SpringBoot的那些常用扩展点


    目录

    FactoryBean

    FactoryBean在开源框架中的使用

    1、 在Mybatis中的使用

    @Import注解

    @Import注解导入的配置类的分类

    第一种:配置类实现了 ImportSelector 接口

    第二种:配置类实现了 ImportBeanDefinitionRegistrar 接口

    第三种:配置类什么接口都没实现

    @Import总结

    Bean的生命周期

    Bean生命周期的回调

    声明LifeCycle

    BeanPostProcessor

    来个Demo

    Spring内置的BeanPostProcessor

    BeanFactoryPostProcessor

    Spring SPI机制

    SpringFactoriesLoader

    SpringBoot启动扩展点

    1、自动装配

    2、PropertySourceLoader

    3、ApplicationContextInitializer

    4、EnvironmentPostProcessor


    FactoryBean

            提起FactoryBean,就有一道“著名”的面试题“说一说FactoryBean和BeanFactory的区别”。其实这两者除了名字有点像,没有半毛钱关系。

            BeanFactory是Bean的工厂,可以帮我们生成想要的Bean,而FactoryBean就是一种Bean的类型。当往容器中注入class类型为FactoryBean的类型的时候,最终生成的Bean是用过FactoryBean的getObject获取的。

    来个FactoryBean的Demo

    定义一个UserFactoryBean,实现FactoryBean接口,getObject方法返回一个User对象:

    1. import lombok.Data;
    2. import org.springframework.beans.factory.FactoryBean;
    3. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    4. /**
    5. * @Classname UserFactoryBean
    6. * @Date 2022/9/17 上午10:35
    7. * @Created by liuchao58
    8. * @Description
    9. */
    10. public class UserFactoryBean implements FactoryBean {
    11. @Override
    12. public User getObject() throws Exception {
    13. User user = new User();
    14. user.setUsername("testUserName");
    15. System.out.println("调用 UserFactoryBean 的 getObject 方法生成 Bean:" + user);
    16. return user;
    17. }
    18. @Override
    19. public Class getObjectType() {
    20. // 这个 FactoryBean 返回的Bean的类型
    21. return User.class;
    22. }
    23. }
    24. @Data
    25. class User {
    26. private String username;
    27. }
    28. class Application {
    29. public static void main(String[] args) {
    30. AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    31. //将 UserFactoryBean 注册到容器中
    32. applicationContext.register(UserFactoryBean.class);
    33. applicationContext.refresh();
    34. System.out.println("获取到的Bean为" + applicationContext.getBean(User.class));
    35. }
    36. }

     结果:

    结论:

    从结果可以看出,明明注册到Spring容器的是UserFactoryBean,但是却能从容器中获取到User类型的Bean,User这个Bean就是通过UserFactoryBean的getObject方法返回的。 

    FactoryBean在开源框架中的使用

    1、 在Mybatis中的使用

    Mybatis在整合Spring的时候,就是通过FactoryBean来实现的,这也就是为什么在Spring的Bean中可以注入Mybatis的Mapper接口的动态代理对象的原因。

    代码如下,省略了不重要的代码。

    1. public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
    2. // mapper的接口类型
    3. private Class<T> mapperInterface;
    4. @Override
    5. public T getObject() throws Exception {
    6. // 通过SqlSession获取接口的动态搭理对象
    7. return getSqlSession().getMapper(this.mapperInterface);
    8. }
    9. @Override
    10. public Class<T> getObjectType() {
    11. return this.mapperInterface;
    12. }
    13. }

    getObject方法的实现就是返回通过SqlSession获取到的Mapper接口的动态代理对象。

    而@MapperScan注解的作用就是将每个接口对应的MapperFactoryBean注册到Spring容器的。

    @Import注解

    @Import注解在项目中可能不常见,但是下面这两个注解肯定常见。

    1. @Import({SchedulingConfiguration.class})
    2. public @interface EnableScheduling {
    3. }
    4. @Import({AsyncConfigurationSelector.class})
    5. public @interface EnableAsync {
    6. //忽略
    7. }

    @EnableScheduling和@EnableAsync两个注解,一个是开启定时任务,一个是开启异步执行。通过这两个注解可以看出,他们都使用了@Import注解,所以真正起作用的是@Import注解。并且在很多情况下,@EnbaleXXX这种格式的注解,都是通过@Import注解起作用的,代表开启了某个功能。

    @Import注解导入的配置类的分类

    @Import注解导入的配置类可以分为三种情况:

    第一种:配置类实现了 ImportSelector 接口

    1. public interface ImportSelector {
    2. String[] selectImports(AnnotationMetadata importingClassMetadata);
    3. @Nullable
    4. default Predicate<String> getExclusionFilter() {
    5. return null;
    6. }
    7. }

    当配置类实现了 ImportSelector 接口的时候,就会调用 selectImports 方法的实现,获取一批类的全限定名,最终这些类就会被注册到Spring容器中。

    举例如下,UserImportSelector实现了ImportSelector,selectImports方法返回User的全限定名,代表吧User这个类注册容器中

    1. class UserImportSelector implements ImportSelector {
    2. @Override
    3. public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    4. System.out.println("调用 UserImportSelector 的 selectImports 方法获取一批类限定名");
    5. return new String[]{"com.renrenche.business.spring.extend.User"};
    6. }
    7. }
    1. // @Import 注解导入 UserImportSelector
    2. @Import(UserImportSelector.class)
    3. class Application {
    4. public static void main(String[] args) {
    5. AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    6. //将 UserFactoryBean 注册到容器中
    7. applicationContext.register(Application.class);
    8. applicationContext.refresh();
    9. System.out.println("获取到的Bean为" + applicationContext.getBean(User.class));
    10. }
    11. }

    结果如图:

    所以可以看出,的确成功往容器中注入了User这个Bean 

    第二种:配置类实现了 ImportBeanDefinitionRegistrar 接口

    1. public interface ImportBeanDefinitionRegistrar {
    2. default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {
    3. registerBeanDefinitions(importingClassMetadata, registry);
    4. }
    5. default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    6. }
    7. }

    当配置类实现了 ImportBeanDefinitionRegistrar 接口,你就可以自定义往容器中注册想注入的Bean。这个接口相比与 ImportSelector 接口的主要区别就是,ImportSelector接口是返回一个类,你不能对这个类进行任何操作,但是 ImportBeanDefinitionRegistrar 是可以自己注入 BeanDefinition,可以添加属性之类的。

    来个demo,实现ImportBeanDefinitionRegistrar接口如下:

    1. class UserImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    2. @Override
    3. public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
    4. //构建一个 BeanDefinition , Bean的类型为 User
    5. AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class)
    6. // 设置 User 这个Bean的属性username的值为hello world
    7. .addPropertyValue("username", "hello world")
    8. .getBeanDefinition();
    9. System.out.println("往Spring容器中注入User");
    10. //把 User 这个Bean的定义注册到容器中
    11. beanDefinitionRegistry.registerBeanDefinition("user", beanDefinition);
    12. }
    13. }
    14. // 导入 UserImportBeanDefinitionRegistrar
    15. @Import(UserImportBeanDefinitionRegistrar.class)
    16. class Application {
    17. public static void main(String[] args) {
    18. AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    19. //将 UserFactoryBean 注册到容器中
    20. applicationContext.register(Application.class);
    21. applicationContext.refresh();
    22. User user = applicationContext.getBean(User.class);
    23. System.out.println("获取到的Bean为" + user + ",属性username值为:" + user.getUsername());
    24. }
    25. }

    结果如下图:

    第三种:配置类什么接口都没实现

    这种就不演示了,就是一个普普通通的类。

    @Import总结

    其实不论是什么样的配置类,主要的作用就是往Spring容器中注册Bean,只不过注入的方式不同罢了。

    ImportSelector和ImportBeanDefinitionRegistrar的方法是有入参的,也就是注解的一些属性的封装,所以就可以根据注解的属性的配置,来决定应该返回样的配置类或者是应该往容器中注入什么样的类型的Bean,可以看一下 @EnableAsync 的实现,看看是如何根据@EnableAsync注解的属性来决定往容器中注入什么样的Bean。

     

    @Import的核心作用就是导入配置类,并且还可以根据配合(比如@EnableXXX)使用的注解的属性来决定应该往Spring中注入什么样的Bean。 

    Bean的生命周期

    上述说过FactoryBean是一种特殊的Bean的类型,@Import注解是往Spring容器中注册Bean。其实不论是@Import注解,还是@Component、@Bean等注解,又或是xml配置,甚至是demo中的register方法,其实主要都是做了一件事,那就是往Spring容器去注册Bean。

    那么为什么需要去注册Bean呢? 

    当然是为了让Spring知道要为我们生成Bean,并且需要按照我的要求来生成Bean,比如说,我要@Autowired一个对象,那么你在创建Bean的过程中,就得给我@Autowired一个对象,这就是一个IOC的过程。所以这就涉及了Bean的创建,销毁的过程,也就是面试常问的Bean的生命周期

    下面着重看一下,一个Bean在创建的过程中,有哪些常见的操作Spring在Bean的创建过程中给我们完成,并且操作的顺序是什么样的。

    Bean生命周期的回调

    测试demo如下,创建LifeCycle类

    创建了一个LifeCycle,实现了 InitializingBean、ApplicationContextAware、DisposableBean接口,加了@PostConstruct、@PreDestroy注解,注入了一个User对象。

    1. package com.renrenche.business.spring.extend;
    2. import org.springframework.beans.BeansException;
    3. import org.springframework.beans.factory.DisposableBean;
    4. import org.springframework.beans.factory.InitializingBean;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.context.ApplicationContext;
    7. import org.springframework.context.ApplicationContextAware;
    8. import javax.annotation.PostConstruct;
    9. import javax.annotation.PreDestroy;
    10. /**
    11. * @Classname LifeCycle
    12. * @Date 2022/9/17 下午2:41
    13. * @Created by liuchao58
    14. * @Description
    15. */
    16. public class LifeCycle implements InitializingBean, ApplicationContextAware, DisposableBean {
    17. @Autowired
    18. private User user;
    19. public LifeCycle() {
    20. System.out.println("LifeCycle对象被创建了");
    21. }
    22. /**
    23. * 实现的 Aware 回调接口
    24. *
    25. * @param applicationContext
    26. * @throws BeansException
    27. */
    28. @Override
    29. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    30. System.out.println("Aware接口起作用,setApplicationContext被调用了,此时user=" + user);
    31. }
    32. @PostConstruct
    33. public void postConstruct() {
    34. System.out.println("@PostConstruct注解起作用,postConstruct方法被调用了");
    35. }
    36. /**
    37. * 实现 InitializingBean 接口
    38. *
    39. * @throws Exception
    40. */
    41. @Override
    42. public void afterPropertiesSet() throws Exception {
    43. System.out.println("InitializingBean接口起作用,afterPropertiesSet方法被调用了");
    44. }
    45. /**
    46. *
    47. * @throws Exception
    48. */
    49. public void initMethod() throws Exception {
    50. System.out.println("@Bean#initMethod()起作用,initMethod方法被调用了");
    51. }
    52. @PreDestroy
    53. public void preDestroy() throws Exception {
    54. System.out.println("@PreDestroy注解起作用,preDestroy方法被调用了");
    55. }
    56. /**
    57. *
    58. * @throws Exception
    59. */
    60. public void destroyMethod() throws Exception {
    61. System.out.println("@Bean#destroyMethod()起作用,destroyMethod方法被调用了");
    62. }
    63. /**
    64. * 实现 DisposableBean 注解
    65. *
    66. * @throws Exception
    67. */
    68. @Override
    69. public void destroy() throws Exception {
    70. System.out.println("DisposableBean接口起作用,destroy方法被调用了");
    71. }
    72. }

    声明LifeCycle

    通过@Bean声明了LifeCycle,并且initMethod和destroyMethod属性分别指定到了LifeCycle类的initMethod方法和destroyMethod方法。

    1. @Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
    2. public LifeCycle lifeCycle() {
    3. return new LifeCycle();
    4. }

    测试

    1. class Application {
    2. public static void main(String[] args) {
    3. AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    4. //将 LifeCycle 注册到容器中
    5. applicationContext.register(Application.class);
    6. applicationContext.refresh();
    7. // 关闭上下文,触发销毁操作
    8. applicationContext.close();
    9. }
    10. @Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
    11. public LifeCycle lifeCycle() {
    12. return new LifeCycle();
    13. }
    14. @Bean
    15. public User user() {
    16. return new User();
    17. }
    18. }

    执行结果如下图:

     分析结果

            通过测试的结果可以看出,Bean在创建和销毁的过程当我们实现了某些接口或者加了某些注解,Spring就会回调我们实现的接口或者执行的方法。同时,在执行setApplicationContext的时候,能打印出User对象,说明User已经被注入了,说明注入发生在setApplicationContext之前。

    这里画张图总结一下Bean创建和销毁过程中调用的顺序。

            红色部分发生在Bean的创建过程,灰色部分发生在Bean销毁的过程中,在容器关闭的时候,就会销毁Bean。

            这里说一下图中的Aware接口指的是什么。其余的其实没什么好说的,就是按照这种方式配置,Spring会调用对应的方法而已。Aware接口是指以Aware结尾的一些Spring提供的接口,当你的Bean实现了这些接口的话,在创建过程中会回调对应的set方法,并传入响应的对象。

    这里列举几个Aware接口以及它们的作用。

    接口作用
    ApplicationContextAware注入ApplicationContext
    ApplicationEventPublisherAware注入ApplicationEventPublisher事件发布器
    BeanFactoryAware注入BeanFactory
    BeanNameAware注入Bean的名称

    有了这些回调,比如说我的Bean想拿到ApplicationContext,不仅可以通过@Autowired注入,还可以通过实现ApplicationContextAware接口拿到。

    通过上面的例子我们知道了比如说@PostConstruct注解、@Autowired注解、@PreDestroy注解的作用,但是它们是如何在不同的阶段实现的呢?接着往下看。

    BeanPostProcessor

    BeanPostProcessor,中文名 Bean的后置处理器,在Bean创建的过程中起作用。

    BeanPostProcessor是Bean在创建过程中一个非常重要的扩展点,因为每个Bean在创建的各个阶段,都会回调BeanPostProcessor及其子接口的方法,传入正在创建的Bean对象,这样如果想对Bean创建过程中某个阶段进行自定义扩展,那么就可以自定义BeanPostProcessor来完成。

    说得简单点,BeanPostProcessor就是在Bean创建过程中留的口子,通过这个口子可以对正在创建的Bean进行扩展。只不过Bean创建的阶段比较多,然后BeanPostProcessor接口以及他的子接口InstantiationAwareBeanPostProcessor、DestructionAwareBeanPostProcessor就提供了很多方法,可以使得在不同的阶段都可以拿到正在创建的Bean进行扩展。

    来个Demo

    现在需要实现一个这样的需求,如果Bean的类型是User,那么就设置这个对象的username属性为 ”hello world“。那么就可以这么写:

    1. package com.renrenche.business.spring.extend;
    2. import org.springframework.beans.BeansException;
    3. import org.springframework.beans.factory.config.BeanPostProcessor;
    4. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    5. /**
    6. * @Classname UserBeanPostProcessor
    7. * @Date 2022/9/17 下午2:54
    8. * @Created by liuchao58
    9. * @Description
    10. */
    11. public class UserBeanPostProcessor implements BeanPostProcessor {
    12. @Override
    13. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    14. if (bean instanceof User) {
    15. //如果当前的Bean的类型是 User ,就把这个对象 username 的属性赋值为 hello world
    16. ((User) bean).setUsername("hello world");
    17. }
    18. return bean;
    19. }
    20. }
    21. class MyApplication {
    22. public static void main(String[] args) {
    23. AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    24. //将 UserBeanPostProcessor 和 User 注册到容器中
    25. applicationContext.register(UserBeanPostProcessor.class);
    26. applicationContext.register(User.class);
    27. applicationContext.refresh();
    28. User user = applicationContext.getBean(User.class);
    29. System.out.println("获取到的Bean为" + user + ",属性username值为:" + user.getUsername());
    30. }
    31. }

    结果如下图:

    从结果可以看出,每个生成的Bean在执行到某个阶段的时候,都会回调UserBeanPostProcessor,然后UserBeanPostProcessor就会判断当前创建的Bean的类型,如果是User类型,那么就会将username的属性设置为 ”hello world“。

    Spring内置的BeanPostProcessor

    这里我列举了常见的一些BeanPostProcessor的实现以及它们的作用。

    BeanPostProcessor作用
    AutowiredAnnotationBeanPostProcessor处理@Autowired、@Value注解
    CommonAnnotationBeanPostProcessor处理@Resource、@PostConstruct、@PreDestroy注解
    AnnotationAwareAspectJAutoProxyCreator处理一些注解或者是AOP切面的动态代理
    ApplicationContextAwareProcessor处理Aware接口注入的
    AsyncAnnotationBeanPostProcessor处理@Async注解
    ScheduledAnnotationBeanPostProcessor处理@Scheduled注解

    通过列举的这些BeanPostProcessor的实现可以看出,Spring Bean的很多注解的处理都是依靠BeanPostProcessor及其子类的实现来完成的,也解释了处理@Autowired、@PostConstruct、@PreDestroy注解是如何起作用的,其实就是通过BeanPostProcessor,在Bean的不同阶段来调用对应的方法起作用的。

    BeanFactoryPostProcessor

    通过上面我们知道 BeanPostProcessor 是对Bean的处理,那么BeanFactoryPostProcessor很容易就猜到是对BeanFactory,也就是Spring容器的处理。

    举个例子,如果我们想禁止循环依赖,那么就可以这么写。

    1. public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    2. @Override
    3. public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    4. // 禁止循环依赖
    5. ((DefaultListableBeanFactory) beanFactory).setAllowCircularReferences(false);
    6. }
    7. }

     后面只需要将注入到Spring容器中就会生效。

    BeanFactoryPostProcessor是可以对Spring容器做处理的,方法的入参就是Spring的容器,通过这个接口,就对容器进行为所欲为的操作。

    Spring SPI机制

    SPI全称为 (Service Provider Interface),是一种动态替换发现的机制,一种解耦非常优秀的思想,SPI可以很灵活的让接口和实现分离, 让api提供者只提供接口,第三方来实现,然后可以使用配置文件的方式来实现替换或者扩展,在框架中比较常见,提高框架的可扩展性。

    JDK有内置的SPI机制的实现ServiceLoader,Dubbo也有自己的SPI机制的实现ExtensionLoader,这里我们着重讲一下Spring的SPI机制的实现SpringFactoriesLoader

    SpringFactoriesLoader

    Spring的SPI机制规定,配置文件必须在classpath路径下的META-INF文件夹内,文件名必须为spring.factories,文件内容为键值对,一个键可以有多个值,只需要用逗号分割就行,同时键值都需要是类的全限定名。但是键和值可以没有任何关系,当然想有也可以有。

    举例如下:

    这里我自定义一个类,MyEnableAutoConfiguration作为键,值就是User

    1. public class MyEnableAutoConfiguration {
    2. }

    spring.factories文件

    com.liuchao58.spring.extension.spi.MyEnableAutoConfiguration=com.liuchao58.spring.extension.User

    然后放在META-INF底下

    测试:

    1. public class Application {
    2. public static void main(String[] args) {
    3. List<String> classNames = SpringFactoriesLoader.loadFactoryNames(MyEnableAutoConfiguration.class, MyEnableAutoConfiguration.class.getClassLoader());
    4. classNames.forEach(System.out::println);
    5. }
    6. }

    可以看出,通过SpringFactoriesLoader的确可以从spring.factories文件中拿到MyEnableAutoConfiguration键对应的值。

    到这你可能说会,这SPI机制也没啥用啊。的确,我这个例子比较简单,拿到就是遍历,但是在Spring中,如果Spring在加载类的话使用SPI机制,那我们就可以扩展,接着往下看。

    SpringBoot启动扩展点

    SpringBoot项目在启动的过程中有很多扩展点,这里就来盘点一下几个常见的扩展点。

    1、自动装配

    说到SpringBoot的扩展点,第一时间肯定想到的就是自动装配机制,面试贼喜欢问,但是其实就是一个很简单的东西。当项目启动的时候,会去从所有的spring.factories文件中读取@EnableAutoConfiguration键对应的值,拿到配置类,然后根据一些条件判断,决定哪些配置可以使用,哪些不能使用。

    spring.factories文件?键值?不错,自动装配说白了就是SPI机制的一种运用场景。

    @EnableAutoConfiguration注解:

    1. @Import(AutoConfigurationImportSelector.class)
    2. public @interface EnableAutoConfiguration {
    3. //忽略
    4. }

    这个注解也是使用@Import注解,而且配置类还实现了ImportSelector接口,跟前面也都对上了。在SpringBoot中,@EnableAutoConfiguration是通过@SpringBootApplication来使用的。

    在AutoConfigurationImportSelector中还有这样一段代码

    所以,这段代码也明显地可以看出,自动装配也是基于SPI机制实现的。

    2、PropertySourceLoader

    PropertySourceLoader,这是干啥的呢?

    我们都知道,在SpringBoot环境下,外部化的配置文件支持properties和yaml两种格式。但是,现在不想使用properties和yaml格式的文件,想使用json格式的配置文件,怎么办?

    当然是基于该小节讲的PropertySourceLoader来实现的。

    1. public interface PropertySourceLoader {
    2. //可以支持哪种文件格式的解析
    3. String[] getFileExtensions();
    4. // 解析配置文件,读出内容,封装成一个PropertySource<?>结合返回回去
    5. List<PropertySource<?>> load(String name, Resource resource) throws IOException;
    6. }

    对于PropertySourceLoader的实现,SpringBoot两个实现

    PropertiesPropertySourceLoader:可以解析properties或者xml结尾的配置文件

    YamlPropertySourceLoader:解析以yml或者yaml结尾的配置文件

     

    所以可以看出,要想实现json格式的支持,只需要自己实现可以用来解析json格式的配置文件的PropertySourceLoader就可以了。

    实现可以读取json格式的配置文件

    实现这个功能,只需要两步就可以了。

    第一步:自定义一个PropertySourceLoader

    JsonPropertySourceLoader,实现PropertySourceLoader接口

    1. public class JsonPropertySourceLoader implements PropertySourceLoader {
    2. @Override
    3. public String[] getFileExtensions() {
    4. //这个方法表明这个类支持解析以json结尾的配置文件
    5. return new String[]{"json"};
    6. }
    7. @Override
    8. public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
    9. ReadableByteChannel readableByteChannel = resource.readableChannel();
    10. ByteBuffer byteBuffer = ByteBuffer.allocate((int) resource.contentLength());
    11. //将文件内容读到 ByteBuffer 中
    12. readableByteChannel.read(byteBuffer);
    13. //将读出来的字节转换成字符串
    14. String content = new String(byteBuffer.array());
    15. // 将字符串转换成 JSONObject
    16. JSONObject jsonObject = JSON.parseObject(content);
    17. Map<String, Object> map = new HashMap<>(jsonObject.size());
    18. //将 json 的键值对读出来,放入到 map 中
    19. for (String key : jsonObject.keySet()) {
    20. map.put(key, jsonObject.getString(key));
    21. }
    22. return Collections.singletonList(new MapPropertySource("jsonPropertySource", map));
    23. }
    24. }

    第二步:配置PropertySourceLoader

    JsonPropertySourceLoader 已经有了,那么怎么用呢?当然是SPI机制了,SpringBoot对于配置文件的处理,就是依靠SPI机制,这也是能扩展的重要原因。

     spring.factories文件配置PropertySourceLoader

    SpringBoot会先通过SPI机制加载所有PropertySourceLoader,然后遍历每个PropertySourceLoader,判断当前遍历的PropertySourceLoader,通过getFileExtensions获取到当前PropertySourceLoader能够支持哪些配置文件格式的解析,让后跟当前需要解析的文件格式进行匹配,如果能匹配上,那么就会使用当前遍历的PropertySourceLoader来解析配置文件。

    PropertySourceLoader其实就属于策略接口,配置文件的解析就是策略模式的运用。

    所以,只需要按照这种格式,在spring.factories文件中配置一下就行了。

    1. org.springframework.boot.env.PropertySourceLoader=\
    2. com.liuchao58.spring.extension.springbootextension.propertysourceloader.JsonPropertySourceLoader

     到此,其实就扩展完了。

    Nacos对于PropertySourceLoader的实现

    如果你的项目正在用Nacos作为配置中心,那么刚刚好,Nacos已经实现json配置文件格式的解析。

    Nacos不仅实现了json格式的解析,也实现了关于xml格式的配置文件的解析,并且优先级会比SpringBoot默认的xml格式文件解析的优先级高。至于Nacos为啥需要实现PropertySourceLoader?其实很简单,因为Nacos作为配置中心,不仅支持properties和yaml格式的文件,还支持json格式的配置文件,那么客户端拿到这些配置就需要解析,SpringBoot已经支持了properties和yaml格式的文件的解析,那么Nacos只需要实现SpringBoot不支持的就可以了。

    3、ApplicationContextInitializer

    ApplicationContextInitializer也是SpringBoot启动过程的一个扩展点。

    在SpringBoot启动过程,会回调这个类的实现initialize方法,传入ConfigurableApplicationContext。

    那怎么用呢?

    依然是SPI。

    SPI加载ApplicationContextInitializer

    然后遍历所有的实现,依次调用。

    调用initialize方法。

    这里就不演示了,实现接口,按照如下这种配置就行了

    但是这里需要注意的是,此时传入的ConfigurableApplicationContext并没有调用过refresh方法,也就是里面是没有Bean对象的,一般这个接口是用来配置ConfigurableApplicationContext,而不是用来获取Bean的。

    4、EnvironmentPostProcessor

    EnvironmentPostProcessor在SpringBoot启动过程中,也会调用,也是通过SPI机制来加载扩展的。

    EnvironmentPostProcessor是用来处理ConfigurableEnvironment的,也就是一些配置信息,SpringBoot所有的配置都是存在这个对象的。

    说这个类的主要原因,主要不是说扩展,而是它的一个实现类很关键。

    这个类的作用就是用来处理外部化配置文件的,也就是这个类是用来处理配置文件的,通过前面提到的PropertySourceLoader解析配置文件,放到ConfigurableEnvironment里面。   

  • 相关阅读:
    单片机控制直流电机(风扇)电路详解
    缓存相关问题:雪崩、穿透、预热、更新、降级的深度解析
    AWS】在EC2上创建root用户,并使用root用户登录
    深度解读AIGC存储解决方案
    【数据库】B树、B+树、索引
    【HMS core】【FAQ】音频编辑服务、推送服务、AR Engine典型问题合集
    C++中string对象之间比较、char*之间比较
    【K8S系列】深入解析k8s 网络插件—Antrea
    十五、商城 - 品牌管理-AngularJS(3)
    CSS特效001:鼠标放div上,实现旋转、放大、移动等效果
  • 原文地址:https://blog.csdn.net/CoderTnT/article/details/126902381