1. 程式人生 > >SpringBoot使用AOP實現自定義介面快取

SpringBoot使用AOP實現自定義介面快取

一、引入pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

二、新建兩個註解

(1)該註解表示需要快取

package com.lzc.aopcache.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

/**
 * 自定義快取註解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LzcCache {

    String cacheName() default "";

    String key() default "";

    long timeOut() default 0;

    TimeUnit timeUnit() default TimeUnit.HOURS;
}

(2)該註解表示需要清除快取

package com.lzc.aopcache.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

/**
 * 自定義清除快取註解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LzcCacheEvict {

    String cacheName() default "";

    String key() default "";

}

三、新建兩個切面

(1)快取切面

package com.lzc.aopcache.aspect;

import com.lzc.aopcache.annotation.LzcCache;
import com.lzc.aopcache.utils.JsonUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.StringRedisTemplate;
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;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class LzcCacheAspect {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Around("execution(* com.lzc.aopcache.*.*.*(..))")
    public Object lzcCacheAspect(ProceedingJoinPoint jp) throws Throwable {

        Class<?> cls = jp.getTarget().getClass();
        String methodName = jp.getSignature().getName();

        Map<String, Object> map = isChache(cls, methodName);
        boolean isCache = (boolean)map.get("isCache");

        if (isCache) {
            String cacheName = (String) map.get("cacheName"); // 快取名字
            String key = (String) map.get("key"); // 自定義快取key
            long timeOut = (long) map.get("timeOut"); // 過期時間, 0代表永久有效
            TimeUnit timeUnit = (TimeUnit) map.get("timeUnit"); // 過期時間單位
            Class<?> methodReturnType = (Class<?>)map.get("methodReturnType"); // 方法的返回型別
            Method  method = (Method)map.get("method"); // 方法

            String realCacheName = "";
            // 判斷cacheName是否為空,如果cacheName為空則使用預設的cacheName
            if(cacheName.equals("")) {
                realCacheName = cls.getName() + "." + methodName;
            } else {
                realCacheName = cacheName;
            }

            String realKey = "";
            // 判斷key是否為空, 如果為空則使用預設的key
            if (key.equals("")) {
                realKey = realCacheName + "::" + defaultKeyGenerator(jp);
            } else {
                realKey = realCacheName + "::" + parseKey(key, method, jp.getArgs());
            }

            // 判斷快取中是否存在該key, 如果存在則直接從快取中獲取資料並返回
            if (stringRedisTemplate.hasKey(realKey)) {
                String value = stringRedisTemplate.opsForValue().get(realKey);
                return JsonUtil.toBean(methodReturnType, value);
            } else {
                Object result = jp.proceed();
                // 將返回結果儲存到快取中
                if (timeOut == 0) {
                    stringRedisTemplate.opsForValue().set(realKey, JsonUtil.toJson(result));
                } else {
                    stringRedisTemplate.opsForValue().set(realKey, JsonUtil.toJson(result), timeOut, timeUnit);
                }
                return result;
            }
        }
        return jp.proceed();
    }
    /**
     * 自定義生成key,使用方法中的引數作為key
     */
    private String defaultKeyGenerator(ProceedingJoinPoint jp) {
        // 獲取所有引數的值
        List<String> list = new ArrayList<>();
        Object[] args = jp.getArgs();
        for (Object object : args) {
            list.add(object.toString());
        }
        return list.toString();
    }

    /**
     *	獲取快取的key
     *	key 定義在註解上,支援SPEL表示式
     */
    private String parseKey(String key,Method method,Object [] args){
        //獲取被攔截方法引數名列表(使用Spring支援類庫)
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String [] paraNameArr = u.getParameterNames(method);
        //使用SPEL進行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        //SPEL上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        //把方法引數放入SPEL上下文中
        for(int i=0;i<paraNameArr.length;i++){
            context.setVariable(paraNameArr[i], args[i]);
        }
        return parser.parseExpression(key).getValue(context, String.class);
    }

    private Map<String, Object> isChache(Class<?> cls, String methodName) {
        boolean isCache = false;
        Map<String, Object> map = new HashMap<>();
        Method[] methods = cls.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName) && method.isAnnotationPresent(LzcCache.class)) {
                LzcCache lzcCache = method.getAnnotation(LzcCache.class); // 獲取方法上的註解
                Class<?> methodReturnType = method.getReturnType(); // 獲取方法的返回型別
                map.put("cacheName", lzcCache.cacheName());
                map.put("key", lzcCache.key());
                map.put("timeOut", lzcCache.timeOut());
                map.put("timeUnit", lzcCache.timeUnit());
                map.put("methodReturnType", methodReturnType);
                map.put("method", method);
                isCache = true;
                break;
            }
        }
        map.put("isCache", isCache);
        return map;
    }
}

