2018-09-24

springboot中AOP+redis封装缓存


目录

一、目的
二、配置
--1、pom.xml
--2、redis配置
--3、响应类
--4、AOP切面的配置
三、如何使用自已写的缓存程序


一、目的

在controller层的方法上加一个注释就可以利用aop切面去做代码的扩展,在代码的扩展中利用redis做为缓存中间件。关于注解就用自定义的@RedisCached


二、配置

1、pom.xml

    org.springframework.boot
    spring-boot-starter-aop


    org.springframework.boot
    spring-boot-starter-data-redis

2、redis配置

基础配置

@Configuration
public class RedisConfig {
    /**
     * @Description: 创建一个模板类,将redis连接工厂设置到模板类中{ @link RedisTemplate}
     * @Author: maozi
     * @Date: 2018/6/5 11:49
     * @see:
     **/
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory){
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(factory);
        return template;
    }
}

再封装一层

@Repository
public class RedisDao {

    /**
     * @Description: 字符类的模板{ @link }
     * @Author: maozi
     * @Date: 2018/6/5 11:52
     * @see:
     **/
    @Autowired
    private StringRedisTemplate stringTemplate;

    /**
     * @Description: 对象类的模板 { @link }
     * @Author: maozi
     * @Date: 2018/6/5 11:53
     * @see:
     **/
    @Autowired
    private RedisTemplate template;

    public void setStringKey(String key, String value,int expire) {
        if(stringTemplate.hasKey(key)){
            stringTemplate.delete(key);
        }

        ValueOperations ops = stringTemplate.opsForValue();
        ops.set(key,value,expire, TimeUnit.MINUTES);
    }

    public String getStringValue(String key) {
        ValueOperations ops = this.stringTemplate.opsForValue();
        return ops.get(key);
    }

    public Object getValue(String key) {
        return template.opsForValue().get(key);
    }

    public void setKey(String key,Object value,long minutes){
        if(template.hasKey(key)){
            template.delete(key);
        }
        template.opsForValue().set(key,value,minutes, TimeUnit.MINUTES);
    }

    public boolean existByKey(String key){
        return template.hasKey(key);
    }

    public boolean existByStringKey(String key){
        return stringTemplate.hasKey(key);
    }

    public long getExpireTime(String key){
        return template.getExpire(key);
    }

    public Boolean setExpireToReturnYes(String key,long timeout){
        if (!template.hasKey(key)){
            return false;
        }
        return template.expire(key,timeout,TimeUnit.MINUTES);
    }

    public Boolean setStringExpireToReturnYes(String key,long timeout){
        if(!stringTemplate.hasKey(key)){
            return false;
        }
        return stringTemplate.expire(key,timeout,TimeUnit.MINUTES);
    }

    public void cleanCacheByString(String key){
        stringTemplate.delete(key);
    }

    public void cleanCache(String key){
        template.delete(key);
    }
}

注意:这里的模板有分字符的和对象的,如果value是字符的,那建议有字符模板

application.properties

# redis
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=1
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=500
spring.redis.pool.min-idle=0
spring.redis.timeout=0
3、响应类
@ApiModel(description = "请求返回结果")
public class ResponseResult {
    private String errorCode;
    private String errorMsg;
    private Object objectResult;


    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

    public Object getObjectResult() {
        return objectResult;
    }

    public void setObjectResult(Object objectResult) {
        this.objectResult = objectResult;
    }

    @Override
    public String toString(){
        return JSON.toJSONString(this);
    }

}
4、AOP切面的配置

自定义注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedisCached {
    public int expire() default 5;//过期时间,默认5分钟
}

切面类

/**
 * @Description: redis缓存的AOP
 * @Author: maozi
 * @Date: 2018/9/3 10:07
 * @see: RedisCached
 **/
@Aspect
@Component
public class RedisAspect {

    private final Logger logger = LoggerFactory.getLogger(RedisAspect.class);

    @Autowired
    RedisDao redisDao;

    @Pointcut("execution(public com.zhengjia.entity.ResponseResult com.zhengjia.web.*.*(..)) && @annotation(com.zhengjia.common.Annotation.RedisCached)")
    public void redisAdvice(){}

