四时宝库

程序员的知识宝库

1. SpringCache实战遇坑(spring cache key)

1. SpringCache实战遇坑

1.1. pom

  1. 主要是以下两个
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 配合redis做缓存 -->
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

1.2. Redis配置

package com.zhiyis.common.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import redis.clients.jedis.JedisPoolConfig;
import java.lang.reflect.Method;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
 private static Logger logger = LoggerFactory.getLogger(RedisConfig.class);
 @Value("${spring.redis.host}")
 private String redisHost;
 @Value("${spring.redis.port}")
 private int redisPort;
 @Value("${spring.redis.timeout}")
 private int redisTimeout;
 @Value("${spring.redis.password}")
 private String redisAuth;
 @Value("${spring.redis.database}")
 private int redisDb;
 @Value("${spring.redis.pool.max-active}")
 private int maxActive;
 @Value("${spring.redis.pool.max-wait}")
 private int maxWait;
 @Value("${spring.redis.pool.max-idle}")
 private int maxIdle;
 @Value("${spring.redis.pool.min-idle}")
 private int minIdle;
 @Bean
 @Override
 public KeyGenerator keyGenerator() {
 return new KeyGenerator() {
 @Override
 public Object generate(Object target, Method method, Object... params) {
 StringBuilder sb = new StringBuilder();
 sb.append(target.getClass().getName());
 sb.append(method.getName());
 for (Object obj : params) {
 sb.append(obj.toString());
 }
 return sb.toString();
 }
 };
 }
 @Bean
 public CacheManager redisCacheManager() {
 RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate());
 //默认300秒过期
 cacheManager.setDefaultExpiration(300);
 // 启动时加载远程缓存
 cacheManager.setLoadRemoteCachesOnStartup(true);
 //是否使用前缀生成器
 cacheManager.setUsePrefix(true);
 return cacheManager;
 }
 @Bean
 public RedisConnectionFactory redisConnectionFactory() {
 JedisPoolConfig poolConfig = new JedisPoolConfig();
 poolConfig.setMaxTotal(maxActive);
 poolConfig.setMaxIdle(maxIdle);
 poolConfig.setMaxWaitMillis(maxWait);
 poolConfig.setMinIdle(minIdle);
 poolConfig.setTestOnBorrow(true);
 poolConfig.setTestOnReturn(false);
 poolConfig.setTestWhileIdle(true);
 JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(poolConfig);
 jedisConnectionFactory.setPassword(redisAuth);
 jedisConnectionFactory.setHostName(redisHost);
 jedisConnectionFactory.setDatabase(redisDb);
 jedisConnectionFactory.setPort(redisPort);
 jedisConnectionFactory.setTimeout(redisTimeout);
 return jedisConnectionFactory;
 }
 @Bean
 public RedisTemplate<String, Object> redisTemplate() {
 RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
 Jackson2JsonRedisSerializer<Object> serializer = jackson2JsonRedisSerializer();
 redisTemplate.setConnectionFactory(redisConnectionFactory());
 redisTemplate.setKeySerializer(new StringRedisSerializer());
 redisTemplate.setValueSerializer(serializer);
 redisTemplate.setHashKeySerializer(new StringRedisSerializer());
 redisTemplate.setHashValueSerializer(serializer);
 return redisTemplate;
 }
 @Bean
 public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
 final Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
 final ObjectMapper objectMapper = Jackson2ObjectMapperBuilder
 .json().build();
 objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
 objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
 jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
 return jackson2JsonRedisSerializer;
 }
}

在application.properties填上相应的参数

1.3. 使用

1.3.1. 坑1

  1. 目前主要使用的就是缓存和删除缓存
@Cacheable(sync = true, value = "on_hospital_list", key = "'3003101006_'+#requestReport.body['carer_id']", condition = "#requestReport.body['carer_id'] != '' ")
 @Override
 public ResponseReport getHospitalList(RequestReport requestReport) {
 ResponseReport responseReport = new ResponseReport();
 。。。
 return responseReport.returnSuccessResult(hospitals, "获取医院列表成功", requestReport);
 }
  1. 这里没有经验的人可能会纠结很久,因为我封装的入参对象,里面放的是JSONObject或者map作为的body值,这里我一开始是写成requestReport.body.carer_id这样的,但是这样会报如下错误
EL1008E: object of type 'com.alibaba.fastjson.JSONObject' - maybe not public

但你在网上找答案,都是文不对题,或者说其他错误导致相同的报错,反正我是找不到正确的解答

  1. 解决方法就是如上代码,直接写成#requestReport.body['carer_id']

1.3.2. 坑2

  1. 删除缓存,我自定义了一个注解,原因是好像CacheEvict没提供删除多个key的方法
// @CacheEvict(value = "on_hospital_list", key="'3003101006_'+#requestReport.body['carer_id']")
 @CacheRemove(value = "on_hospital_list"/*,key={"'3003101006_'+#requestReport.body['carer_id']","'3003101007_'+#requestReport.body['carer_id']"}*/)
 @Override
 public ResponseReport upDownServer(RequestReport requestReport) {
 。。。业务逻辑
 return responseReport.returnError("9999", "上下线失败", requestReport);
 }
  1. 注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheRemove {
 
 /**
 * 需要清除的大类 例如 autocms 所有缓存
 *
 * @return
 */
 String value() default "";
 
 
 /**
 * 需要清除的具体的额类型
 *
 * @return
 */
 String[] key() default {};
}
  1. 注解实现
