自定义注解中使用SpEL表达式,动态获取方法参数或执行方法


SpEL表达式很早就接触过,感觉很高大上,但是没有了解过更多的使用方法。基本都是一些开源项目包装好的。

1 SpEL的常见用法

1.1 获取变量

获取变量的功能,比较常见的用法是spring中缓存注解的功能。可以动态获取方法中参数的值。如下:

	@Cacheable(value = "cacheKey",key = "#key")
    public String getName(String key) {
        return "name";
    }

1.2 执行方法

这个例子用过spring security的或者类似的授权框架的可能比较熟悉。如下

	@PreAuthorize("hasRole('ROLE_ADMIN')")
	public void addUser(User user) {
      System.out.println("addUser................" + user);
	}

这里其实就是通过表达式去执行某个类的hasRole方法,参数为’ROLE_ADMIN’,然后获得返回值再进行后续操作。

1.3 其他用法

表达式不仅支持获取属性和执行方法,还支持各种运算符操作,能查到很多其他大神写的。这里就不写了

2 自己实现表达式的运行

写demo之前先简单介绍一下执行表达式时比较重要的几个类

类名描述
ExpressionParser表达式的解析器,主要用来生产表达式
Expression表达式对象
EvaluationContext表达式运行的上下文。包含了表达式运行时的参数,以及需要执行方法的类

2.1 获取变量

import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

/**
 *
 * @Author: dong
 * @Date: 2022/6/23 10:25
 */
public class Demo {

    public static void main(String[] args) {
    	// 表达式解析器
        ExpressionParser parser = new SpelExpressionParser();
        // 解析出一个表达式
        Expression expression = parser.parseExpression("#user.name");
        // 开始准备表达式运行环境
        EvaluationContext ctx = new StandardEvaluationContext();
        ctx.setVariable("user", new User("三侃"));
        String value = expression.getValue(ctx, String.class);
        System.out.println(value);
    }

    public static class User{
        private String name;
        public User(String name){
            this.name = name;
        }
        public String getName(){
            return this.name;
        }
    }
}

最后获得的value就是传入的值,这个demo已经可以通过表达式获取到user的name属性值了。

2.2 执行方法

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

/**
 *
 * @Author: dong
 * @Date: 2022/6/23 10:25
 */
public class Demo {

    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression("getInput()");
        StandardEvaluationContext ctx = new StandardEvaluationContext();
        User user = new User("三侃");
        // 设置需要执行方法的类
        ctx.setRootObject(user);
        String value = expression.getValue(ctx, String.class);
        System.out.println(value);
    }

    public static class User{
        private String name;
        public User(String name){
            this.name = name;
        }
        public String getName(){
            return this.name;
        }
        public String getInput(){
            return "我叫:"+this.name;
        }
    }

}

上面demo的运行结果是“我叫:三侃”,也就是调用getInput方法的结果

3 自定义注解并通过SpEL获取参值

demo是用java的动态代理实现的,所以中间会多出一个需要将接口方法转换为实现类方法的步骤,springAOP的话可以忽略。
User类

public class User {
    private String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

自定义注解

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GetVal{

    String spel();
}

接口类和其实现类

public interface Run {

    void run(User user);
}

public class RunImpl implements Run {


    @GetVal(spel = "#user.name")
    @Override
    public void run(User user) {
        System.out.println(user.getName() + "正在跑");
    }
}

代理类

import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 *
 * @Author: dong
 * @Date: 2022/6/23 13:46
 */
public class RunProxy implements InvocationHandler {

    private Object target;

    public RunProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 由于demo的动态代理使用接口无法获取到形参名称,所以转换为实现类的方法对象
        method = target.getClass().getMethod(method.getName(), method.getParameterTypes());
        Object res = method.invoke(target, args);
        DefaultParameterNameDiscoverer defaultParameterNameDiscoverer = new DefaultParameterNameDiscoverer();
        // 这里参考的org.springframework.context.expression.MethodBasedEvaluationContext.lazyLoadArguments
        String[] parameterNames = defaultParameterNameDiscoverer.getParameterNames(method);

        GetVal annotation = method.getAnnotation(GetVal.class);
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(annotation.spel());
        StandardEvaluationContext ctx = new StandardEvaluationContext();
        // 填充表达式上下文环境
        for(int i=0;i<parameterNames.length;i++){
            ctx.setVariable(parameterNames[i],args[i]);
        }
        String value = expression.getValue(ctx, String.class);
        // 打印日志
        System.out.println(value+"执行了"+method.getName());
        return res;
    }
}

开始运行

public static void main(String[] args) {
        User user = new User("三侃");
        RunImpl runImpl = new RunImpl();
        RunProxy proxy = new RunProxy(runImpl);
        Run run = (Run) Proxy.newProxyInstance(runImpl.getClass().getClassLoader(), runImpl.getClass().getInterfaces(), proxy);
        run.run(user);
    }

可以看到控制台输处如下信息:

Connected to the target VM, address: '127.0.0.1:54442', transport: 'socket'
三侃正在跑
三侃执行了run
Disconnected from the target VM, address: '127.0.0.1:54442', transport: 'socket'

Process finished with exit code 0

结合springAOP+SpEL就能实现更加丰富的功能了

  • 8
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Security ,可以通过实现 PermissionEvaluator 接口来自定义权限验证逻辑。 首先,需要在配置文件开启自定义 PermissionEvaluator 的支持: ```xml <beans:bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler"> <beans:property name="permissionEvaluator" ref="customPermissionEvaluator" /> </beans:bean> <beans:bean id="customPermissionEvaluator" class="com.example.CustomPermissionEvaluator" /> ``` 接下来,实现 PermissionEvaluator 接口的 evaluate 方法: ```java public class CustomPermissionEvaluator implements PermissionEvaluator { @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { // 在此处编写自定义的权限验证逻辑 } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { // 在此处编写自定义的权限验证逻辑 } } ``` 在 evaluate 方法,第一个参数 authentication 表示当前用户的认证信息,第二个参数 targetDomainObject 表示要验证的对象,第三个参数 permission 表示要验证的权限。 如果要验证的对象是一个实体类,可以使用 @PreAuthorize 或 @PostAuthorize 注解配合 SpEL 表达式来进行验证: ```java @PreAuthorize("hasPermission(#entity, 'read')") public void doSomething(Entity entity) { // ... } ``` 在 SpEL 表达式,可以使用 #parameterName 来引用方法参数,也可以使用 #returnObject 来引用方法返回值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值