Java 和 Kotlin 的自定义注解,实现代替缓存管理器的操作支持EL注解

今天实现的是一个缓存管理器的代替,也是我自己的一个小尝试,有不足的地方需要大家提出,思路大部分都是按照Redis的缓存管理器的想法来的(文章会很长)

前言:

spring boot 版本 2.1.6.RELEASE
引入redis

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

引入AOP

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

引入FastJson

		
            com.alibaba
            fastjson
            1.2.61
        

统一解说一下
注解上的参数代表意义
key代表的缓存前缀例 user:info:
keys代表前缀后面拼接的参数值,例 keys = “phone”,传递方法中phone的参数是1234,则写入缓存的key是:user:info:1234,这个可以写EL表达式,用来解析对象参数的某一个值,例参数值为User user,keys="#user.phone",则解析后为user:info:,如果这个不写参数,则会把这个方法的所有参数排序以后生成一个key。
涉及有三个注解,三个AOP切面的实现类,一个EL表达式解析类
RedisSetString 写入和更新
RedisGetString 单纯获取
RedisDelString 删除

今天先来kotlin的

Kotlin

首先写三个注解 RedisSetString、RedisGetString、RedisDelString

注解RedisSetString代码如下:

@Target(AnnotationTarget.FUNCTION)			//表明适用于方法上
@Retention(AnnotationRetention.RUNTIME)		//表明运行时
@MustBeDocumented
annotation class RedisSetString(val key: String = "", val keys: String = "", val expire: Long = 0L, val timeUnit: TimeUnit = TimeUnit.SECONDS)
参数解析:
key -- 缓存前缀
keys -- 获取方法相同参数名的值用于拼接完整的key,
expire -- 缓存有效时间,默认为0L
timeUnit -- 缓存有效时间的单位,默认是秒

注解RedisGetString代码如下:

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class RedisGetString(val key: String = "", val keys: String = "")
key -- 缓存前缀
keys -- 获取方法相同参数名的值用于拼接完整的key,

注解RedisDelString代码如下

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class RedisDelString(val key: String = "", val keys: String = "")

上面就是三个注解的代码,接下来是一个Keys值得获取与解析类,支持EL表达式,创建AspectExpression 类

import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONObject
import com.bedding.core.exception.NotFoundException
import org.aspectj.lang.ProceedingJoinPoint
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.core.DefaultParameterNameDiscoverer
import org.springframework.expression.EvaluationContext
import org.springframework.expression.Expression
import org.springframework.expression.spel.standard.SpelExpressionParser
import org.springframework.expression.spel.support.StandardEvaluationContext
import java.lang.Exception
import java.lang.StringBuilder
import java.lang.reflect.Method

object AspectExpression {

    /**
     * 用于SpEL表达式解析.
     */
    private val parser = SpelExpressionParser()
    /**
     * 用于获取方法参数定义名字.
     */
    private val nameDiscoverer = DefaultParameterNameDiscoverer()

    private val logger: Logger = LoggerFactory.getLogger(AspectExpression::class.java)

    fun getKey(keys: String, method: Method, joinPoint: ProceedingJoinPoint): String {
        // 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
        val paramNames = nameDiscoverer.getParameterNames(method)
        // 通过joinPoint获取被注解方法的形参
        val args = joinPoint.args
        return if (!paramNames.isNullOrEmpty()) {
            //当没有指定keys时,把所有参数的值拼接成一个key,对象的使用字典排序
            if (keys == "") {
                val value = StringBuffer()
                args.forEach {
                    val str = getStr(it)
                    if (str.first().toString() == ":") {
                        value.append(str)
                    } else {
                        value.append(":").append(str)
                    }
                }
                value.toString()
            }
            //指定的keys
            else {
                //当keys中不存在#即没有使用el表达式,
                if (!keys.contains("#")) {
                    getStr(args[paramNames.toList().indexOf(keys)])
                }
                //存在#符号,则是支持EL表达式
                else {
                    // 解析过后的Spring表达式对象
                    val expression: Expression = parser.parseExpression(keys)
                    // spring的表达式上下文对象
                    val context: EvaluationContext = StandardEvaluationContext()
                    // 给上下文赋值
                    for (i in args.indices) {
                        context.setVariable(paramNames[i], args[i])
                    }
                    expression.getValue(context).toString()
                }
            }
        } else {
            throw NotFoundException("没有找到对应的参数")
        }
    }

