【SpringBoot基础系列-实战】如何指定 bean 最先加载(应用篇)

简介: 在日常的业务开发中,绝大多数我们都是不关注 bean 的加载顺序,然而如果在某些场景下,当我们希望某个 bean 优于其他的 bean 被实例化时,往往并没有我们想象中的那么简单

image.png


在日常的业务开发中,绝大多数我们都是不关注 bean 的加载顺序,然而如果在某些场景下,当我们希望某个 bean 优于其他的 bean 被实例化时,往往并没有我们想象中的那么简单


I. 启动类指定方式



在实际的 SpringBoot 开发中,我们知道都会有一个启动类,如果希望某个类被优先加载,一个成本最低的简单实现,就是在启动类里添加上依赖


@SpringBootApplication
public class Application {
    public Application(DemoBean demoBean) {
        demoBean.print();
    }
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}
复制代码


请注意上面的构造方法,如果我们希望在应用启动之前,demoBean就已经被加载了,那就让 Application 强制依赖它,所以再 Application 的 bean 初始化之前,肯定会优先实例化demoBean


相信上面这种写法,大家并不会陌生,特别是当我们应用启动之后,发现某个依赖的 bean(一般来讲是第三方库提供的 bean)还没有初始化导致 npe 时,用这种方法还是比较多的


case1

我们且不谈这种实现方式是否优雅,当我们希望targetBean在所有的 bean 实例化之前被实例时,上面这种写法是否一定会生效呢?


case2

中间件同学:吭哧吭哧的开发了一个 🐂🍺jar 包,只要接入了保证你的应用永远不会宕机(请无视夸张的言语),唯一的要求是接入时,需要优先加载 jar 包里面的firstBean...


接入方:你的 bean 要求被首先加载这个得你自己保证啊,我写些 if/else 代码已经很辛苦了,哪有精力保证你的这个优先加载!!!你自己都没法保证,那我也没办法保证...

中间件同学:还能不能愉快的玩耍了....


II. InstantiationAwareBeanPostProcessorAdapter方式



在看下文的实现之前,墙裂推荐先看一下博文: 【SpringBoot 基础系列】指定 Bean 初始化顺序的若干姿势


接下来介绍另外一种使用姿势,借助

InstantiationAwareBeanPostProcessorAdapter来实现在 bean 实例化之前优先加载目标 bean


声明

  • 我个人认为下面这种使用方式,依然很不优雅,如有更好方式,恳请大佬留言告知
  • 我个人认为下面这种使用方式,依然很不优雅,如有更好方式,恳请大佬留言告知
  • 我个人认为下面这种使用方式,依然很不优雅,如有更好方式,恳请大佬留言告知


1. 场景分析


假设我们提供了一个配置读取的工具包,但是不同的应用可能对配置的存储有不同的要求,比如有的配置存在本地,有的存在 db,有的通过 http 方式远程获取;而这些存储方式呢,通过application.yml配置文件中的配置参数config.save.mode来指定

这个工具包呢,会做一件事情,扫描应用程序的所有类,并注入配置信息,所以我们希望在应用程序启动之前,这个工具包就已经从数据源获取到了配置信息,而这又要求先获取应用到底是用的哪个数据源


简单来讲,就是希望在应用程序工作之前,DatasourceLoader这个 bean 已经被实例化了


-- 插播一句,上面这个 case,正是我在筹备的SpringBoot实战教程--从0到1创建一个高可用的配置中心的具体应用场景


2. 常规流程


新建一个 SpringBoot 项目工程,源码中 springboot 版本为2.2.1.RELEASE

首先我们来定义这个目标 bean: DatasourceLoader


public class DatasourceLoader {
    @Getter
    private String mode;
    public DatasourceLoader(Environment environment) {
        this.mode = environment.getProperty("config.save.mode");
        System.out.println("init DatasourceLoader for:" + mode);
    }
    @PostConstruct
    public void loadResourcres() {
        System.out.println("开始初始化资源");
    }
}
复制代码


因为这个工程主要是供第三方使用,所以按照 SpringBoot 的通常玩法,声明一个自动配置类


@Configuration
public class ClientAutoConfiguration {
    @Bean
    public DatasourceLoader propertyLoader(Environment environment) {
        return new DatasourceLoader(environment);
    }
}
复制代码


然后在资源目录下新建文件夹 META-INF,创建文件spring.factories,内容如下

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.git.hui.boot.client.ClientAutoConfiguration
复制代码


然后使用方添加依赖,就完了???


上面这套流程,属于一般的工具包写法了,请注意,这种方式,一般情况下是应用程序内声明的 bean 加载完毕之后,才会加载第三方依赖包中声明的 bean;也就是说通过上面的写法,DatasourceLoader并不会被优先加载,也达不到我们的目的(应用都开始服务了,结果所有的配置都是 null)


3. 特殊写法


接下来我们借助所有的 bean 在实例化之前,会优先检测是否存在InstantiationAwareBeanPostProcessor接口这个特点,来实现

DatasourceLoader的优先加载


