Spring-AOP根据spel获取方法参数值、Bean对象属性值

发布时间 2023-10-08 17:29:49作者: 郭慕荣

Spring-AOP根据spel获取方法参数值、Bean对象属性值,动态的获取属性值,可以用来做注解式分布式锁、注解式获取属性值等等。

第一步:自定义注解,代码如下所示

package com.example.springbootstudy.interfaces;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
/**
 * 自定义注解,在注解上的方法将执行代理流程
 */
public @interface MyAnnotation {

    String key();

}

第二步:方法上添加注解,代码如下所示:

package com.example.springbootstudy.service;

import com.example.springbootstudy.config.BizException;
import com.example.springbootstudy.config.CommonEnum;
import com.example.springbootstudy.entity.Person;
import com.example.springbootstudy.interfaces.MyAnnotation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class TestService {


    public void print() {
        throw new BizException(CommonEnum.SERVER_BUSY);
    }

    /**
     * 添加自定义aop注解 #id为el表达式,需要和被替换的参数名称相同
     * 获取参数值
     *
     * @param id
     * @param person
     */
    @MyAnnotation(key = "'param id is ' + #id")
    public void doSome(String id, Person person) {
        System.out.println("方法执行中");
    }


    /**
     * 添加自定义aop注解 #id为el表达式,需要和被替换的参数名称相同
     * 获取bean对象属性值
     *
     * @param id
     * @param person
     */
    @MyAnnotation(key = "'param id is ' + #person.name")
    public void doSome1(String id, Person person) {
        System.out.println("方法执行中");
    }


}

第三步:添加AOP,代码如下所示:

package com.example.springbootstudy.interceptors;

import com.example.springbootstudy.interfaces.MyAnnotation;
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.core.DefaultParameterNameDiscoverer;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.*;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @Description TODO
 * @Author Jelly
 * @Date 2023/10/8 15:56
 */
@Component
@Aspect
public class MyAspect {

    @Pointcut("@annotation(com.example.springbootstudy.interfaces.MyAnnotation)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object dosome(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取切入方法的对象
        Method method = signature.getMethod();
        //获取方法上的Aop注解
        MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
        //获取注解上的值如 : @MyAnnotation(key = "'param id is ' + #id")
        String keyEl = annotation.key();
        //将注解的值中的El表达式部分进行替换
        //创建解析器
        SpelExpressionParser parser = new SpelExpressionParser();
        //获取表达式
        Expression expression = parser.parseExpression(keyEl);
        //设置解析上下文(有哪些占位符,以及每种占位符的值)
        EvaluationContext context = new StandardEvaluationContext();
        //获取参数值
        Object[] args = joinPoint.getArgs();
        //获取运行时参数的名称
        DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(method);
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i].toString());
        }

        //解析,获取替换后的结果
        try {
            String result = expression.getValue(context).toString();
            System.out.println(result);
        } catch (EvaluationException e) {
            e.printStackTrace();
        }

        test(joinPoint);

        return null;
    }


    /**
     * 获取bean对象属性值
     *
     * @param point
     */
    private void test(ProceedingJoinPoint point) {
        //获取方法签名
        MethodSignature signature = (MethodSignature) point.getSignature();
        //获取切入方法的对象
        Method method = signature.getMethod();
        //获取方法上的Aop注解
        MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
        //获取注解上的值如 : @MyAnnotation(key = "'param id is ' + #id")
        String keyEl = annotation.key();

        EvaluationContext context = getContext(point.getArgs(), signature.getMethod());
        String value = getValue(context, keyEl, String.class);
        System.out.println(value);
    }


    /**
     * 获取spel 定义的参数值
     *
     * @param context 参数容器
     * @param key     key
     * @param clazz   需要返回的类型
     * @param <T>     返回泛型
     * @return 参数值
     */
    private <T> T getValue(EvaluationContext context, String key, Class<T> clazz) {
        SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
        Expression expression = spelExpressionParser.parseExpression(key);
        return expression.getValue(context, clazz);
    }


    /**
     * 获取参数容器
     *
     * @param arguments       方法的参数列表
     * @param signatureMethod 被执行的方法体
     * @return 装载参数的容器
     */
    private EvaluationContext getContext(Object[] arguments, Method signatureMethod) {
        String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(signatureMethod);
        if (parameterNames == null) {
            throw new IllegalArgumentException("参数列表不能为null");
        }
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < arguments.length; i++) {
            context.setVariable(parameterNames[i], arguments[i]);
        }
        return context;
    }


}

总结:spring根据spel表达式动态的获取参数值,动态的获取bean属性值,使用场景主要是在注解式REDIS分布式锁种,注解式获取bean对象值等等场景。