Spring AOP 自定义注解 [英] Spring AOP custom annotation

查看:31
本文介绍了Spring AOP 自定义注解的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

@Aspect@成分公共类 AddParameterToFormAspect {@Before("@annotation(addParameterToForm)")public void addParameterToForm(JoinPoint joinPoint, AddParameterToForm addParameterToForm) {String form = addParameterToForm.form();String pathVariable = addParameterToForm.pathVariable();CodeSignature methodSignature = (CodeSignature) joinPoint.getSignature();列表<字符串>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();对象 formObject = methodArgs[formIndex];字段路径变量对象;尝试 {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")公共字符串测试(@PathVariable 字符串用户名,@RequestBody 用户用户){返回 user.getUsername();}

验证无效的控制器示例...

@PostMapping("/{domainCode}")@AddParameterToForm(pathVariable = "domainCode", form = "userAddForm")公共响应实体saveUserForDomain(@PathVariable(name="domainCode") String domainCode, @RequestBody @Valid final UserAddForm userAddForm, BindingResult results) {...}

向表单添加路径变量有效,但似乎@Valid 不再有效,问题可能出在连接点表达式中...如何在验证之前执行建议然后再验证?

解决方案

@Before 通知中更改方法参数并不意味着工作.在调用 thisJoinPoint.proceed() 之前,您应该使用 @Around 建议来更改参数.这是因为在调用 thisJoinPoint.getArgs() 时,您会获得原始类型参数的副本,您无法在预先通知中操作原始类型.您很幸运,在这种情况下想要操作对象类型,这就是它起作用的原因.使用 around-advice 可以让你将全新的参数传递给一个方法,或者只是操作原始对象,你可以自由选择.

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

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

<小时>

问题编辑后更新:

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

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

  • 一种更类似于 Spring 的解决方法是使用 Spring 拦截器(M. Deinum 提到 HandlerInterceptor)而不是方面.还有一个指向其他人的示例的链接.

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

I am trying to implement custom annotation and aspect which will insert path variable into request body before validation. For now it looks like this...

@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();
    }

}

Controller example of working annotation...

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

Controller example of validation not working...

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

Adding path variable to form works but it seems @Valid no longer works, problem is probably in join point expression... How can I make it to do advice before validation and then validate?

解决方案

Changing method parameters in a @Before advice is not meant to work. You should use an @Around advice in order to change parameters before calling thisJoinPoint.proceed(). This is because when calling thisJoinPoint.getArgs() you get copies of primitive type parameters, you cannot manipulate the originals in a before-advice. You are lucky that you want to manipulate object types in this case, so that is the reason it works. Using an around-advice would enable you to pass completely new arguments to a method or just manipulate the original objects, you are free to choose.

Furthermore, you should - whenever possible - use args() in order to bind your method arguments of interest to advice parameters in order to be able to interact with them in a non-cryptic and type-safe manner. Creating a local variable and assigning some value to it will not influence the method parameter of the same type at all. Why should it?

Feel free to ask follow-up questions if this explanation is not comprehensive enough for you. Then I could add some sample code for you, too.


Update after question edit:

After having inspected you code a bit more closely and in addition to my remarks earlier today in my comments under your question, disregarding the content of your aspect code, your actual problem is that the validation check cause by @Valid annotations is performed before the method is executed. I.e. what is validated is not the state after the aspect has done its job (populate member fields in your target objects) but the state before the aspect runs. It is actually the same problem discussed in this question, see also M. Deinum's and my suggestions how to solve it:

  • Maybe you want to try full AspectJ via LTW (load-time weaving) and see if a call() pointcut instead of the implicit execution() pointcut used by Spring AOP solves the problem. You would weave into the calling code (method calls) instead of the callee (method execution) itself. Chances are, that this happens before validation is performed.

  • A more Spring-like way to solve it is to use a Spring interceptor (M. Deinum mentions HandlerInterceptor) instead of an aspect. There is also a link to an example by someone else.

Having said that, I still recommend to refactor your code so as not to use reflection and matching strings on method parameter names or class member names. I think you could also get rid of your custom annotation by matching your pointcut on methods parameters with @RequestBody and @PathVariable annotations.

这篇关于Spring AOP 自定义注解的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