Spring AOP自定义注解

3

我正试图实现自定义注解和切面,它们将在验证之前将路径变量插入请求体中。目前看起来是这样的...

@Aspect
@Component
public class AddParameterToFormAspect {

@Before("@annotation(addParameterToForm)")
public void addParameterToForm(JoinPoint joinPoint, AddParameterToForm addParameterToForm) {
    String form = addParameterToForm.form();
    String pathVariable = addParameterToForm.pathVariable();
    CodeSignature methodSignature = (CodeSignature) joinPoint.getSignature();
    List<String> methodParamNames = Arrays.asList(methodSignature.getParameterNames());
    int formIndex = 0;
    int pathVariableIndex = 0;

    for(String s : methodSignature.getParameterNames()) {
        if(s.equals(form)) {
            formIndex = methodParamNames.indexOf(s);
        }
        if(s.equals(pathVariable)) {
            pathVariableIndex = methodParamNames.indexOf(s);
        }
    }

    Object[] methodArgs = joinPoint.getArgs();
    Object formObject = methodArgs[formIndex];
    Field pathVariableObject;

    try {
        pathVariableObject = formObject.getClass().getDeclaredField(pathVariable);
        pathVariableObject.setAccessible(true);
        pathVariableObject.set(formObject, methodArgs[pathVariableIndex]);
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

}

控制器工作注解示例...

@PostMapping("/test/{username}")
@AddParameterToForm(pathVariable = "username", form = "user")
public String test(@PathVariable String username, @RequestBody User user) {
    return user.getUsername();
}

控制器示例中验证不起作用的问题...

@PostMapping("/{domainCode}")
@AddParameterToForm(pathVariable = "domainCode", form = "userAddForm")
public ResponseEntity<UserDto> saveUserForDomain(@PathVariable(name="domainCode") String domainCode, @RequestBody @Valid  final UserAddForm userAddForm, BindingResult results) {...}

在表单中添加路径变量可以工作,但似乎@Valid不再起作用了,问题可能出在连接点表达式上...我该如何使其在验证之前执行advice,然后再进行验证?

天啊,我刚刚看了一下你的代码。你真的是通过反射来匹配方法参数名称编码在注释中吗?这样做很慢,不安全,并且在重构应用程序时会破坏它并只在运行时注意到。除了最初的AOP问题外,此设计有缺陷。不要这样做!请解释一下你想实现什么。我100%肯定一定有更好的解决方案。这段代码太牵强了,甚至连你自己三个月后都无法理解它。 - kriegaex
情况变得更糟:您还将注释参数与类中的字段名称(例如“User”和“AddParameterToForm”)进行匹配。这是维护的噩梦。避免依赖参数或字段名称以及反射。 - kriegaex
我有另一个问题:如果您已经在进行反射操作,那么使用参数注释(例如@RequestBody@PathVariable)匹配是否比使用自己的自定义注释冗余编码相同的信息更好呢? - kriegaex
是的,现在想起来,这真的很糟糕,但至少它起作用了。无论如何,我将尝试在没有自定义注释的情况下解决问题,使用过滤器或拦截器。感谢您的所有帮助 :) - Mido
1个回答

1
在@Before通知中更改方法参数并不会生效。您应该使用@Around通知来在调用thisJoinPoint.proceed()之前更改参数。这是因为在调用thisJoinPoint.getArgs()时,您获得的是基本类型参数的副本,您无法在before-advice中操作原始参数。您很幸运,因为在这种情况下,您想要操作对象类型,所以它可以工作。使用around-advice将使您能够将全新的参数传递给方法或仅操作原始对象,您可以自由选择。
此外,尽可能使用args()将您感兴趣的方法参数绑定到advice参数,以便以非加密和类型安全的方式与它们交互。创建一个局部变量并分配一些值给它不会影响相同类型的方法参数。为什么要这样做呢?
如果这个解释对您来说不够全面,请随时提出跟进问题。那么我也可以为您添加一些示例代码。

编辑问题后的更新:

在更仔细地检查了您的代码之后,除了我今天在您的问题下评论中提到的内容外,不考虑您方面代码的内容,您实际的问题是由@Valid注释引起的验证检查是在方法执行之前执行的。也就是说,被验证的不是方面完成其工作(填充目标对象中的成员字段)后的状态,而是方面运行之前的状态。这实际上是this question中讨论的相同问题,请参见M. Deinum和我的建议如何解决它:

  • 也许您想尝试完整的AspectJ通过LTW(装载时编织),看看使用call()切入点而不是Spring AOP使用的隐式execution()切入点是否解决了问题。您将编织到调用代码(方法调用)中,而不是被调用方(方法执行)本身。很有可能,在执行验证之前发生这种情况。

  • 更符合Spring风格的解决方法是使用Spring拦截器(M.Deinum提到了HandlerInterceptor),而不是方面。还有一个他人的示例的链接。

话虽如此,我仍然建议重构代码,以避免使用反射和匹配方法参数名称或类成员名称的字符串。我认为您还可以通过与@RequestBody@PathVariable注释匹配方法参数来摆脱自定义注释。


我更新了方面(请参见原帖),所以现在它适用于任何方法...我已经尝试使用Around,但是我的注释甚至不起作用,而使用Before时则不同。你能否向我的代码添加示例,以便我可以看到如何实现Around? - Mido
我也尝试了大多数解决方案,例如添加EnableAspectJAutoProxy(proxyTargetClass = true),更改为public Object addParamterToForm...然后返回proceedingJoinPoint.proceed(),但似乎都没有起作用... - Mido
我已经更新了我的答案,提供了关于@Valid问题和可能的解决方案的信息。 - kriegaex

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