Spring Boot 钩子全集实战(五):ApplicationContextInitializer详解

Spring Boot 钩子全集实战(五):ApplicationContextInitializer 详解

在上一篇中,我们深入剖析了 SpringApplicationRunListener.environmentPrepared() 这一关键扩展点,实现了环境合法性校验、启动上下文传递、多环境隔离兜底与配置动态增强等高阶能力。今天,我们将进入 Spring Boot 启动生命周期的下一个重要阶段------容器初始化前的关键扩展点ApplicationContextInitializer

一、什么是 ApplicationContextInitializer

ApplicationContextInitializer 是 Spring 框架原生提供的接口,在 Spring Boot 中被广泛用于 ApplicationContext 初始化完成前、刷新前(refresh 前)对容器进行定制化配置。其触发时机具有如下特征:

  • 触发时机ApplicationContext 已创建但尚未调用 refresh()
  • 核心状态Environment 已完全准备就绪,所有配置已生效;
  • 执行顺序 :晚于 environmentPrepared(),早于 BeanFactoryPostProcessor
  • 核心能力:可注册 Bean、修改 Bean 定义、设置容器属性、注入自定义逻辑。

核心价值 :在容器刷新前对 ApplicationContext 做最后的"预热"和"定制",是实现容器级扩展、安全加固、AOP 注入、监控埋点等场景的核心入口。

二、场景 :容器安全加固(禁止危险 Bean 注入)

业务痛点

微服务架构下,部分第三方库或内部组件可能通过自动配置注入高危 Bean(如 RuntimeScriptEngineJndiTemplate),存在远程代码执行(RCE)风险。即使通过配置禁用,仍可能因依赖传递或版本升级被意外激活。

解决方案

利用 ApplicationContextInitializer 在容器刷新前扫描并移除/替换高危 Bean 定义,从源头阻断安全隐患。

实现代码
复制代码
package com.example.demo.initializer;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.jndi.JndiTemplate;
import org.springframework.util.StringUtils;

import javax.naming.InitialContext;
import javax.script.ScriptEngine;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * 容器安全加固初始化器
 */
public class SecurityHardeningInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    // 高危类黑名单(可根据安全策略动态加载)
    private static final Set<String> DANGEROUS_BEAN_TYPES = new HashSet<>(Arrays.asList(
            "javax.script.ScriptEngine",
            "javax.naming.InitialContext",
            "org.springframework.jndi.JndiTemplate",
            "java.lang.Runtime"
    ));

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("[安全加固] 开始扫描并清理高危 Bean 定义");

        //构造高危类黑名单的类
        if (applicationContext instanceof BeanDefinitionRegistry registry) {
            BeanDefinitionBuilder builder1 = BeanDefinitionBuilder.genericBeanDefinition(ScriptEngine.class);
            BeanDefinitionBuilder builder2 = BeanDefinitionBuilder.genericBeanDefinition(InitialContext.class);
            BeanDefinitionBuilder builder3 = BeanDefinitionBuilder.genericBeanDefinition(JndiTemplate.class);
            BeanDefinitionBuilder builder4 = BeanDefinitionBuilder.genericBeanDefinition(Runtime.class);
            // 注册BeanDefinition
            registry.registerBeanDefinition("scriptEngine", builder1.getBeanDefinition());
            registry.registerBeanDefinition("initialContext", builder2.getBeanDefinition());
            registry.registerBeanDefinition("jndiTemplate", builder3.getBeanDefinition());
            registry.registerBeanDefinition("runtime", builder4.getBeanDefinition());
        }

        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
        String[] beanNames = beanFactory.getBeanDefinitionNames();

        for (String beanName : beanNames) {
            var beanDefinition = beanFactory.getBeanDefinition(beanName);
            String beanClassName = beanDefinition.getBeanClassName();

            if (StringUtils.hasText(beanClassName) && DANGEROUS_BEAN_TYPES.contains(beanClassName)) {
                System.err.printf("[安全加固] 检测到高危 Bean:%s(类型:%s),已移除!%n", beanName, beanClassName);
                beanFactory.removeBeanDefinition(beanName);
            }
        }

        // 禁止通过 @Import 或 ComponentScan 自动注册高危类
        disableDangerousScanning(applicationContext);

        System.out.println("[安全加固] 容器安全加固完成");
    }

    /**
     * 禁用高危类的组件扫描(防止后续自动注册)
     */
    private void disableDangerousScanning(ConfigurableApplicationContext context) {
        ClassPathScanningCandidateComponentProvider scanner =
                new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AssignableTypeFilter(Runtime.class));
        // 实际生产中可结合 AspectJ 或字节码工具拦截,此处仅作示意
        System.out.println("[安全加固] 已禁用高危类自动扫描(需配合编译期检查)");
    }
}
配置方式(任选其一)

