El artículo anterior ( la configuración de la caché de Spring especifica el tiempo de caducidad de la clave ) ha mencionado cómo implementar el tiempo de caducidad personalizado de la caché de primavera, pero después de usarlo, no es lo suficientemente elegante como para quejarse (también borracho), y sí, como una persecución A el programador que (no) busca (convencido) mentalidad debería hacerlo mejor.
Después de la reorganización, la idea general es la siguiente:
- Personalice una anotación @TimeToLive, incluido el tiempo de vencimiento y la unidad de tiempo
- Agregue una anotación personalizada @TimeToLive al método
- Defina un aspecto, utilizando el principio de primavera AOP, pointcut contiene este método de anotación personalizado
- Obtenga la relación de mapeo entre cacheName y Method a través del aspecto AOP, y almacénelo localmente
- Personalice el personalizador RedisCacheManager para anular el método computeExpiration de RedisCacheManager
La siguiente es la parte del código:
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;
}
}
Configure la clave de RedisTemplate como modo de serialización StringRedisSerializer (serialización JDK predeterminada)
@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;
}
}
Nota: La clase de aspecto CacheTtlAdvice debe implementar la interfaz Ordered e implementar el método getOrder, porque @Cacheable también se implementa a través de AOP, lo que implicará problemas de prioridad. Cuanto menor sea el valor de getOrder, la primera ejecución. El valor predeterminado es (2147483647). puede ver el orden de @EnableCaching El valor predeterminado, donde nuestras anotaciones personalizadas deben ejecutarse primero.