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

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

问题描述

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

@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通知中的方法参数不起作用.在调用thisJoinPoint.proceed()之前,应该使用@Around建议来更改参数.这是因为调用thisJoinPoint.getArgs()时会获得原始类型参数的副本,因此无法在事先通知中操作原始文件.您很幸运在这种情况下想要操作对象类型,因此这就是它起作用的原因.使用环绕建议可以使您将全新的参数传递给方法,或者仅操纵原始对象,即可自由选择.

此外,您应该-尽可能使用args(),以便将感兴趣的方法参数绑定到建议参数,以便能够以非加密和类型安全的方式与它们进行交互.创建局部变量并为其分配一些值根本不会影响相同类型的方法参数.为什么要这样?

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


问题编辑后更新:

在仔细检查了您的代码之后,除了今天早些时候在我对问题的评论中的评论之外,无视方面代码的内容,您的实际问题是由@Valid注释引起的验证检查是在该方法执行之前之前执行. IE.验证的内容不是方面完成其工作后的状态(填充目标对象中的成员字段),而是方面运行之前的状态.实际上,这是此问题中讨论的相同问题,另请参见Deinum先生和我的解决方法: >

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

  • 一种更类似于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天全站免登陆