SpringBoot如何让业务Bean优先于其他Bean加载

这篇具有很好参考价值的文章主要介绍了SpringBoot如何让业务Bean优先于其他Bean加载。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本博客原文地址:https://ntopic.cn/p/2023090901/

源代码先行:

  • Gitee本文介绍的完整仓库:https://gitee.com/obullxl/ntopic-boot
  • GitHub本文介绍的完整仓库:https://github.com/obullxl/ntopic-boot

背景介绍

今天走读一个应用程序代码,发现一个有趣的现象:有多个不同的业务Bean中均依赖了一个参数工具类ParamUtils(即:@Autowired ParamUtils paramUtis),ParamUtils依赖了ParamDAO Bean用于从DB中获取参数;为了便于ParamUtils使用,工具类全部都是static静态方法,也就是说,业务Bean仅仅增加Autowired依赖,在实际调用时还是直接使用的ParamUtils类静态方法。那个Autowired注入ParamUtils的依赖看起来是无用代码,但是其实还不能去掉。

代码业务这么写的目的其实很好理解:因为ParamUtils依赖了DAO Bean,增加依赖是保障ParamUtils的类静态方法在调用时已经被SpringBoot初始化了。那么,有没有更优雅的办法,能让业务代码更优雅更安全的使用ParamUtils工具类呢?

思路分析

ParamUtils业务Bean,比其他的业务Bean提前初始化,基本思路如下:

第一思路:采用优先级Ordered注解(类:org.springframework.core.Ordered),但是不可行,因为该注解主要是用于控制Spring自身Bean的初始化顺序,如Listener/Filter等。

第二思路:采用Bean依赖DependsOn注解(类:org.springframework.context.annotation.DependsOn),该方法可行,它和Autowired注解一致,也是表明Bean之间依赖,但是没有从本质上解决问题。

第三思路:手工注册Bean让Spring优先初始化,查看SpringApplication类代码,发现里面有个addInitializers(ApplicationContextInitializer<?>... initializers)方法,可以让业务在ApplicationContext初始化时initialize(C applicationContext)基于Context做一些事情。那么可不可以在这个地方,能手工注册业务Bean呢?

代码实现和验证

代码分为3部分:ParamDAO业务Bean访问DB,ParamUtils参数工具类依赖ParamDAO,RestController测试类使用参数工具类。

为了阅读方便,以下展示的代码均只有主体部分,完整的代码注释和代码内容,请下载本工程仓库。

ParamDAO业务Bean

为了测试简便,本工程不依赖MySQL数据库,我们还是采用SQLite,源文件就在代码根目录下,clone本仓库后即可执行运行:

SQLite数据表准备

首先新建一张参数表(nt_param),并且插入一些数据。为了尽快验证我们的思路,其他的数据新增、修改和删除等就不做特别的验证了。

--
-- 参数表
--
CREATE TABLE nt_param
(
    id          bigint unsigned NOT NULL auto_increment,
    category    varchar(64) NOT NULL,
    module      varchar(64) NOT NULL,
    name        varchar(64) NOT NULL,
    content     varchar(4096) DEFAULT '',
    create_time timestamp,
    modify_time timestamp,
    PRIMARY KEY (id),
    UNIQUE (category, module, name)
);

--
-- 插入数据
--
INSERT INTO nt_param (category, module, name, content, create_time, modify_time)
VALUES ('CONFIG', 'USER', 'minAge', '18', strftime('%Y-%m-%d %H:%M:%f', 'now'), strftime('%Y-%m-%d %H:%M:%f', 'now')),
       ('CONFIG', 'USER', 'maxAge', '60', strftime('%Y-%m-%d %H:%M:%f', 'now'), strftime('%Y-%m-%d %H:%M:%f', 'now'));

ParamDAO数据查询