import com.zhiyis.framework.annotation.CacheRemove;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
 * 清除缓存切面类
 *
 * @author laoliangliang
 * @date 2019/1/14 16:04
 */
@Component
@Aspect
public class CacheRemoveAspect {
 Logger logger = LoggerFactory.getLogger(this.getClass());
 @Autowired
 RedisTemplate<String, String> redis;
 ExpressionParser parser = new SpelExpressionParser();
 LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
 /**
 * 截获标有@CacheRemove的方法
 */
 @Pointcut(value = "(execution(* *.*(..)) && @annotation(com.zhiyis.framework.annotation.CacheRemove))")
 private void pointcut() {
 }
 /**
 * 功能描述: 切面在截获方法返回值之后
 */
 @AfterReturning(value = "pointcut()")
 private void process(JoinPoint joinPoint) {
 Object[] args = joinPoint.getArgs();
 //获取切入方法的数据
 MethodSignature signature = (MethodSignature) joinPoint.getSignature();
 //获取切入方法
 Method method = signature.getMethod();
 //获得注解
 CacheRemove cacheRemove = method.getAnnotation(CacheRemove.class);
 //注解解析
 String[] params = discoverer.getParameterNames(method);
 EvaluationContext context = new StandardEvaluationContext();
 for (int len = 0; len < params.length; len++) {
 context.setVariable(params[len], args[len]);
 }
 if (cacheRemove != null) {
 StringBuilder sb = new StringBuilder();
 String value = cacheRemove.value();
 if (!value.equals("")) {
 sb.append(value);
 }
 //需要移除的正则key
 String[] keys = cacheRemove.key();
 sb.append(":");
 for (String key : keys) {
 Expression expression = parser.parseExpression(key);
 String value1 = expression.getValue(context, String.class);
 //指定清除的key的缓存
 cleanRedisCache(sb.toString() + value1);
 }
 }
 }
 private void cleanRedisCache(String key) {
 if (key != null) {
 //删除缓存
 redis.delete(key);
 logger.info("清除 " + key + " 缓存");
 }
 }
}
  1. 这里的注解写入参数,如果想要使用spel表达式,要写上解析注解的一段代码

1. SpringCache实战遇坑

1.1. pom

  1. 主要是以下两个
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 配合redis做缓存 -->
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

1.2. Redis配置

package com.zhiyis.common.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import redis.clients.jedis.JedisPoolConfig;
import java.lang.reflect.Method;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
 private static Logger logger = LoggerFactory.getLogger(RedisConfig.class);
 @Value("${spring.redis.host}")
 private String redisHost;
 @Value("${spring.redis.port}")
 private int redisPort;
 @Value("${spring.redis.timeout}")
 private int redisTimeout;
 @Value("${spring.redis.password}")
 private String redisAuth;
 @Value("${spring.redis.database}")
 private int redisDb;
 @Value("${spring.redis.pool.max-active}")
 private int maxActive;
 @Value("${spring.redis.pool.max-wait}")
 private int maxWait;
 @Value("${spring.redis.pool.max-idle}")
 private int maxIdle;
 @Value("${spring.redis.pool.min-idle}")
 private int minIdle;
 @Bean
 @Override
 public KeyGenerator keyGenerator() {
 return new KeyGenerator() {
 @Override
 public Object generate(Object target, Method method, Object... params) {
 StringBuilder sb = new StringBuilder();
 sb.append(target.getClass().getName());
 sb.append(method.getName());
 for (Object obj : params) {
 sb.append(obj.toString());
 }
 return sb.toString();
 }
 };
 }
 @Bean
 public CacheManager redisCacheManager() {
 RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate());
 //默认300秒过期
 cacheManager.setDefaultExpiration(300);
 // 启动时加载远程缓存
 cacheManager.setLoadRemoteCachesOnStartup(true);
 //是否使用前缀生成器
 cacheManager.setUsePrefix(true);
 return cacheManager;
 }
 @Bean
 public RedisConnectionFactory redisConnectionFactory() {
 JedisPoolConfig poolConfig = new JedisPoolConfig();
 poolConfig.setMaxTotal(maxActive);
 poolConfig.setMaxIdle(maxIdle);
 poolConfig.setMaxWaitMillis(maxWait);
 poolConfig.setMinIdle(minIdle);
 poolConfig.setTestOnBorrow(true);
 poolConfig.setTestOnReturn(false);
 poolConfig.setTestWhileIdle(true);
 JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(poolConfig);
 jedisConnectionFactory.setPassword(redisAuth);
 jedisConnectionFactory.setHostName(redisHost);
 jedisConnectionFactory.setDatabase(redisDb);
 jedisConnectionFactory.setPort(redisPort);
 jedisConnectionFactory.setTimeout(redisTimeout);
 return jedisConnectionFactory;
 }
 @Bean
 public RedisTemplate<String, Object> redisTemplate() {
 RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
 Jackson2JsonRedisSerializer<Object> serializer = jackson2JsonRedisSerializer();
 redisTemplate.setConnectionFactory(redisConnectionFactory());
 redisTemplate.setKeySerializer(new StringRedisSerializer());
 redisTemplate.setValueSerializer(serializer);
 redisTemplate.setHashKeySerializer(new StringRedisSerializer());
 redisTemplate.setHashValueSerializer(serializer);
 return redisTemplate;
 }
 @Bean
 public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
 final Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
 final ObjectMapper objectMapper = Jackson2ObjectMapperBuilder
 .json().build();
 objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
 objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
 jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
 return jackson2JsonRedisSerializer;
 }
}

