容器启动时,将缓存加载到类的静态属性

该博客讨论了一个Java应用程序在启动时如何初始化静态缓存,包括表格类型、协调制度编码、原产国关系和包装种类等数据。使用StringRedisTemplate从Redis获取并解析数据填充到内存中的Map。文章指出了这种缓存策略的缺点,即需要维护本地缓存且数据存储在元空间,可能需要关注JVM的元空间设置。作者提供了元空间大小调整的建议,并提到了Lucene库的API用于计算对象占用的内存空间。

代码实现:

@Data
public class HkBaseCache {

    /**
     * 表格类型List
     */
    public static List<BaseCustomsClearanceParameters> tableTypeList = new ArrayList<>();
    /**
     * 协调制度编码Map   key:协调制度编号   value:协调制度对象
     */
    public static  Map<String, BaseHarmonizedSystem> harmonizedMap = new ConcurrentHashMap<>();
    /**
     * 原产国与香港来源国关系Map  key:原产国代码 value:原产国与香港来源国关系
     */
    public static  Map<String, CountryTurnRelation> countryTurnRelationMap = new ConcurrentHashMap<>();
    /**
     * 包装种类Map  key:包装种类代码 value:包装种类信息
     */
    public static  Map<String, BaseCustomsClearanceParameters> packTypeMap = new HashMap<>();

    /**
     * 初始化
     *
     * @param  stringRedisTemplate string序列化的
     * @author  wrx
     * @date  2021/12/25 11:00
     */
    public static HkBaseCache init(StringRedisTemplate stringRedisTemplate){
        HkBaseCache hkBaseCache = new HkBaseCache();
        hkBaseCache.loadCache(stringRedisTemplate);
        return hkBaseCache;
    }

    /**
     * 加载缓存
     *
     * @param  stringRedisTemplate string序列化的
     * @author  wrx
     * @date  2021/12/25 11:00
     */
    private void loadCache(StringRedisTemplate stringRedisTemplate){
        // 设置缓存中的表格类型
        String tableTypeJson = stringRedisTemplate.opsForValue().get(RedisConstants.TZ_REDIS_BASE_CUSTOMS_CLEARANCE_PARAMETERS + BaseCustomsClearanceParametersEnum.TABLE_TYPE.getCode());
        tableTypeList.clear();
        tableTypeList = JSONUtil.toList(JSONUtil.parseArray(tableTypeJson), BaseCustomsClearanceParameters.class);
        // 设置缓存中的协调制度编号
        harmonizedMap.clear();
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(RedisConstants.TZ_REDIS_BASE_HARMONIZED_SYSTEM);
        entries.entrySet().parallelStream().forEach(entry-> harmonizedMap.put(StrUtil.toString(entry.getKey()),JSONUtil.toBean(StrUtil.toString(entry.getValue()),BaseHarmonizedSystem.class)));
        // 设置缓存中的原产国关系
        countryTurnRelationMap.clear();
        Map<Object, Object> entries2 = stringRedisTemplate.opsForHash().entries(RedisConstants.TZ_REDIS_COUNTRY_TURN_RELATION);
        entries2.entrySet().parallelStream().forEach(entry-> countryTurnRelationMap.put(StrUtil.toString(entry.getKey()),JSONUtil.toBean(StrUtil.toString(entry.getValue()),CountryTurnRelation.class)));
        // 设置缓存中的包装种类
        packTypeMap.clear();
        String packTypeJson = stringRedisTemplate.opsForValue().get(RedisConstants.TZ_REDIS_BASE_CUSTOMS_CLEARANCE_PARAMETERS + BaseCustomsClearanceParametersEnum.PACK_TYPE.getCode());
        List<BaseCustomsClearanceParameters> packTypeList = JSONUtil.toList(JSONUtil.parseArray(packTypeJson), BaseCustomsClearanceParameters.class);
        packTypeMap = packTypeList.stream().collect(Collectors.toMap(BaseCustomsClearanceParameters::getCode, obj -> obj));
    }
  • 容器启动时,初始化
@SpringCloudApplication
@Slf4j
public class XXXApplication  implements CommandLineRunner {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    public static void main(String[] args) {
        SpringApplication.run(XXXApplication.class,args);
    }

    @Override
    public void run(String... args) throws Exception {
        log.info("---------初始化工作 start----------");
        stringRedisTemplate.init();
        log.info("---------初始化工作 end----------");
    }
}

缺点:

  • 需要维护本地缓存,所以本地缓存的数据尽量是恒古不变的
  • 数据存储在元空间,注意自己的JVM设置是否合理

关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N,对于64位JVM来说,元空间的默认初始大小是21MB,默认的元空间的最大值是无限。
-XX:MaxMetaspaceSize: 设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。
-XX:MetaspaceSize: 指定元空间的初始空间大小, 以字节为单位,默认是21M,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。
由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,一般我会将这两个值都设置为256M。

如何判断对象占用的空间?

