CacheAutoConfiguration.java 6.67 KB
package com.infoloop.tianting.cache;

import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.TimeUnit;

/**
 * 缓存自动配置类
 * 在Spring Boot启动时自动配置缓存相关组件
 */
@Slf4j
@Configuration
@EnableCaching
@ConditionalOnClass({Caffeine.class, StringRedisTemplate.class})
@EnableConfigurationProperties(CacheProperties.class)
@ConditionalOnProperty(name = "cache.enabled", havingValue = "true", matchIfMissing = true)
public class CacheAutoConfiguration {

    @Autowired
    private CacheProperties cacheProperties;

    /**
     * 配置Caffeine缓存管理器
     */
    @Bean
    @ConditionalOnMissingBean
    public com.github.benmanes.caffeine.cache.Cache<String, Object> caffeineCache() {
        Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
                .initialCapacity(cacheProperties.getCaffeine().getInitialCapacity())
                .maximumSize(cacheProperties.getCaffeine().getMaximumSize())
                .expireAfterWrite(cacheProperties.getCaffeine().getExpireAfterWrite(), TimeUnit.SECONDS);

        // 如果配置了访问后过期时间,则启用
        if (cacheProperties.getCaffeine().getExpireAfterAccess() > 0) {
            caffeine.expireAfterAccess(cacheProperties.getCaffeine().getExpireAfterAccess(), TimeUnit.SECONDS);
        }

        // 优化并发性能:使用调用线程执行过期任务,减少线程切换开销
        caffeine.executor(Runnable::run);

        if (cacheProperties.getCaffeine().isRecordStats()) {
            caffeine.recordStats();
        }

        log.info("Caffeine缓存配置完成: initialCapacity={}, maximumSize={}, expireAfterWrite={}s, expireAfterAccess={}s",
                cacheProperties.getCaffeine().getInitialCapacity(),
                cacheProperties.getCaffeine().getMaximumSize(),
                cacheProperties.getCaffeine().getExpireAfterWrite(),
                cacheProperties.getCaffeine().getExpireAfterAccess());

        return caffeine.build();
    }


    /**
     * 配置Caffeine缓存服务
     */
    @Bean("caffeineCacheService")
    @ConditionalOnMissingBean(name = "caffeineCacheService")
    public CaffeineCacheService caffeineCacheService(com.github.benmanes.caffeine.cache.Cache<String, Object> caffeineCache) {
        log.info("Caffeine缓存服务配置完成");
        return new CaffeineCacheService(caffeineCache);
    }

    /**
     * 配置ObjectMapper用于JSON序列化
     * 针对Lombok @Data类优化,支持标准的序列化/反序列化
     */
    @Bean("cacheObjectMapper")
    @ConditionalOnMissingBean(name = "cacheObjectMapper")
    public com.fasterxml.jackson.databind.ObjectMapper cacheObjectMapper() {
        com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
        mapper.findAndRegisterModules(); // 注册所有模块,包括Java 8时间模块
        
        // 基本配置
        mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.configure(com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        mapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        mapper.configure(com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_SELF_REFERENCES, false);
        
        // 为Lombok @Data类启用类型信息,这样可以正确反序列化
        mapper.activateDefaultTyping(
            com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator.builder()
                .allowIfSubType(Object.class)
                .build(),
            com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping.NON_FINAL,
            com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY
        );
        
        log.info("缓存专用ObjectMapper配置完成(优化支持Lombok @Data类)");
        return mapper;
    }

    /**
     * 配置Redis缓存服务
     */
    @Bean("redisCacheService")
    @ConditionalOnMissingBean(name = "redisCacheService")
    public RedisCacheService redisCacheService(StringRedisTemplate redisTemplate, 
            @org.springframework.beans.factory.annotation.Qualifier("cacheObjectMapper") 
            com.fasterxml.jackson.databind.ObjectMapper objectMapper) {
        log.info("Redis缓存服务配置完成");
        return new RedisCacheService(redisTemplate, objectMapper);
    }

    /**
     * 配置缓存服务(主要使用多级缓存)
     */
    @Bean("multiLevelCacheService")
    @ConditionalOnMissingBean(name = "multiLevelCacheService")
    public MultiLevelCacheService multiLevelCacheService(
            CaffeineCacheService caffeineCacheService,
            RedisCacheService redisCacheService,
            CacheProperties cacheProperties) {
        log.info("多级缓存服务配置完成");
        return new MultiLevelCacheService(caffeineCacheService, redisCacheService, cacheProperties);
    }

    /**
     * 配置Spring Cache管理器 - 桥接到多级缓存服务
     */
    @Bean
    @Primary
    @ConditionalOnMissingBean(CacheManager.class)
    public CacheManager cacheManager(
            @org.springframework.beans.factory.annotation.Qualifier("multiLevelCacheService") 
            CacheService cacheService,
            CacheProperties cacheProperties) {
        log.info("Spring Cache管理器配置完成,桥接到多级缓存服务");
        return new MultiLevelCacheManager(cacheService, cacheProperties);
    }

    /**
     * 配置缓存健康检查
     */
    @Bean
    @ConditionalOnMissingBean
    public CacheHealthIndicator cacheHealthIndicator() {
        log.info("缓存健康检查配置完成");
        return new CacheHealthIndicator();
    }

    /**
     * 配置缓存管理控制器
     */
    @Bean
    @ConditionalOnMissingBean
    public CacheManagementController cacheManagementController() {
        log.info("缓存管理控制器配置完成");
        return new CacheManagementController();
    }
}