1. SpringCache实战遇坑
1.1. pom
- 主要是以下两个
<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
- 目前主要使用的就是缓存和删除缓存
@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); }
- 这里没有经验的人可能会纠结很久,因为我封装的入参对象,里面放的是JSONObject或者map作为的body值,这里我一开始是写成requestReport.body.carer_id这样的,但是这样会报如下错误
EL1008E: object of type 'com.alibaba.fastjson.JSONObject' - maybe not public
但你在网上找答案,都是文不对题,或者说其他错误导致相同的报错,反正我是找不到正确的解答
- 解决方法就是如上代码,直接写成#requestReport.body['carer_id']
1.3.2. 坑2
- 删除缓存,我自定义了一个注解,原因是好像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); }
- 注解
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface CacheRemove { /** * 需要清除的大类 例如 autocms 所有缓存 * * @return */ String value() default ""; /** * 需要清除的具体的额类型 * * @return */ String[] key() default {}; }
- 注解实现
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 + " 缓存"); } } }
- 这里的注解写入参数,如果想要使用spel表达式,要写上解析注解的一段代码
1. SpringCache实战遇坑
1.1. pom
- 主要是以下两个
<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
- 目前主要使用的就是缓存和删除缓存
@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); }
- 这里没有经验的人可能会纠结很久,因为我封装的入参对象,里面放的是JSONObject或者map作为的body值,这里我一开始是写成requestReport.body.carer_id这样的,但是这样会报如下错误
EL1008E: object of type 'com.alibaba.fastjson.JSONObject' - maybe not public
但你在网上找答案,都是文不对题,或者说其他错误导致相同的报错,反正我是找不到正确的解答
- 解决方法就是如上代码,直接写成#requestReport.body['carer_id']
1.3.2. 坑2
- 删除缓存,我自定义了一个注解,原因是好像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); }
- 注解
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface CacheRemove { /** * 需要清除的大类 例如 autocms 所有缓存 * * @return */ String value() default ""; /** * 需要清除的具体的额类型 * * @return */ String[] key() default {}; }
- 注解实现
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 + " 缓存"); } } }
- 这里的注解写入参数,如果想要使用spel表达式,要写上解析注解的一段代码
码字不易看到最后了,那就点个关注呗,只收藏不点关注的都是在耍流氓!
关注并私信我“架构”,免费送一套Java架构资料,先到先得!