(2)清除快取切面

package com.lzc.aopcache.aspect;

import com.lzc.aopcache.annotation.LzcCacheEvict;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.StringRedisTemplate;
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;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Aspect
@Component
public class LzcCacheEvictAspect {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Around("execution(* com.lzc.aopcache.*.*.*(..))")
    public Object lzcCacheAspect(ProceedingJoinPoint jp) throws Throwable {

        Class<?> cls = jp.getTarget().getClass();
        String methodName = jp.getSignature().getName();

        Map<String, Object> map = isChacheEvict(cls, methodName);
        boolean isCacheEvict = (boolean)map.get("isCacheEvict");

        if (isCacheEvict) {
            String cacheName = (String) map.get("cacheName"); // 快取名字
            String key = (String) map.get("key"); // 自定義快取key
            Method  method = (Method)map.get("method"); // 方法

            String realCacheName = "";
            // 判斷cacheName是否為空,如果cacheName為空則使用預設的cacheName
            if(cacheName.equals("")) {
                realCacheName = cls.getName() + "." + methodName;
            } else {
                realCacheName = cacheName;
            }

            String realKey = "";
            // 判斷key是否為空, 如果為空則使用預設的key
            if (key.equals("")) {
                realKey = realCacheName + "::" + defaultKeyGenerator(jp);
            } else {
                realKey = realCacheName + "::" + parseKey(key, method, jp.getArgs());
            }

            // 判斷快取中是否存在該key, 如果存在則直接從快取中刪除該資料
            if (stringRedisTemplate.hasKey(realKey)) {
                stringRedisTemplate.delete(realKey);
            }
        }
        return jp.proceed();
    }
    /**
     * 自定義生成key,使用方法中的引數作為key
     */
    private String defaultKeyGenerator(ProceedingJoinPoint jp) {
        // 獲取所有引數的值
        List<String> list = new ArrayList<>();
        Object[] args = jp.getArgs();
        for (Object object : args) {
            list.add(object.toString());
        }
        return list.toString();
    }

    /**
     *	獲取快取的key
     *	key 定義在註解上,支援SPEL表示式
     */
    private String parseKey(String key,Method method,Object [] args){
        //獲取被攔截方法引數名列表(使用Spring支援類庫)
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String [] paraNameArr = u.getParameterNames(method);
        //使用SPEL進行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        //SPEL上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        //把方法引數放入SPEL上下文中
        for(int i=0;i<paraNameArr.length;i++){
            context.setVariable(paraNameArr[i], args[i]);
        }
        return parser.parseExpression(key).getValue(context, String.class);
    }

    private Map<String, Object> isChacheEvict(Class<?> cls, String methodName) {
        boolean isCacheEvict = false;
        Map<String, Object> map = new HashMap<>();
        Method[] methods = cls.getDeclaredMethods();

        for (Method method : methods) {
            if (method.getName().equals(methodName) && method.isAnnotationPresent(LzcCacheEvict.class)) {
                LzcCacheEvict lzcCacheEvict = method.getAnnotation(LzcCacheEvict.class); // 獲取方法上的註解
                map.put("cacheName", lzcCacheEvict.cacheName());
                map.put("key", lzcCacheEvict.key());
                map.put("method", method);
                isCacheEvict = true;
                break;
            }
        }
        map.put("isCacheEvict", isCacheEvict);
        return map;
    }
}

四、使用

@GetMapping("/cache")
    @LzcCache(cacheName = "user",key = "#user.username", timeOut = 10, timeUnit = TimeUnit.MINUTES)
    public ResultVO cahce(User user) {
        System.out.println("進來了");
        return ResultVOUtil.success(user);
    }

    @GetMapping("/cache1")
    @LzcCacheEvict(cacheName = "user",key = "#user.username")
    public ResultVO cahce1(User user) {
        return ResultVOUtil.success("清除成功");
    }