天天看點

SpringCache實戰遇坑1. SpringCache實戰遇坑1. SpringCache實戰遇坑

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工程師朋友們加入Java程式員開發:

854393687 

群内提供免費的Java架構學習資料(裡面有高可用、高并發、高性能及分布式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代! 

轉載于:https://juejin.im/post/5c3fef5f51882533e05ee4ca