    //判断是否支持序列化
    private fun getStr(any: Any): String {
        return try {
            val data = JSON.parseObject(JSON.toJSONString(any), JSONObject()::class.java)
            //固定排序后开始拼接
            val keySet = data.keys.sorted()
            val value = StringBuffer()
            keySet.forEach {
                if (data[it] != null) {
                    value.append(":").append(data[it])
                }
            }
            value.toString()
        } catch (e: Exception) {
            any.toString()
        }
    }
}

只要把keys 传入即可返回出解析后的值
然后创建RedisSetString 的 AOP切面类RedisSetStringAspect需要引入StringRedisTemplate
类RedisSetString的代码如下:

import com.alibaba.fastjson.JSON
import com.bedding.web.core.annotations.RedisSetString
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.*
import org.aspectj.lang.reflect.MethodSignature
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.stereotype.Component*

@Aspect
@Component
class RedisSetStringAspect(private val redisTemplate: StringRedisTemplate) {

    private val logger: Logger = LoggerFactory.getLogger(RedisSetStringAspect::class.java)

    @Pointcut("@annotation(com.bedding.web.core.annotations.RedisSetString)")
    fun pointcutRedisSetString() {
    }
	
	//包围切面
	//该注解表示拦截添加了@RedisSetString注解的方法体
    @Around("pointcutRedisSetString()")
    fun redisSetStringBefore(joinPoint: ProceedingJoinPoint): Any {
        val sign: MethodSignature = joinPoint.signature as MethodSignature
        val method = sign.method
        //获取方法上的注解
        val annotation = method.getAnnotation(RedisSetString::class.java)
        //keys代表的值
        val parameter = AspectExpression.getKey(annotation.keys, method, joinPoint)
        //判断自己个字符是不是":",然后进行拼接
        val key = if (parameter.first().toString() == ":"){
            "${annotation.key}${parameter}"
        }else{
            "${annotation.key}:${parameter}"
        }
        //调用方法内的所有内容获取返回值
        val result = joinPoint.proceed()
        //序列化采用的是FastJson
        if (annotation.expire != 0L) {
        //如果对应的时间是默认值0L则是永久写入
            redisTemplate.opsForValue().set(key, JSON.toJSONString(result))
        } else {
        //如果写入了则是按传递的来写入
            redisTemplate.opsForValue().set(key, JSON.toJSONString(result), annotation.expire, annotation.timeUnit)
        }
        return result
    }

}

然后创建RedisGetString 的 AOP切面类RedisGetStringAspect需要引入StringRedisTemplate
类RedisGetString的代码如下

import com.alibaba.fastjson.JSON
import com.bedding.web.core.annotations.RedisGetString
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
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.data.redis.core.StringRedisTemplate
import org.springframework.stereotype.Component


@Aspect
@Component
class RedisGetStringAspect(private val redisTemplate: StringRedisTemplate) {

    private val logger: Logger = LoggerFactory.getLogger(RedisGetStringAspect::class.java)


    @Pointcut("@annotation(com.bedding.web.core.annotations.RedisGetString)")
    fun pointcutRedisGetString() {
    }
    
 	//该注解表示拦截添加了@RedisGetString注解的方法体
    @Around(value = "pointcutRedisGetString()")
    fun redisGetStringBefore(joinPoint: ProceedingJoinPoint): Any {
        val sign: MethodSignature = joinPoint.signature as MethodSignature
        val method = sign.method
        //获取方法上的注解
        val annotation = method.getAnnotation(RedisGetString::class.java)
        val parameter = AspectExpression.getKey(annotation.keys, method, joinPoint)
        val key = if (parameter.first().toString() == ":"){
            "${annotation.key}${parameter}"
        }else{
            "${annotation.key}:${parameter}"
        }
        //实现判断这个存不存在,然后再去获取
        return if (redisTemplate.hasKey(key)) {
        	//采用FastJson的一个序列化方式,获取值然后获取方法的返回参数类型再来序列化
            JSON.parseObject(redisTemplate.opsForValue().get(key), method.returnType)
        } else {
        	//不存在则去执行方法,然后返回
            joinPoint.proceed()
        }
    }
}

然后创建RedisDelString 的 AOP切面类RedisDelStringAspect需要引入StringRedisTemplate
类RedisDelString的代码如下

import com.bedding.core.exception.NotFoundException
import com.bedding.web.core.annotations.RedisDelString
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.*
import org.aspectj.lang.reflect.MethodSignature
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.stereotype.Component


@Aspect
@Component
class RedisDelStringAspect(private val redisTemplate: StringRedisTemplate) {

    private val logger: Logger = LoggerFactory.getLogger(RedisDelStringAspect::class.java)