NTParamDAO为普通的Spring Bean(ID为:ntParamDAO

@Repository("ntParamDAO")
public interface NTParamDAO {

    @Select("SELECT * FROM nt_param WHERE category=#{category,jdbcType=VARCHAR} AND module=#{module,jdbcType=VARCHAR}")
    List<NTParamDO> selectByModule(@Param("category") String category, @Param("module") String module);

}

ParamUtils工具类定义和使用

ParamUtils工具类定义:非Spring Bean

ParamUtils是静态工具类,依赖了ParamDAO Spring Bean,并且ParamUtils并不是Spring Bean:

// @Component("ntParamUtils") SpringBoot优先初始化本类,因此无需增加注解
public class NTParamUtils {
    private static final Logger LOGGER = LoggerFactory.getLogger(LogConstants.DAS);

    /**
     * 系统参数DAO
     */
    private static NTParamDAO NT_PARAM_DAO;

    /**
     * 依赖注入
     */
    public NTParamUtils(@Qualifier("ntParamDAO") NTParamDAO ntParamDAO) {
        Assert.notNull(ntParamDAO, "NTParamDAO注入为NULL.");
        NT_PARAM_DAO = ntParamDAO;

        // 打印日志
        LOGGER.info("{}:初始化完成.", this.getClass().getName());
    }

    public static List<NTParamDO> findList(String category, String module) {
        Assert.hasText(category, "分类参数为空");
        Assert.hasText(module, "模块参数为空");
        return NT_PARAM_DAO.selectByModule(category, module);
    }

}

ParamUtils工具类使用:普通Spring Bean

NTUserServiceImpl是一个普通的Spring Bean,它没有显示依赖ParamUtils,而是直接使用它:

@Component("ntUserService")
public final class NTUserServiceImpl implements NTUserService {
    private static final Logger LOGGER = LoggerFactory.getLogger(LogConstants.BIZ);

    @Autowired
    public NTUserServiceImpl() {
        // 打印日志
        LOGGER.info("{}:初始化完成.", this.getClass().getName());
    }

    /**
     * 获取用户模块参数
     */
    @Override
    public List<NTParamDO> findUserParamList() {
        return NTParamUtils.findList("CONFIG", "USER");
    }
}

SpringBoot优先初始化设置

两个关键点:

  1. ApplicationContextInitializer类:提供Context初始化入口,业务逻辑可以通过此次注入。
  2. BeanDefinitionRegistryPostProcessor类:Spring Bean收集完成后,但还没有初始化之前入口,我们的关键就在这里定义ParamUtils Bean,并且Bean定义为RootBeanDefinition保障提前初始化。

Context自定义初始化:手工注册ParamUtils Bean

public class NTApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, BeanDefinitionRegistryPostProcessor {
    
    /**
     * Context初始化,给业务逻辑初始化提供了机会
     */
    @Override
    public void initialize(ConfigurableApplicationContext context) {
        // 注册Bean上下文初始化后处理器,用于手工注册Bean
        context.addBeanFactoryPostProcessor(this);
    }

    /**
     * 手工注册ParamUtils工具类,并且是RootBean定义,保障优先初始化,下面会详细分析
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 在ConfigurationClassPostProcessor前手动注册Bean,保障优先于其他Bean初始化
        registry.registerBeanDefinition("ntParamUtils", new RootBeanDefinition(NTParamUtils.class));
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }
}

SpringBoot启动类增加自定义初始化器

原来的方法:SpringApplication.run(NTBootApplication.class, args);

@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
@MapperScan(basePackages = "cn.ntopic.das..**.dao", sqlSessionFactoryRef = "ntSqlSessionFactory")
public class NTBootApplication {

    /**
     * SpringBoot启动
     */
    public static void main(String[] args) {
        // 注册自定义处理器
        SpringApplication application = new SpringApplication(NTBootApplication.class);
        application.addInitializers(new NTApplicationContextInitializer());

        // SpringBoot启动
        application.run(args);
    }
}

至此,业务Bean提前初始化的整个代码完毕,下面进行验证!

ParamUtils初始化验证(符合预期)

我们分表从SpringBoot的启动日志实际使用2个方面来验证我们的设计思路:

SpringBoot启动日志:符合预期

第21行第22行日志,可以看到,ParamUtils优于其他Bean完成初始化:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.3)