    @Around("redisAdvice()")
    public Object Interceptor(ProceedingJoinPoint pjp){
        Object result = null;
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();

        String port = String.valueOf(request.getServerPort());
        String uri = request.getRequestURI();
        String methodType = request.getMethod();
        String queryString = request.getQueryString();

        //反射拿方法信息
        Method method=getMethod(pjp);

        //获得annotation的信息
        RedisCached annotation = method.getAnnotation(RedisCached.class);
        int expire = annotation.expire();

        //检查请求类型
        if(!methodType.equalsIgnoreCase("GET")){
            throw new RuntimeException("只允许get请求做缓存");
        }

        //key的唯一性由url加上参数保证
        String keyName = "";
        if(method.getAnnotation(IgnoreToken.class) == null){ //如果没有注解的话,keyName规则上加上一sourceFrom参数
            String token = request.getHeader("Authorization");
            if(token.isEmpty()) throw new RuntimeException("请求头Authorization中没有token信息");
            Map userMap = (Map)redisDao.getValue(token);
            keyName = (String)userMap.get("sourceFrom") + port + uri + "?" + queryString;
        }else {
            keyName = port + uri + "?" + queryString;
        }

        ResponseResult responseResult = new ResponseResult();

        try {
            if (!redisDao.existByKey(keyName)){//没存在缓存,去查数据库
                result = pjp.proceed();
                responseResult = (ResponseResult)result;
                if(responseResult.getObjectResult() != null){ //防止缓存空值
                    redisDao.setKey(keyName,responseResult.getObjectResult(),expire);
                }
            }else{//存在缓存中,在缓存中取数据库
                responseResult.setObjectResult(redisDao.getValue(keyName));
                responseResult.setErrorCode(Constants.RESPONSE_CODE_SUCCESS);
                responseResult.setErrorMsg(Constants.RESPONSE_MSG_OK);
                result = responseResult;
                logger.info("redis缓存中查询数据,key值为" + keyName);
            }

        } catch (Throwable e) {
            e.printStackTrace();
            responseResult.setErrorCode(Constants.RESPONSE_CODE_ERROR);
            responseResult.setErrorMsg("redisAspect报错!");
            logger.info("redisAspect报错!");
        }
        return result;
    }

    /**
     *  获取被拦截方法对象
     *
     *  MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象
     *  而缓存的注解在实现类的方法上
     *  所以应该使用反射获取当前对象的方法对象
     */
    public Method getMethod(ProceedingJoinPoint pjp){
        //获取参数的类型
        Object [] args=pjp.getArgs();
        Class [] argTypes=new Class[pjp.getArgs().length];
        if(args.length == 1 && args[0] == null){
            args = new Object[0];
        }
        for(int i=0;i 0){
                method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), argTypes);
            }else { //无参的情况下
                Class clazz = Class.forName(pjp.getSignature().getDeclaringTypeName());
                Method[] i = clazz.getMethods();
                for(Method data : clazz.getMethods()){
                    if(data.getName().equalsIgnoreCase(pjp.getSignature().getName())){
                        method = data;
                        break;
                    }
                }

            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return method;

    }
}

注意:
1、缓存的key的唯一性是用url来保证,而且这里只允许Get请求,如果想扩展,那就自行修改代码,另外key的唯一性也可以用其他方式来确定。
2、这里缓存ResponseResult 中的objectResult的值,所以要用ResponseResult做返回,另外是因为,返回值在切面中做了限制。也就是说不用ResponseResult做返回值返回,那这个缓存是不起作用的。
3、expire是提供出去,动态设置缓存过期的时间


三、如何使用自已写的缓存程序

/**
  * 商圈-购物中心关联度
  */
@GetMapping(value = "/correlation-degree/{name}")
@RedisCached(expire = 6)
public ResponseResult shopMallCorrelationDegree(@PathVariable String name) {
    ResponseResult responseResult = new ResponseResult();

    List> resultList = bussinessCircleService.getShopMallCorrelationDegree(name);

    responseResult.setErrorCode(Constants.RESPONSE_CODE_SUCCESS);
    responseResult.setErrorMsg(Constants.RESPONSE_MSG_OK);
    responseResult.setObjectResult(resultList);
    return responseResult;
}

你可能感兴趣的:(2018-09-24)