前回の記事(Springキャッシュ設定はキーの有効期限を指定します)は、Springキャッシュのカスタム有効期限を実装する方法について説明しましたが、それを使用した後は、不平を言う(酔っている)ほどエレガントではなく、追跡Aとしてメンタリティを(確信して)求めない(しない)プログラマーは、もっとうまくやるべきです。
再編成後の一般的な考え方は次のとおりです。
- 有効期限や時間単位など、アノテーション@TimeToLiveをカスタマイズします
- カスタムアノテーション@TimeToLiveをメソッドに追加します
- Spring AOPの原則を使用してアスペクトを定義すると、ポイントカットにはこのカスタムアノテーションメソッドが含まれます
- AOPアスペクトを介してcacheNameとMethodの間のマッピング関係を取得し、ローカルにキャッシュします
- カスタマイザーRedisCacheManagerをカスタマイズして、RedisCacheManagerのcomputeExpirationメソッドをオーバーライドします
コード部分は次のとおりです。
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* @Description Redis过期时间注解
* @Author huangd
* @Date 2020-01-02
**/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TimeToLive {
long ttl() default 60 * 60;
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class VehicleSeriesCacheService {
@Autowired
private XXXMapper xxxMapper;
@Cacheable(value = "ota.vehicle.series", key = "#id + ''")
**@TimeToLive(ttl = 20, timeUnit = TimeUnit.MINUTES)**
public VehicleSeries fetchById(Long id) {
return xxxMapper.fetch(id);
}
}
import com.mng.boot.common.annotation.TimeToLive;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.HashMap;
/**
* @Description Spring cache 过期时间AOP处理
* @Author huangd
* @Date 2020-01-02
**/
@Aspect
@Component
public class CacheTtlAdvice implements Ordered {
private HashMap<String, Method> cacheNameMethodCache = new HashMap<>(16);
/**
* 初始化过期时间配置
* @param joinPoint 切入点
*/
@Before("@annotation(com.xiaopeng.ota.mng.boot.common.annotation.TimeToLive)")
public void before(JoinPoint joinPoint){
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Method method = signature.getMethod();
TimeToLive timeToLive = AnnotationUtils.findAnnotation(method, TimeToLive.class);
Cacheable cacheable = AnnotationUtils.findAnnotation(method, Cacheable.class);
if(null != cacheable && cacheable.cacheNames().length > 0){
String[] cacheNames = cacheable.cacheNames();
if(timeToLive != null){
for(String cacheName : cacheNames){
if(!cacheNameMethodCache.containsKey(cacheName)){
cacheNameMethodCache.put(cacheName, method);
}
}
}
}
}
@Override
public int getOrder() {
return 1000;
}
public HashMap<String, Method> getCacheNameMethodCache() {
return cacheNameMethodCache;
}
}
@Slf4j
@ConditionalOnProperty(name = "spring.cache.type", havingValue = "redis")
@Service
public class CustomizerRedisCacheManager extends RedisCacheManager {
@Autowired
private CacheTtlAdvice cacheTtlAdvice;
public CustomizerRedisCacheManager(@Qualifier("redisTemplate") RedisOperations redisOperations) {
super(redisOperations);
}
@Override
public long computeExpiration(String name) {
long defaultExpiration = super.computeExpiration(name);
Method method = cacheTtlAdvice.getCacheNameMethodCache().get(name);
if (method != null) {
TimeToLive timeToLive = AnnotationUtils.findAnnotation(method, TimeToLive.class);
return timeToLive.timeUnit().toSeconds(timeToLive.ttl());
}
return defaultExpiration;
}
}
RedisTemplateのキーをStringRedisSerializerシリアル化モード(デフォルトのJDKシリアル化)として構成します
@Configuration
public class CustomCachingConfigurerSupport extends CachingConfigurerSupport {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
注:@CacheableもAOPを介して実装されるため、CacheTtlAdviceアスペクトクラスはOrderedインターフェイスを実装し、getOrderメソッドを実装する必要があります。これには優先度の問題が含まれます。getOrder値が小さいほど、最初の実行が行われます。デフォルトは(2147483647)です。 @EnableCachingの順序を表示できます。デフォルト値。カスタムアノテーションを最初に実行する必要があります。