静态块适用于类首次主动使用时初始化全局、不可变、不依赖运行时上下文的状态,如配置常量、预热资源、只读元数据和系统钩子;避免放入租户ID、Spring Bean等动态依赖项。

静态块适合管理业务中真正全局、只初始化一次、且不依赖运行时上下文的状态变量,关键在于“类首次主动使用时就准备好”,而不是等第一次调用才加载。
明确哪些状态适合放进静态块
不是所有“看起来全局”的变量都该放静态块。真正适合的有:
- 配置常量:如数据库连接串、超时毫秒数、环境标识(static final String ENV = "prod")
- 预热资源:单例服务实例(DatabaseManager.INSTANCE)、共享线程池、预编译正则表达式
- 只读元数据:枚举映射表(Map
STATUS_MAP )、国家代码字典 - 系统级钩子:向监控注册指标、添加 JVM 关闭清理逻辑(Runtime.getRuntime().addShutdownHook())
避免放入:需用户登录后才确定的租户ID、依赖 Spring 上下文的 Bean、可能动态刷新的配置(应改用 @RefreshScope 或 ConfigurableBeanFactory)。
控制加载时机:让类在需要时才初始化
静态块不会在类被编译或类文件存在时执行,而是在 JVM 判定该类被“主动使用”时触发。常见触发点包括:
- 首次 new A() 创建实例(注意:只是声明 A a; 不触发)
- 首次访问 A.CONSTANT 或调用 A.doSomething()
- 子类初始化前,父类静态块一定已执行(可利用此顺序组织依赖)
- 反射调用 Class.forName("A")(显式强制加载)
例如:把数据库连接池初始化放在 DbPool.class 的静态块里,只要业务代码第一次调用 DbPool.getConnection(),池就自动建好;没调用过,就不会加载、不占资源。
实战写法要点与避坑
写静态块不是堆代码,要兼顾可读、健壮和可观测:
- 用 static final 字段接收结果,确保不可变、线程安全
- 初始化逻辑尽量轻量:配置加载建议用 getResourceAsStream,别做网络请求或大文件解析
- 必须包裹 try-catch:IOException、NullPointerException 等未捕获异常会导致 NoClassDefFoundError,后续整个类不可用
- 多个静态块按源码顺序执行,静态变量赋值语句也参与排序——把依赖项写在前面
- 日志输出建议用 Logger.info("Xxx initialized"),方便启动期排查是否执行成功
示例片段:
public class BizConfig {public static final int MAX_RETRY;
public static final Logger LOG;
static {
LOG = LoggerFactory.getLogger(BizConfig.class);
try {
MAX_RETRY = Integer.parseInt(System.getProperty("biz.max.retry", "3"));
LOG.info("BizConfig loaded: MAX_RETRY={}", MAX_RETRY);
} catch (NumberFormatException e) {
LOG.error("Invalid biz.max.retry, fallback to 3", e);
MAX_RETRY = 3;
}
}
替代方案对比:什么情况下不该用静态块
静态块不是银弹。以下场景建议换方式:
- 需要延迟到 Spring 容器启动后再初始化 → 改用 @PostConstruct 或 @Bean(initMethod = "...")
- 单例对象创建较重(如含外部连接),又不想应用一启动就加载 → 改用静态内部类(Initialization-on-demand holder)
- 配置需支持运行时刷新(如 Nacos、Apollo)→ 避免硬编码静态块,改用监听回调更新 volatile 字段
- 跨模块强依赖(如 A 类静态块里 new B(),但 B 尚未加载)→ 易引发 ClassNotFoundException,应解耦或改用服务发现