    @Pointcut("@annotation(com.bedding.web.core.annotations.RedisDelString)")
    fun pointcutRedisDelString() {
    }

    //该注解表示拦截添加了@RedisDelString注解的方法体
    @Around("pointcutRedisDelString()")
    fun redisDelStringBefore(joinPoint: ProceedingJoinPoint): Any {
        val sign: MethodSignature = joinPoint.signature as MethodSignature
        val method = sign.method
        //获取方法上的注解
        val annotation = method.getAnnotation(RedisDelString::class.java)
        val parameter = AspectExpression.getKey(annotation.keys, method, joinPoint)
        val key = if (parameter.first().toString() == ":"){
            "${annotation.key}${parameter}"
        }else{
            "${annotation.key}:${parameter}"
        }
        //事先判断判断存不存在
        return if (redisTemplate.hasKey(key)) {
        	//存在则去删除
            redisTemplate.delete(key)
        } else {
        	//此处是我的自定义异常,需要完整使用这需要改称自己的
            throw NotFoundException("缓存中没有记录")
        }
    }

}

以上就是Kotlin的一个自定义注解代替Redis缓存管理器的实现方案.这个还不是很完善,如果大家有好的思路可以提供到我,我可以整改,接下来是JAVA的实现方式,注解和类命名一致

JAVA

同样写三个注解 RedisSetString、RedisGetString、RedisDelString
注解RedisSetString代码如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisSetString {
    public String key() default "";

    public String keys() default "";

    public long expire() default 0L;

    public TimeUnit timeUnit() default TimeUnit.SECONDS;



}

参数解析:
key -- 缓存前缀
keys -- 获取方法相同参数名的值用于拼接完整的key,
expire -- 缓存有效时间,默认为0L
timeUnit -- 缓存有效时间的单位,默认是秒

注解RedisGetString代码如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisGetString {
    public String key() default "";

    public String keys() default "";
}

key -- 缓存前缀
keys -- 获取方法相同参数名的值用于拼接完整的key,

注解RedisDelString代码如下

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisDelString {
    public String key() default "";

    public String keys() default "";

}

上面就是三个注解的代码,接下来是一个Keys值得获取与解析类,支持EL表达式,创建AspectExpression 类

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.springcloud.common.core.exception.NotFoundException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class AspectExpression {

    private Logger logger = LoggerFactory.getLogger(AspectExpression.class);

    /**
     * 用于SpEL表达式解析.
     */
    ;
    private static SpelExpressionParser parser = new SpelExpressionParser();
    /**
     * 用于获取方法参数定义名字.
     */
    private static DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    public static String getKey(String keys, Method method, ProceedingJoinPoint joinPoint) {
        // 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
        String[] paramNames = nameDiscoverer.getParameterNames(method);
        // 通过joinPoint获取被注解方法的形参
        Object[] args = joinPoint.getArgs();
        //当没有指定keys时,把所有参数的值拼接成一个key,对象的使用字典排序
        if (paramNames != null) {
            if ("".equals(keys)) {
                StringBuffer value = new StringBuffer();
                Arrays.asList(args).forEach(it -> {
                    String str = getStr(it);
                    if (str.subSequence(0, 1) == ":") {
                        value.append(str);
                    } else {
                        value.append(":").append(str);
                    }
                });
                return value.toString();
            }
            //指定的keys
            else {
                //当keys中不存在#即没有使用el表达式,
                if (!keys.contains("#")) {
                    return getStr(args[Arrays.asList(paramNames).indexOf(keys)]);
                }
                //存在#符号,则是支持EL表达式
                else {
                    // 解析过后的Spring表达式对象
                    Expression expression = parser.parseExpression(keys);
                    // spring的表达式上下文对象
                    EvaluationContext context =new StandardEvaluationContext();
                    // 给上下文赋值
                    for (int i = 0; i < args.length; i++) {
                        context.setVariable(paramNames[i], args[i]);
                    }
                    return Objects.requireNonNull(expression.getValue(context)).toString();
                }
            }
        } else {
            throw new NotFoundException("没有找到对应的参数");
        }

    }

    //判断是否支持序列化
    private static String getStr(Object any) {
        try {
            JSONObject data = JSON.parseObject(JSON.toJSONString(any), JSONObject.class);
            //固定排序后开始拼接
            List keySet = data.keySet().stream().sorted().collect(Collectors.toList());
            StringBuffer value = new StringBuffer();
            keySet.forEach(it -> {
                if (data.get(it) != null) {
                    value.append(":").append(data.get(it));
                }
            });
            return value.toString();
        } catch (Exception e) {
            return any.toString();
        }
    }
}