2023-09-09 11:40:55,607  INFO (StartupInfoLogger.java:55)- Starting NTBootApplication using Java 1.8.0_281 on OXL-MacBook.local with PID 1371 (/Users/obullxl/CodeSpace/ntopic-boot/ntopic/target/classes started by obullxl in /Users/obullxl/CodeSpace/ntopic-boot)
2023-09-09 11:40:55,612  INFO (SpringApplication.java:659)- No active profile set, falling back to default profiles: default
2023-09-09 11:40:55,692  INFO (DeferredLog.java:255)- Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2023-09-09 11:40:55,693  INFO (DeferredLog.java:255)- For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2023-09-09 11:40:56,834  INFO (TomcatWebServer.java:108)- Tomcat initialized with port(s): 8088 (http)
2023-09-09 11:40:56,842  INFO (DirectJDKLog.java:173)- Initializing ProtocolHandler ["http-nio-8088"]
2023-09-09 11:40:56,842  INFO (DirectJDKLog.java:173)- Starting service [Tomcat]
2023-09-09 11:40:56,842  INFO (DirectJDKLog.java:173)- Starting Servlet engine: [Apache Tomcat/9.0.50]
2023-09-09 11:40:56,901  INFO (DirectJDKLog.java:173)- Initializing Spring embedded WebApplicationContext
2023-09-09 11:40:56,901  INFO (ServletWebServerApplicationContext.java:290)- Root WebApplicationContext: initialization completed in 1208 ms
2023-09-09 11:40:57,043 ERROR (Log4j2Impl.java:58)- testWhileIdle is true, validationQuery not set
2023-09-09 11:40:57,051  INFO (Log4j2Impl.java:106)- {dataSource-1} inited
2023-09-09 11:40:57,127  INFO (NTParamUtils.java:39)- cn.ntopic.NTParamUtils:初始化完成.
2023-09-09 11:40:57,160  INFO (NTUserServiceImpl.java:78)- cn.ntopic.service.impl.NTUserServiceImpl:初始化完成.
2023-09-09 11:40:57,170  INFO (NTExecutorConfig.java:65)- start ntThreadPool
2023-09-09 11:40:57,563  INFO (OptionalLiveReloadServer.java:58)- LiveReload server is running on port 35729
2023-09-09 11:40:57,582  INFO (DirectJDKLog.java:173)- Starting ProtocolHandler ["http-nio-8088"]
2023-09-09 11:40:57,600  INFO (TomcatWebServer.java:220)- Tomcat started on port(s): 8088 (http) with context path ''
2023-09-09 11:40:57,610  INFO (StartupInfoLogger.java:61)- Started NTBootApplication in 2.363 seconds (JVM running for 3.091)

RestController验证:符合预期

@RestController
public class NTParamAct {

    private final NTUserService ntUserService;

    public NTParamAct(@Qualifier("ntUserService") NTUserService ntUserService) {
        this.ntUserService = ntUserService;
    }

    @RequestMapping("/param")
    public List<NTParamDO> paramList() {
        return this.ntUserService.findUserParamList();
    }

}

打开浏览器,访问:http://localhost:8088/param

可以看到,参数数据被查询并输出:

[
    {
        "id": 3,
        "category": "CONFIG",
        "module": "USER",
        "name": "maxAge",
        "content": "60",
        "createTime": "2023-09-08T18:30:20.818+00:00",
        "modifyTime": "2023-09-08T18:30:20.818+00:00"
    },
    {
        "id": 2,
        "category": "CONFIG",
        "module": "USER",
        "name": "minAge",
        "content": "18",
        "createTime": "2023-09-08T18:30:20.818+00:00",
        "modifyTime": "2023-09-08T18:30:20.818+00:00"
    }
]

SpringBoot实现分析

SpringBoot启动的代码入口:

public static void main(String[] args) {
    // 注册自定义处理器
    SpringApplication application = new SpringApplication(NTBootApplication.class);
    application.addInitializers(new NTApplicationContextInitializer());

    // SpringBoot启动
    application.run(args);
}

有几个非常核心的点,基本调用链路:

  1. SpringApplication类:run() -> prepareContext() -> applyInitializers(本方法:调用自定义NTApplicationContextInitializer上下文器)
  2. SpringApplication类:run() -> refreshContext() -> refresh(ConfigurableApplicationContext)
  3. ConfigurableApplicationContext类:AbstractApplicationContext.refresh() -> finishBeanFactoryInitialization(ConfigurableListableBeanFactory)
  4. ConfigurableListableBeanFactory类,关键代码都在这里:preInstantiateSingletons()
  • beanDefinitionNames属性:Spring收集到的所有Bean定义,包括Repository注解、Component注解和我们手工定义的Bean
  • 遍历beanDefinitionNames的时候,优先RootBeanDefinition初始化,手工定义的ParamUtils也是该类型

至此,问题解决,能解决的原因也搞清楚了!文章来源地址https://www.toymoban.com/news/detail-702590.html