方式 1:通过 spring.factories

复制代码
# resources/META-INF/spring.factories
org.springframework.context.ApplicationContextInitializer=\
com.example.demo.initializer.SecurityHardeningInitializer

方式 2:通过主类显式注册

复制代码
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(DemoApplication.class);
        app.addInitializers(new SecurityHardeningInitializer());
        app.run(args);
    }
}
输出结果
复制代码
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.8)

[安全加固] 开始扫描并清理高危 Bean 定义
[安全加固] 已禁用高危类自动扫描(需配合编译期检查)
[安全加固] 容器安全加固完成
2025-12-17T23:58:06.580+08:00  INFO 11158 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication using Java 21.0.9 with PID 11158 (/Users/wangmingfei/Documents/个人/05 java天梯之路/01 源码/03 每日打卡系列/daily-check-in/springboot钩子/demo/target/classes started by wangmingfei in /Users/wangmingfei/Documents/个人/05 java天梯之路/01 源码/03 每日打卡系列/daily-check-in/springboot钩子/demo)
2025-12-17T23:58:06.581+08:00  INFO 11158 --- [           main] com.example.demo.DemoApplication         : The following 1 profile is active: "prod"
[安全加固] 检测到高危 Bean:scriptEngine(类型:javax.script.ScriptEngine),已移除!
[安全加固] 检测到高危 Bean:initialContext(类型:javax.naming.InitialContext),已移除!
[安全加固] 检测到高危 Bean:jndiTemplate(类型:org.springframework.jndi.JndiTemplate),已移除!
[安全加固] 检测到高危 Bean:runtime(类型:java.lang.Runtime),已移除!
2025-12-17T23:58:06.876+08:00  INFO 11158 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2025-12-17T23:58:06.882+08:00  INFO 11158 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-12-17T23:58:06.883+08:00  INFO 11158 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.49]
2025-12-17T23:58:06.901+08:00  INFO 11158 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-12-17T23:58:06.901+08:00  INFO 11158 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 303 ms
2025-12-17T23:58:07.038+08:00  INFO 11158 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2025-12-17T23:58:07.042+08:00  INFO 11158 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.645 seconds (process running for 0.792)
生产价值
  • 从容器层面拦截高危 Bean,比运行时检测更早、更彻底;
  • 防止因依赖升级或配置错误引入安全漏洞;

三、总结

ApplicationContextInitializer 是 Spring Boot 启动流程中 连接环境准备与容器刷新的关键桥梁 ,它与 environmentPrepared() 形成"环境 → 容器"的完整扩展链路,是构建企业级高可靠、高安全、高可观测性应用不可或缺的一环。

📌 关注我,每天 5 分钟,带你从 Java 小白变身编程高手!

👉 点赞 + 关注 + 转发,让更多小伙伴一起进步!

👉 私信 "SpringBoot 钩子源码" 获取完整源码!

相关推荐
VX:Fegn08957 小时前
计算机毕业设计|基于ssm + vue超市管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·课程设计
徐徐同学7 小时前
cpolar为IT-Tools 解锁公网访问,远程开发再也不卡壳
java·开发语言·分布式
Mr.朱鹏8 小时前
Nginx路由转发案例实战
java·运维·spring boot·nginx·spring·intellij-idea·jetty
VX:Fegn08959 小时前
计算机毕业设计|基于springboot + vue酒店管理系统(源码+数据库+文档)
vue.js·spring boot·课程设计
白露与泡影10 小时前
2026版Java架构师面试题及答案整理汇总
java·开发语言
历程里程碑10 小时前
滑动窗口---- 无重复字符的最长子串
java·数据结构·c++·python·算法·leetcode·django
qq_2290580110 小时前
docker中检测进程的内存使用量
java·docker·容器
我真的是大笨蛋11 小时前
InnoDB行级锁解析
java·数据库·sql·mysql·性能优化·数据库开发
钦拆大仁11 小时前
Java设计模式-单例模式
java·单例模式·设计模式
小手cool11 小时前
在保持数组中对应元素(包括负数和正数)各自组内顺序不变的情况下,交换数组中对应的负数和正数元素
java