只要把keys 传入即可返回出解析后的值
然后创建RedisSetString 的 AOP切面类RedisSetStringAspect需要引入StringRedisTemplate
类RedisSetString的代码如下:

import com.alibaba.fastjson.JSON;
import com.springcloud.userserver.core.annotations.RedisSetString;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class RedisSetStringAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;


    @Pointcut("@annotation(com.springcloud.userserver.core.annotations.RedisSetString)")
    public void pointcutRedisSetString() {
    }

    //该注解表示拦截添加了@RedisSetString注解的方法体
    @Around("pointcutRedisSetString()")
    public Object redisSetStringBefore(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature sign = (MethodSignature) joinPoint.getSignature();
        Method method = sign.getMethod();
        //获取方法上的注解
        RedisSetString annotation = method.getAnnotation(RedisSetString.class);
        String parameter = AspectExpression.getKey(annotation.keys(), method, joinPoint);
        String key;
        if (parameter.subSequence(0, 1) == ":") {
            key = annotation.key() + parameter;
        } else {
            key = annotation.key() + ":" + parameter;
        }
        Object result = joinPoint.proceed();
        if (annotation.expire() != 0L) {
            redisTemplate.opsForValue().set(key, JSON.toJSONString(result));
        } else {
            redisTemplate.opsForValue().set(key, JSON.toJSONString(result), annotation.expire(), annotation.timeUnit());
        }
        return result;
    }

}

然后创建RedisGetString 的 AOP切面类RedisGetStringAspect需要引入StringRedisTemplate
类RedisGetString的代码如下

import com.alibaba.fastjson.JSON;
import com.springcloud.userserver.core.annotations.RedisGetString;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class RedisGetStringAspect {

    @Autowired
    public StringRedisTemplate redisTemplate;


    @Pointcut("@annotation(com.springcloud.userserver.core.annotations.RedisGetString)")
    public void pointcutRedisGetString() {
    }

    @Around(value = "pointcutRedisGetString()") //该注解表示拦截添加了@RedisSetString注解的方法体
    public Object redisGetStringBefore(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature sign = (MethodSignature) joinPoint.getSignature();
        Method method = sign.getMethod();
        //获取方法上的注解
        RedisGetString annotation = method.getAnnotation(RedisGetString.class);
        String parameter = AspectExpression.getKey(annotation.keys(), method, joinPoint);
        String key;
        if (parameter.subSequence(0, 1) == ":") {
            key = annotation.key() + parameter;
        } else {
            key = annotation.key() + ":" + parameter;
        }
        Boolean hasKey = redisTemplate.hasKey(key);
        if (hasKey != null && hasKey) {
            return JSON.parseObject(redisTemplate.opsForValue().get(key), method.getReturnType());
        } else {
            return joinPoint.proceed();
        }
    }
}

然后创建RedisDelString 的 AOP切面类RedisDelStringAspect需要引入StringRedisTemplate
类RedisDelString的代码如下

import com.springcloud.common.core.exception.NotFoundException;
import com.springcloud.userserver.core.annotations.RedisDelString;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
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.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class RedisDelStringAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private Logger logger = LoggerFactory.getLogger(RedisDelStringAspect.class);

    @Pointcut("@annotation(com.springcloud.userserver.core.annotations.RedisDelString)")
    public void pointcutRedisDelString() {
    }

    //该注解表示拦截添加了@RedisDelString注解的方法体
    @Around("pointcutRedisDelString()")
    public Object redisDelStringBefore(ProceedingJoinPoint joinPoint) {
        MethodSignature sign = (MethodSignature) joinPoint.getSignature();
        Method method = sign.getMethod();
        //获取方法上的注解
        RedisDelString annotation = method.getAnnotation(RedisDelString.class);
        String parameter = AspectExpression.getKey(annotation.keys(), method, joinPoint);
        String key;
        if (parameter.subSequence(0, 1) == ":") {
            key = annotation.key() + parameter;
        } else {
            key = annotation.key() + ":" + parameter;
        }
        Boolean hasKey = redisTemplate.hasKey(key);
        if (hasKey != null && hasKey) {
            return redisTemplate.delete(key);
        } else {
            throw new NotFoundException("缓存中没有记录");
        }
    }
}

关联代码已经上传:
下载链接:https://download.csdn.net/download/qq_40466900/12737503
所需积分:0

以上就是JAVA的自定注解以及AOP切面实现Redis的写入,更新查看以及删除。

如果有更好的实现方式可以一起聊天,然后更加完善

你可能感兴趣的:(redis,aop,语法基础)