在application.properties填上相应的参数

1.3. 使用

1.3.1. 坑1

  1. 目前主要使用的就是缓存和删除缓存
@Cacheable(sync = true, value = "on_hospital_list", key = "'3003101006_'+#requestReport.body['carer_id']", condition = "#requestReport.body['carer_id'] != '' ")
 @Override
 public ResponseReport getHospitalList(RequestReport requestReport) {
 ResponseReport responseReport = new ResponseReport();
 。。。
 return responseReport.returnSuccessResult(hospitals, "获取医院列表成功", requestReport);
 }
  1. 这里没有经验的人可能会纠结很久,因为我封装的入参对象,里面放的是JSONObject或者map作为的body值,这里我一开始是写成requestReport.body.carer_id这样的,但是这样会报如下错误
EL1008E: object of type 'com.alibaba.fastjson.JSONObject' - maybe not public

但你在网上找答案,都是文不对题,或者说其他错误导致相同的报错,反正我是找不到正确的解答

  1. 解决方法就是如上代码,直接写成#requestReport.body['carer_id']

1.3.2. 坑2

  1. 删除缓存,我自定义了一个注解,原因是好像CacheEvict没提供删除多个key的方法
// @CacheEvict(value = "on_hospital_list", key="'3003101006_'+#requestReport.body['carer_id']")
 @CacheRemove(value = "on_hospital_list"/*,key={"'3003101006_'+#requestReport.body['carer_id']","'3003101007_'+#requestReport.body['carer_id']"}*/)
 @Override
 public ResponseReport upDownServer(RequestReport requestReport) {
 。。。业务逻辑
 return responseReport.returnError("9999", "上下线失败", requestReport);
 }
  1. 注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheRemove {
 
 /**
 * 需要清除的大类 例如 autocms 所有缓存
 *
 * @return
 */
 String value() default "";
 
 
 /**
 * 需要清除的具体的额类型
 *
 * @return
 */
 String[] key() default {};
}
  1. 注解实现
import com.zhiyis.framework.annotation.CacheRemove;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
 * 清除缓存切面类
 *
 * @author laoliangliang
 * @date 2019/1/14 16:04
 */
@Component
@Aspect
public class CacheRemoveAspect {
 Logger logger = LoggerFactory.getLogger(this.getClass());
 @Autowired
 RedisTemplate<String, String> redis;
 ExpressionParser parser = new SpelExpressionParser();
 LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
 /**
 * 截获标有@CacheRemove的方法
 */
 @Pointcut(value = "(execution(* *.*(..)) && @annotation(com.zhiyis.framework.annotation.CacheRemove))")
 private void pointcut() {
 }
 /**
 * 功能描述: 切面在截获方法返回值之后
 */
 @AfterReturning(value = "pointcut()")
 private void process(JoinPoint joinPoint) {
 Object[] args = joinPoint.getArgs();
 //获取切入方法的数据
 MethodSignature signature = (MethodSignature) joinPoint.getSignature();
 //获取切入方法
 Method method = signature.getMethod();
 //获得注解
 CacheRemove cacheRemove = method.getAnnotation(CacheRemove.class);
 //注解解析
 String[] params = discoverer.getParameterNames(method);
 EvaluationContext context = new StandardEvaluationContext();
 for (int len = 0; len < params.length; len++) {
 context.setVariable(params[len], args[len]);
 }
 if (cacheRemove != null) {
 StringBuilder sb = new StringBuilder();
 String value = cacheRemove.value();
 if (!value.equals("")) {
 sb.append(value);
 }
 //需要移除的正则key
 String[] keys = cacheRemove.key();
 sb.append(":");
 for (String key : keys) {
 Expression expression = parser.parseExpression(key);
 String value1 = expression.getValue(context, String.class);
 //指定清除的key的缓存
 cleanRedisCache(sb.toString() + value1);
 }
 }
 }
 private void cleanRedisCache(String key) {
 if (key != null) {
 //删除缓存
 redis.delete(key);
 logger.info("清除 " + key + " 缓存");
 }
 }
}
  1. 这里的注解写入参数,如果想要使用spel表达式,要写上解析注解的一段代码

码字不易看到最后了,那就点个关注呗,只收藏不点关注的都是在耍流氓!

关注并私信我“架构”,免费送一套Java架构资料,先到先得!

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接