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

参考

https://huan1993.iteye.com/blog/2395239

http://www.cnblogs.com/imyijie/p/6518547.html

原文地址:https://www.cnblogs.com/sky-chen/p/10268229.html

时间: 2024-11-09 19:25:57

SpringCache实战遇坑的相关文章

vue项目实战爬坑小记002

1.如何使用vuex来保存数据(需要传参的情况下) 实例说明:登录->缓存用户信息->跳转到首页->从state获取用户信息显示在页面上 step1: 新建store.js作为初始化vuex的主文件,可在里面创建state对象,缓存数据字段,初始化vuex, 目录结构如下: store.js代码如下: 1 import Vue from 'vue'; 2 import Vuex from 'vuex'; 3 import * as actions from './actions'; 4

Windows安装Scrapy遇坑解决办

PS: Windows真心不适合开发.且行且珍惜.... 坑: error: Setup script exited with error: Microsoft Visual C++ 9.0 is required (Unable to find vcvarsall.bat). Get it from http://aka.ms/vcpython27 解决方法: Microsoft Visual C++ Compiler for Python 2.7

phalcon安装-遇坑php-config is not installed 解决方法

通过源码编译安装php环境,按照phalcon官方文档安装扩展,会遇到php-config is not installed的坑. 尝试通过下列命令可以解决: 1 cd /opt/cphalcon-3.2.1/build/php7/64bits 2 && phpize --enable-phalcon 3 --with-phpconfig=/usr/local/src/php7/bin/php-config 4 && ./configure --with-php-confi

vue项目实战爬坑小记003

太久没更新了.其实期间遇到了很多问题,都想着来整理和记录一下.可能是懒癌发作了吧,一直没动手记录.今天突破懒癌,重新来记录一波~~ 页面上如果要显示可编辑的保留数据格式的文本,可以有2种方式: 1. textarea标签中直接插入该数据的对象格式.有一个需要注意的坑是:如果修改里面的内容,容器不重绘的话,多个切换的时候内容不会变!!! 应该有人会吐槽: 不会用v-model 吗?小菜鸡~~~ 如果用了,确实能响应,但是不会显示这个格式, 而且如果直接绑定对象,就只会显示object文本. 1 <

ionic3 ion-slides遇坑

不想吐槽  ionic-slides  的组件,是个巨坑...切换页面以后再返回当前页面, 不能自动播放,网上的解决方案都是没用的(亲测,后台获取的数据) ...  不信邪的宝宝们可以去试试..建议换成swiper.js吧...因为ionic-slides本来就是基于这个插件开发的... swiper官网:https://www.swiper.com.cn/ 附上ionic3中怎么调用swiper.js 1.下载swiper到本地项目中(src/assets/xxx.js) 2.在index.h

k8s遇坑:The connection to the server k8s-api.virtual.local:6443 was refused - did you specify the right host or port?

k8s坑The connection to the server localhost:8080 was refused - did you specify the right host or port 2019年01月08日 17:21:06 金柱 阅读数:47 定义一个mysql的RC文件:mysql-rc.yaml apiVersion: v1 kind: ReplicationController metadata: name: mysql spec: replicas: 1 select

protobuf遇坑总结

在一个vs2013解决方案下创建了三个工程文件,在其中一个工程(Foundation)中放有CtrlMessage.pb.h和ControlMessageTags的头文件.编译后出错总共68处. 错误 2 error LNK2019: 无法解析的外部符号 "void __cdecl google::protobuf::internal::VerifyVersion(int,int,char const *)" ([email protected]@[email protected]@@

cocos2d-x v2.2 IOS工程支持64-bit 遇坑记录

修改缘由 由于 iPhone 5S的A7 CPU   iPhone 6(A8 CPU)都已经支持64-bit ARM 架构,据说64位处理器跑64代码会提高处理能力?因此二月一新提交appstore应用必须支持64位并且六月份更新应用也必须支持. 支持64bit 关于Xcode “Build Setting” 设置 1. Xcode “Build Setting”中的Architectures参数必须设置Standard architectures (armv7,arm64). 2. Xcode

小程序遇坑

1.首先无法直接操作dom 脚本逻辑是放在JsCore中运行的,jscore是一个没有窗口对象的环境,所以无法在脚本中使用window,渲染页面只能重新获取数据渲染页面, 因此做到一些下滑加载更多数据时,需要将原本数据存下来,再将新获取数据与原数据进行去重排序(或不需要)整合渲染. 2.scroll-view 如进行聊天操作时,需将最新消息显示在底部,所以获取消息都应将scroll-top显示最底部(99999之类),但实际设置后并无反映. 测试后发现,不能将页面数据与scroll-top数据同