到了这里,关于SpringBoot如何让业务Bean优先于其他Bean加载的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • springboot es索引@Document通过动态加载bean实现动态改变

    需求:elasticsearch中,每天的数据放在当天的索引中,如2022.08.23。现有一个服务,定时从es中将数据拉取到mysql数据库中存储。 更新:可以通过SPEL进行动态注入 ========================================================================= 考虑的点:java中es映射实体需要@Document注解指定连接的elas

    2024年02月15日
    浏览(8)
  • SpringBoot3.0整合RocketMQ时出现未能加载bean文件

    SpringBoot3.0整合RocketMQ时出现未能加载bean文件

    问题 APPLICATION FAILED TO START Description: Field rocketMQTemplate in com.spt.message.service.MqProducerService required a bean of type ‘org.apache.rocketmq.spring.core.RocketMQTemplate’ that could not be found. The injection point has the following annotations: - @org.springframework.beans.factory.annotation.Autowired(required=true) Action: Consider

    2024年02月12日
    浏览(10)
  • 超越竞争:Spring Boot如何在加载bean时优先选择我?

    超越竞争:Spring Boot如何在加载bean时优先选择我?

    🏅 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! Spring Boot 是当前业界最受欢迎和广泛使用的 Java Web 应用开发框架之一。在 Spring Boot 应用中,bean 是通过自动配置进行装载的,因为其按照约定顺序位置,Spring Boot 希望尽可能提供正确的自动配置,在应用运行时重写或自

    2023年04月08日
    浏览(9)
  • 如何获取springboot中所有的bean

    这段代码是一个使用 Spring Framework 的依赖注入(DI)功能的示例。它用 @Autowired 注解将一个类型为 MapString, Object 的变量声明为一个由 Spring 容器管理的 bean,并初始化为一个线程安全的 ConcurrentMap 实现对象。 从代码中可以看出以下几点: @Autowired :这是 Spring Framework 提供的一

    2024年02月09日
    浏览(5)
  • SpringBoot复习:(20)如何把bean手动注册到容器?

    可以通过实现BeanDefinitionRegistryPostProcessor接口,它的父接口是BeanFactoryPostProcessor. 步骤: 一、自定义一个组件类: 二、定义类实现BeanDefinitionRegistryPostProcessor: 通过@Component注解,Spring就能够扫描到MyBeanDefinitionRegistryPostProcessor,也就能够把MusicService这个组件注册到容器。 三、可

    2024年02月14日
    浏览(8)
  • [SpringBoot]如何在一个普通类中获取一个Bean

    [SpringBoot]如何在一个普通类中获取一个Bean

    最近在项目中出现了一个这种情况:我一顿操作猛如虎的写了好几个设计模式,然后在设计模式中的类中想将数据插入数据库,因此调用Mapper持久层,但是数据怎么都写不进去,在我一顿操作猛如虎的查找下,发现在普通类中用 @Autowired 注入的Bean是Null,也就是说注入失败了

    2024年01月19日
    浏览(14)
  • 基于java个人博客系统(springboot框架)开题答辩常规问题和如何回答

     博主介绍 :黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程,免费 项目配有对应开发文档、开题报告、任务书、

    2024年01月18日
    浏览(8)
  • 说说如何在SpringBoot中启动加载全局变量

    需要加载全局变量的情况很常见,如取通用的系统初始化配置等 Spring Boot 可以通过实现 CommandLineRunner 或 ApplicationRunner 接口,让某些代码在 Spring Boot 应用启动之后执行。因此,在启动时从数据库加载全局变量,可以在这些接口的 run() 方法中实现 在这个类中,全局变量被定义

    2024年02月16日
    浏览(9)
  • 企业级 Selenium 刷 其他平台 博客访问(学习使用 )

    企业级 Selenium 刷 其他平台 博客访问(学习使用 )

    今天我们来学习一下 Selenium , 来统计 博客的访问量 Selenium 介绍 Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera,Edge等。这个工具的主要功能包

    2023年04月24日
    浏览(13)
  • SpringBoot 更新业务场景下,如何区分null是清空属性值 还是null为vo属性默认值?

    SpringBoot 更新业务场景下,如何区分null是清空属性值 还是null为vo属性默认值?

    值为null 未传递此属性 所以此时如何区分null 时传递进来的的null,还是属性的默认值null? 引入过滤器,中间截获 requestBodyData 并保存到 HttpServletRequest ,业务层从 HttpServletRequest 获取到 requestBodyData 辅助判断此属性为 未传递 还是 值为null 自定义过滤器截获 requestBodyData 并保存到

    2024年01月18日
    浏览(10)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包