从ProceedingJoinPoint中获取参数值

11
在我的请求中,我有一个名为“accessToken”的参数,如何从ProceedingJoinPoint获取请求参数值?
public Object handleAccessToken(ProceedingJoinPoint joinPoint) throws Throwable { 
    final Signature signature = joinPoint.getStaticPart().getSignature();
    if (signature instanceof MethodSignature) {
        final MethodSignature ms = (MethodSignature) signature;
        String[] params = ms.getParameterNames();
        for (String param : params) {
            System.out.println(param);
            // here how do i get parameter value using param ?
        }
    }
}

调用方法:

public MyResponse saveUser(
    @RequestParam("accessToken") String accessToken,
    @RequestBody final UserDto userDto
) {
    // code 
}

我想在AOP中获取这个访问令牌。

提前感谢。


1
请提供更多信息,例如切面切入点和要拦截的代码示例,参数类型和参数位置(例如从方法签名的左/右边开始计数的第一个、第二个、第三个参数)。然后我将提供一个优雅的答案,使用参数绑定而不是丑陋的 getArgs() 或反射代码。 - kriegaex
谢谢回复,我正在尝试验证accessToken。在我的REST应用程序中,我将accessToken与请求正文一起发送,例如{"accessToken":"myValue"},我需要从ProceedingJoinPoint中检索该访问令牌。 - Shamseer PC
1
我想看代码,而不是散文式的描述。我是AspectJ专家,而不是Spring专家。 - kriegaex
我刚刚编辑了我的问题,添加了调用方法的源代码。 - Shamseer PC
请在建议上展示切入点,以及它们所属的类/方面名称和包。请不要只分享片段,最好给我一个完整的大局观,最好是一个SSCCE。请还回答我之前的问题,关于参数位置,以及告诉我是否可以在每个方法中有多个带有访问令牌注释的参数。 - kriegaex
我还想知道如何安全地识别参数。我猜测是通过注释参数,即@RequestParameter中的“accessToken”,而不是通过变量名来识别,因为变量名很容易改变,似乎不太可靠。魔术参数名称是一个坏主意。 - kriegaex
2个回答

30

好的,Shamseer,我有一点空闲时间,所以我尝试回答你的问题,而不需要你回答我在评论中提出的所有问题。我采用的方法是,我将不会使用参数名称,而是尝试匹配带有注释@RequestParam("accessToken")的参数,即我将匹配注释类型和值与“accessToken”的魔术名称相匹配,而不是一个方法参数名称,因为该名称可能由于某个不知道您方面的人进行简单重构,由于在编译期间从类文件中剥离调试信息或由于混淆而发生变化。

下面是一些自洽的示例代码,已针对AspectJ进行了测试,而不是Spring AOP,但后者的语法是前者语法的子集:

带有main方法的样本类:

这里有三个方法,所有这些方法都在其中一个参数上有一个@RequestParam注释,但只有两个具有"accessToken"的魔术值。无论参数类型如何(一个String和一个int),它们都应该被匹配,但是具有@RequestParam("someParameter")的那个不应该匹配。严格来说,所有方法执行都被匹配,但是运行时反射消除了不需要的方法。如果您的注释位于类或方法级别或参数类型上,则可以在切入点中直接匹配它们而无需反射,但在参数注释的情况下,这超出了AspectJ当前(v1.8.4)的能力,我们不得不遗憾地使用反射。

package de.scrum_master.app;

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

public class MyResponse {
    public MyResponse saveUser(
        @RequestParam("accessToken") String accessToken,
        @RequestBody final UserDto userDto
    ) {
        return this;
    }

    public MyResponse doSomething(
        @RequestParam("someParameter") String text,
        @RequestBody final UserDto userDto
    ) {
        return this;
    }

    public MyResponse doSomethingElse(
        @RequestParam("accessToken") int number
    ) {
        return this;
    }

    public static void main(String[] args) {
        MyResponse myResponse = new MyResponse();
        myResponse.doSomething("I am not a token", new UserDto());
        myResponse.saveUser("I am a token", new UserDto());
        myResponse.doSomethingElse(12345);
    }
}

虚拟帮助类,用于使代码编译:

package de.scrum_master.app;

public class UserDto {}

方面:

请注意,我的通用切入点 execution(* *(..)) 仅作为示例。您应该将其缩小到实际想要匹配的方法。

package de.scrum_master.aspect;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.bind.annotation.RequestParam;

@Aspect
public class AccessTokenAspect {
    @Around("execution(* *(..))")
    public Object handleAccessToken(ProceedingJoinPoint thisJoinPoint) throws Throwable {
        System.out.println(thisJoinPoint);
        Object[] args = thisJoinPoint.getArgs();
        MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getStaticPart().getSignature();
        Method method = methodSignature.getMethod();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        assert args.length == parameterAnnotations.length;
        for (int argIndex = 0; argIndex < args.length; argIndex++) {
            for (Annotation annotation : parameterAnnotations[argIndex]) {
                if (!(annotation instanceof RequestParam))
                    continue;
                RequestParam requestParam = (RequestParam) annotation;
                if (! "accessToken".equals(requestParam.value()))
                    continue;
                System.out.println("  " + requestParam.value() + " = " + args[argIndex]);
            }
        }
        return thisJoinPoint.proceed();
    }
}

控制台输出:

execution(void de.scrum_master.app.MyResponse.main(String[]))
execution(MyResponse de.scrum_master.app.MyResponse.doSomething(String, UserDto))
execution(MyResponse de.scrum_master.app.MyResponse.saveUser(String, UserDto))
  accessToken = I am a token
execution(MyResponse de.scrum_master.app.MyResponse.doSomethingElse(int))
  accessToken = 12345

请参见此答案,它回答了一个相关但比较简单的问题,其中包含类似的代码。


也许你想重构代码,将访问令牌作为自己的类而不是简单的字符串。这样,你可以以类型安全的方式进行工作,并且还可以轻松匹配基于参数类型而不是变量名或注释值的切入点。 - kriegaex
@kriegaex 我想为上面的代码创建一个通用方法,但是我遇到了问题,因为我无法将 AnnotationType 传递到我的方法中以便在该行中使用 if (!(annotation instanceof RequestParam {AnnotationType}))你的代码如何将 RequestParam 作为参数传递? - KNDheeraj
对于四年前的问题进行评论并不是一个好的工具来提出和回答新的问题,无论它们是否相关。我建议您创建一个新的问题,并使用 MCVE 链接到我的答案,并准确地解释您想要做什么以及在哪里/如何失败。谢谢。 - kriegaex
如果您想要 argNames,请参考 https://dev59.com/eVcP5IYBdhLWcg3wRoEI#49155868,类似于这样的代码 String[] argNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames(); - Anand Rockzz
1
我知道这一点,并且我已经解释过这是一个坏主意,因为结果会导致(a)糟糕的设计,(b)脆弱(如果有人重命名方法参数,则会出现错误),(c)在这种情况下是不必要的,因为我们可以简单地匹配参数的注释,(d)如果没有调试信息编译,则无法工作。 - kriegaex

13

为了获取作为参数传递给方法的参数,你可以尝试类似以下的方法:

Object[] methodArguments = joinPoint.getArgs();

请原谅我的问题,我不知道我的问题是否正确? getArgs() 方法是否使用反射来检索参数? - fatemeakbari

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接