SpringCache实战遇坑

作者: java菜 | 来源:发表于2019-01-17 11:05 被阅读2次

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工程师朋友们加入Java程序员开发: 854393687

群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

相关文章

网友评论

    本文标题:SpringCache实战遇坑

    本文链接:https://www.haomeiwen.com/subject/mtnbdqtx.html