Spring AOP自定义注解

Spring AOP custom annotation

我正在尝试实现自定义注释和方面,这将在验证之前将路径变量插入到请求正文中。 现在看起来像这样...

@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 不再有效,问题可能出在连接点表达式中...我怎样才能让它在验证之前做建议然后验证?

@Before 建议中更改方法参数并不意味着有效。您应该使用 @Around 建议,以便在调用 thisJoinPoint.proceed() 之前更改参数。这是因为当调用 thisJoinPoint.getArgs() 时,您会得到基本类型参​​数的副本,您无法在前置通知中操作原始参数。在这种情况下,您很幸运想要操作对象类型,所以这就是它起作用的原因。使用 around-advice 将使您能够将全新的参数传递给方法或仅操作原始对象,您可以自由选择。

此外,您应该 - 只要有可能 - 使用 args() 以便将您感兴趣的方法参数绑定到建议参数,以便能够以非神秘和类型安全的方式与它们进行交互.创建局部变量并为其赋值完全不会影响相同类型的方法参数。为什么要这样做?

如果此解释对您来说不够全面,请随时提出后续问题。然后我也可以为您添加一些示例代码。


问题编辑后更新:

在更仔细地检查了你的代码之后,除了我今天早些时候在你的问题下的评论中的评论之外,不管你的方面代码的内容,你的实际问题是由 @Valid 注解在 方法执行之前执行。 IE。验证的不是方面完成其工作(填充目标对象中的成员字段)之后的状态,而是方面运行之前的状态。这实际上与 this question 中讨论的问题相同,另请参阅 M. Deinum 和我的解决方法建议:

  • 也许您想尝试完整的 AspectJ via LTW (load-time weaving) 并查看 call() 切入点而不是 Spring AOP 使用的隐式 execution() 切入点是否可以解决问题。您将编织到调用代码(方法调用)而不是被调用者(方法执行)本身。这很可能发生在执行验证之前。

  • 一种更像 Spring 的解决方法是使用 Spring 拦截器(M. Deinum 提到 HandlerInterceptor)而不是方面。还有一个 link 到 被别人。

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