2018-09-24

springboot中AOP+redis封裝緩存


目錄

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


一、目的

在controller層的方法上加一個注釋就可以利用aop切面去做代碼的擴展,在代碼的擴展中利用redis做為緩存中間件。關于注解就用自定義的@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配置

基礎配置

@Configuration
public class RedisConfig {
    /**
     * @Description: 創建一個模板類,將redis連接工廠設置到模板類中{ @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、響應類
@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<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請求,如果想擴展,那就自行修改代碼,另外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<Map<String, Object>> resultList = bussinessCircleService.getShopMallCorrelationDegree(name);

    responseResult.setErrorCode(Constants.RESPONSE_CODE_SUCCESS);
    responseResult.setErrorMsg(Constants.RESPONSE_MSG_OK);
    responseResult.setObjectResult(resultList);
    return responseResult;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373