public class ClientBeanProcessor extends InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware {
    private ConfigurableListableBeanFactory beanFactory;
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
            throw new IllegalArgumentException(
                    "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
        }
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
        // 通过主动调用beanFactory#getBean来显示实例化目标bean
        DatasourceLoader propertyLoader = this.beanFactory.getBean(DatasourceLoader.class);
        System.out.println(propertyLoader);
    }
}
复制代码


上面的实现比较简单,借助beanFactory#getBean来手动触发 bean 的实例,通过实现BeanFactoryAware接口来获取BeanFactory,因为实现

InstantiationAwareBeanPostProcessor接口的类会优先于 Bean 被实例,以此来间接的达到我们的目的


关于上面这一套流程分析, 请关注微信公众号/个人博客站点,静待源码分析篇


接下来的问题就是如何让它生效了,我们这里使用 Import 注解来实现


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ClientAutoConfiguration.class, ClientBeanProcessor.class})
public @interface EnableOrderClient {
}
复制代码


请注意上面的注解中,导入上面的自动配置类,和ClientBeanProcessor,所以上一节中的spring.factories文件可以不需要哦


4. 测试


上面的主要流程就完事了,接下来就需要进入测试,我们新建一个 SpringBoot 项目,添加依赖


先加一个 demoBean

@Component
public class DemoBean {
    public DemoBean() {
        System.out.println("demo bean init!");
    }
    public void print() {
        System.out.println("print demo bean ");
    }
}
复制代码


然后是启动类, @EnableOrderClient这个注解必须得有哦

@EnableOrderClient
@SpringBootApplication
public class Application {
    public Application(DemoBean demoBean) {
        demoBean.print();
    }
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}
复制代码


在我们启动之前,请猜测一下,DemoBeanDatasourceLoader这里这两个 bean,谁会优先被实例化?


下面是输出结果

image.png


从上面的两个红框输出,可以知道我们的启动类指定方式依赖的 bean,并不一定会最先被加载哦


5. 小结


最后小结一下,本文提出了两种让 bean 优先加载的方式,一个是在启动类的构造方法中添加依赖,一个是借助InstantiationAwareBeanPostProcessorAdapter在 bean 实例化之前被创建的特点,结合BeanFactory来手动触发目标 bean 的创建

最后通过@Import注解让我们的BeanPostProcessorAdapter生效


有知道其他方式的大佬,请不吝赐教啊



相关文章
|
1月前
|
XML Java 数据库连接
spring boot 参数的过滤注解与实战
在Spring Boot应用中,对于入参的过滤,通常会涉及到对Web层的数据验证和处理。Spring Boot借助Spring框架提供了强大的验证框架支持,主要基于JSR-303/JSR-380(Bean Validation API)规范,以及Spring自身的@Valid或@Validated注解来实现请求参数的验证。以下是一些常见的使用案例来展示如何对参数进行过滤和验证。
29 1
|
1月前
|
XML Java 开发者
Spring Boot中的bean注入方式和原理
Spring Boot中的bean注入方式和原理
61 0
|
1月前
|
Java Spring 容器
【二十二】springboot整合拦截器实战并对比过滤器
【二十二】springboot整合拦截器实战并对比过滤器
33 0
|
1月前
|
人工智能 JSON 前端开发
【Spring boot实战】Springboot+对话ai模型整体框架+高并发线程机制处理优化+提示词工程效果展示(按照框架自己修改可对接市面上百分之99的模型)
【Spring boot实战】Springboot+对话ai模型整体框架+高并发线程机制处理优化+提示词工程效果展示(按照框架自己修改可对接市面上百分之99的模型)
|
7天前
|
Java Spring 容器
SpringBoot 使用Quartz执行定时任务对象时无法注入Bean问题
SpringBoot 使用Quartz执行定时任务对象时无法注入Bean问题
10 1
|
17天前
|
缓存 前端开发 Java
SpringBoot启动后加载初始化数据
SpringBoot启动后加载初始化数据
|
18天前
|
Java 测试技术 数据库
SpringBoot启动时设置不加载数据库
SpringBoot启动时设置不加载数据库
10 0
|
1月前
|
存储 缓存 安全
Spring Boot从入门到实战
本课程从SpringBoot的最基础的安装、配置开始到SpringBoot的日志管理、Web业务开发、数据存储、数据缓存,安全控制及相关企业级应用,全程案例贯穿,案例每一步的都会讲解实现思路,全程手敲代码实现。让你不仅能够掌SpringBoot的应用,还能了解背后的原理,学习完本课程后,能够让你动手独立完成一个中小型的SpringBoot Web应用开发。
19 1
Spring Boot从入门到实战
|
1月前
|
XML Java 数据格式
【springboot原理篇】Bean的加载方式,面试必看
【springboot原理篇】Bean的加载方式,面试必看
|
1月前
|
消息中间件 Java 关系型数据库
【二十】springboot整合ElasticSearch实战(万字篇)
【二十】springboot整合ElasticSearch实战(万字篇)
212 47