2018-09-24

springboot中AOP+redis封裝緩存


目錄

一、目的
二、配置
--1、pom.xml
--2、redis配置
--3、響應(yīng)類
--4、AOP切面的配置
三、如何使用自已寫的緩存程序


一、目的

在controller層的方法上加一個注釋就可以利用aop切面去做代碼的擴(kuò)展,在代碼的擴(kuò)展中利用redis做為緩存中間件。關(guān)于注解就用自定義的@RedisCached


二、配置

1、pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、redis配置

基礎(chǔ)配置

@Configuration
public class RedisConfig {
    /**
     * @Description: 創(chuàng)建一個模板類,將redis連接工廠設(shè)置到模板類中{ @link RedisTemplate}
     * @Author: maozi
     * @Date: 2018/6/5 11:49
     * @see:
     **/
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        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<String, Object> template;

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

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

    public String getStringValue(String key) {
        ValueOperations<String, String> 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、響應(yīng)類
@ApiModel(description = "請求返回結(jié)果")
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;//過期時間,默認(rèn)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加上參數(shù)保證
        String keyName = "";
        if(method.getAnnotation(IgnoreToken.class) == null){ //如果沒有注解的話,keyName規(guī)則上加上一sourceFrom參數(shù)
            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)){//沒存在緩存,去查數(shù)據(jù)庫
                result = pjp.proceed();
                responseResult = (ResponseResult)result;
                if(responseResult.getObjectResult() != null){ //防止緩存空值
                    redisDao.setKey(keyName,responseResult.getObjectResult(),expire);
                }
            }else{//存在緩存中,在緩存中取數(shù)據(jù)庫
                responseResult.setObjectResult(redisDao.getValue(keyName));
                responseResult.setErrorCode(Constants.RESPONSE_CODE_SUCCESS);
                responseResult.setErrorMsg(Constants.RESPONSE_MSG_OK);
                result = responseResult;
                logger.info("redis緩存中查詢數(shù)據(jù),key值為" + keyName);
            }

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

    /**
     *  獲取被攔截方法對象
     *
     *  MethodSignature.getMethod() 獲取的是頂層接口或者父類的方法對象
     *  而緩存的注解在實現(xiàn)類的方法上
     *  所以應(yīng)該使用反射獲取當(dāng)前對象的方法對象
     */
    public Method getMethod(ProceedingJoinPoint pjp){
        //獲取參數(shù)的類型
        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<args.length;i++){
            argTypes[i]=args[i].getClass();
        }
        Method method=null;
        try {
            //有參的情況下
            if(args.length > 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請求,如果想擴(kuò)展,那就自行修改代碼,另外key的唯一性也可以用其他方式來確定。
2、這里緩存ResponseResult 中的objectResult的值,所以要用ResponseResult做返回,另外是因為,返回值在切面中做了限制。也就是說不用ResponseResult做返回值返回,那這個緩存是不起作用的。
3、expire是提供出去,動態(tài)設(shè)置緩存過期的時間


三、如何使用自已寫的緩存程序

/**
  * 商圈-購物中心關(guān)聯(lián)度
  */
@GetMapping(value = "/correlation-degree/{name}")
@RedisCached(expire = 6)
public ResponseResult shopMallCorrelationDegree(@PathVariable String name) {
    ResponseResult responseResult = new ResponseResult();

    List<Map<String, Object>> resultList = bussinessCircleService.getShopMallCorrelationDegree(name);

    responseResult.setErrorCode(Constants.RESPONSE_CODE_SUCCESS);
    responseResult.setErrorMsg(Constants.RESPONSE_MSG_OK);
    responseResult.setObjectResult(resultList);
    return responseResult;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容