SpringCache实战遇坑

  1.1. pom

  主要是以下两个

  

  org.springframework.boot

  spring-boot-starter-data-redis

  

  

  

  org.springframework.boot

  spring-boot-starter-cache

  

  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 redisTemplate() {

  RedisTemplate redisTemplate=new RedisTemplate<>();

  Jackson2JsonRedisSerializer serializer=jackson2JsonRedisSerializer();

  redisTemplate.setConnectionFactory(redisConnectionFactory());

  redisTemplate.setKeySerializer(new StringRedisSerializer());

  redisTemplate.setValueSerializer(serializer);

  redisTemplate.setHashKeySerializer(new StringRedisSerializer());

  redisTemplate.setHashValueSerializer(serializer);

  return redisTemplate;

  }

  @Bean

  public Jackson2JsonRedisSerializer jackson2JsonRedisSerializer() {

  final Jackson2JsonRedisSerializer 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 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.1. pom

  主要是以下两个

  

  org.springframework.boot

  spring-boot-starter-data-redis

  

  

  

  org.springframework.boot

  spring-boot-starter-cache

  

  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 redisTemplate() {

  RedisTemplate redisTemplate=new RedisTemplate<>();

  Jackson2JsonRedisSerializer serializer=jackson2JsonRedisSerializer();

  redisTemplate.setConnectionFactory(redisConnectionFactory());

  redisTemplate.setKeySerializer(new StringRedisSerializer());

  redisTemplate.setValueSerializer(serializer);

  redisTemplate.setHashKeySerializer(new StringRedisSerializer());

  redisTemplate.setHashValueSerializer(serializer);

  return redisTemplate;

  }

  @Bean

  public Jackson2JsonRedisSerializer jackson2JsonRedisSerializer() {

  final Jackson2JsonRedisSerializer 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 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表达式,要写上解析注解的一段代码

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值