  • pom依赖
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-core</artifactId>
    <version>8.3.0</version>
</dependency>
  • 调用api计算
long byteCount = RamUsageEstimator.shallowSizeOf(obj);

得到的是字节数

<think> 嗯,用户的问题是关于在静态方法中加载Spring Bean的方法和原理。根据提供的引用内容,可以看到几种实现方式:一种是使用静态变量结合构造器注入(引用3),另一种是通过自定义SpringContextHolder工具(引用4)。我需要整理这两种方法的原理和步骤。 用户可能正在开发Spring Boot应用,遇到了静态方法无法直接注入Bean的问题。ta的真实需求应该是解决静态环境下获取依赖对象的难题,同时理解背后的机制。这属于Spring框架的高级应用场景,通常出现在工具或遗留代码改造中。 从引用1和2可以提取基础知识:Bean的生命周期分为实例化、属性赋值(依赖注入)、初始化等阶段。静态方法无法使用依赖注入的根本原因在于,它不通过Spring容器管理对象,自然无法享受依赖注入特性。 设计回答时,我会先解释根本原因,再分两个主流方案: 第一种方案(构造器注入静态变量)的核心原理是利用Spring初始化Controller时,通过构造器将Bean实例赋给静态变量。需要强调单例风险(如并发修改静态变量)。 第二种方案(上下文工具)的关键是容器启动时初始化SpringContextHolder,将ApplicationContext保存在静态变量中。这里要重点说明@Component和ApplicationContextAware的作用机制。 注意到用户提供的引用3中存在严重问题:该示例用Controller构造器给静态变量赋值,但Controller默认是单例的,如果多线程并发调用getStatic()方法,静态变量可能被其他Controller实例覆盖。这是个需要警告的陷阱。 最后考虑补充注意事项部分,包括线程安全、启动顺序、设计模式等实用建议。这些问题开发者实际使用时很容易踩坑,比如容器未初始化就调用工具的情况就很常见。 </think> 在静态方法中加载Spring Bean的核心原理是**绕过依赖注入机制,主动从Spring容器获取Bean实例**。以下是具体方法和实现原理: --- ### 🔧 一、实现方法 #### 1. **构造器注入 + 静态变量**(适用于单例Bean)[^3] ```java @RestController public class TestController { private static UserUtil userUtil; // 静态变量持有Bean // 构造器注入(Spring自动调用) public TestController(UserUtil userUtil) { TestController.userUtil = userUtil; // 将Bean赋给静态变量 } public static void getStatic() { System.out.println("用户名:" + userUtil.getUsername()); } } ``` **原理**: - Spring在**属性赋值阶段**(`populateBean()`)通过构造器注入Bean实例[^1]。 - 构造器执行时,将注入的Bean赋值给静态变量。 - ⚠️ 风险:若Controller被销毁重建,静态变量可能失效;非线程安全。 --- #### 2. **Spring上下文工具**(推荐方案)[^4] **步骤一:创建工具** ```java @Component public class SpringContextHolder implements ApplicationContextAware { private static ApplicationContext context; // 静态容器实例 @Override public void setApplicationContext(ApplicationContext ctx) { context = ctx; // Spring启动时自动注入上下文 } public static <T> T getBean(Class<T> beanClass) { return context.getBean(beanClass); } } ``` **步骤二:XML/注解配置** ```xml <bean id="springContextHolder" class="com.example.SpringContextHolder"/> ``` 或使用 `@ComponentScan` 自动注册。 **步骤三:静态方法调用** ```java public static void getStatic() { UserUtil userUtil = SpringContextHolder.getBean(UserUtil.class); System.out.println("用户名:" + userUtil.getUsername()); } ``` **原理**: - Spring容器初始化时,通过 `ApplicationContextAware` 接口**自动注入**`ApplicationContext`实例[^2]。 - 工具容器实例保存在**静态变量**中,提供全局访问入口。 - 符合Spring生命周期:容器启动 → 注册Bean → 初始化工具 → 存储上下文。 --- ### ️ 二、关键技术原理 1. **打破静态限制** - 静态方法不能依赖注入(对象未实例化),但可通过**持有容器引用**主动查找Bean。 - 本质:通过Spring的扩展接口(如 `ApplicationContextAware`)获取容器控制权。 2. **生命周期时机** - 工具的 `setApplicationContext()` 方法在Bean初始化阶段由Spring**自动调用**[^1]。 - 确保容器启动完成后静态变量才可用。 3. **容器与解耦** - 不依赖具体Bean实现,通过接口或名动态获取。 - 符合控制反转(IoC)思想:容器管理Bean,工具仅提供访问入口。 --- ### ️ 三、注意事项 1. **线程安全问题** - 确保工具中的 `ApplicationContext` 引用不可变(`final`修饰)。 - 避免在工具缓存非线程安全的Bean。 2. **启动顺序依赖** - 静态方法需在Spring容器**初始化完成后调用**,否则 `context` 为 `null`。 3. **设计规范** - 优先通过依赖注入获取Bean,静态加载仅作为**备选方案**(如工具静态Util方法)。 - 滥用会破坏Spring的依赖管理优势。 --- ### 💎 总结 | **方法** | **适用场景** | **优势** | **风险** | |------------------------|----------------------|--------------------------|------------------------| | 构造器注入+静态变量 | 简单单例场景 | 实现简单 | 生命周期管理困难 | | Spring上下文工具 | 复杂系统、通用工具 | 解耦、灵活、符合Spring规范 | 需确保容器初始化完成 | > 推荐使用 **SpringContextHolder工具**,它是Spring生态中的通用模式(如 `SpringUtils`),兼顾安全性与扩展性[^4]。 --- ### ❓相关问题 1. 如何在非Spring管理的(如工具)中安全获取Bean? 2. `ApplicationContextAware` 接口的工作原理是什么?它与Bean生命周期有何关联? 3. 静态方法加载Bean会破坏Spring的依赖注入原则吗?如何规避设计风险? 4. 在多模块Spring Boot项目中,如何确保上下文工具被正确初始化? [^1]: Spring容器属性赋值阶段(`populateBean()`)处理依赖注入,确保Bean的初始化正确完成。 [^2]: `BeanDefinition`是Spring容器内部管理Bean的元数据模型,决定了Bean的创建和装配方式。 [^3]: 通过构造器将注入的Bean赋值给静态变量,使静态方法间接持有Bean引用。 [^4]: 自定义`SpringContextHolder`实现`ApplicationContextAware`接口,提供静态访问容器的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值