简体   繁体   English

如何使用 Java 自定义注释和 Spring AOP 设置属性值?

[英]How to set a property value using Java custom annotation and Spring AOP?

I would like to use custom Java annotation to insert a value in a private class property using Spring AOP (and/or AspectJ).我想使用自定义 Java 注释使用 Spring AOP(和/或 AspectJ)在私有 class 属性中插入一个值。 Quick example:快速示例:

MyAnnotation.java: MyAnnotation.java:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface MyAnnotation {
}

MyController.java: MyController.java:

public class MyControllerImpl implements MyController {

    ...
    
    @MyAnnotation
    private String var1;

    @Override
    public String getVarExample() {
       // imagine this is a REST API that gets called on @GET
       // request and returns a string

       System.out.println(this.var1); // <-- I'd like this to be "helloworld"
                                    // this is just for illustration
                                    // of course, I will want to do 
                                    // something more meaningful with
                                    // the 'var1' variable
       return "ok"; <- unimportant for this example
    }
    ...

MyAspect.java: MyAspect.java:

@Aspect
@Component
public class MyAspect {

    @Pointcut("@annotation(com.mypackage.annotation.MyAnnotation)")
    public void fieldAnnotatedWithMyAnnotation() {
        
    }

    @Around("fieldAnnotatedWithMyAnnotation()")
    public Object enrichVar1(ProceedingJoinPoint pjp) throws Throwable {
        
        // problem #1 - the program never enters here
        // problem #2 - I need to figure out how to set up the var1 here
        //              to "helloworld" , how?
        return pjp.proceed();
    }
    ...
}

What would I like to happen?我希望发生什么?

I will call and get into the getVarExample() and after it returns I would like to see "helloworld" in console or log.我将调用并进入getVarExample() ,在它返回后,我想在控制台或日志中看到“helloworld” I would like to somehow set the var1 to a custom value using AOP.我想以某种方式使用 AOP 将var1设置为自定义值。 Any property variable that will be annotated with @MyAnnotation will be set to "helloworld".将使用@MyAnnotation注释的任何属性变量都将设置为“helloworld”。 I hope the example above is clear.我希望上面的例子很清楚。

What have I tried?我尝试了什么?

I made sure there is no typo in the package names, also fiddled with different AOP advice annotations like @Around and @Before .我确保 package 名称中没有错字,还摆弄了不同的 AOP 建议注释,如@Around@Before I also tried different targets in the MyAnnotation and ended up with ElementType.FIELD which should be correct.我还在MyAnnotation中尝试了不同的目标,并以ElementType.FIELD结束,这应该是正确的。

Can you help me to get it working?你能帮我让它工作吗?

I know this can be done, but couldn't find any working example online.我知道这可以做到,但在网上找不到任何工作示例。 Again, I would like to see 2 answers:同样,我想看到 2 个答案:

1. How to get the pointcut to trigger on MyController entrance? 1、如何让切入点在MyController入口触发? I want to catch a breakpoint inside the enrichVar1(..) method of the MyAspect class.我想在MyAspect class 的enrichVar1(..)方法中捕获一个断点。

2. How can I modify the annotated var1 value in enrichVar1(..) method of the MyAspect class? 2.如何修改MyAspect class的MyAspect enrichVar1(..)方法中带注释的var1值?

I don't know what I am doing wrong.我不知道我做错了什么。 Any help will be greatly appreciated.任何帮助将不胜感激。 Thank you!谢谢!

The AOP is set up correctly in my project. AOP 在我的项目中设置正确。 I know that because I am already using AOP for different things (logging for example).我知道这一点,因为我已经将 AOP 用于不同的事情(例如日志记录)。

Update #1:更新#1:

Please, note there are not getters or setters for the var1 private variable.请注意, var1私有变量没有 getter 或 setter。 The variable will be only used within the MyControllerImpl .该变量将仅在MyControllerImpl中使用。 To illustrate this better I changed the return value of the getVarExample .为了更好地说明这一点,我更改了getVarExample的返回值。

As it goes from Spring docs Spring AOP does support Spring beans' method execution join points.Spring 文档Spring 开始,AOP 确实支持 Spring bean 的方法执行连接点。 To make field access join points work you need to use AspectJ's backend with load time weaving for AOP.要使字段访问连接点工作,您需要使用AspectJ 的后端和 AOP 的加载时间编织

But for your case it's not required to use field join points, you can put your annotation on the getter and this should work.但是对于您的情况,不需要使用字段连接点,您可以将注释放在 getter 上,这应该可以。

Like I said in my comment:就像我在评论中说的:

The pointcut designator @annotation() intercepts annotated methods, not annotated fields.切入点指示符@annotation()拦截带注释的方法,而不是带注释的字段。 For that, native AspectJ has get() and set() .为此,原生 AspectJ 具有get()set() Ie, the pointcut would also need to be changed if migrating to AspectJ.即,如果迁移到 AspectJ,也需要更改切入点。 But I agree that sticking to Spring AOP and annotating getter methods instead of fields is probably enough here.但我同意坚持 Spring AOP 并注释 getter 方法而不是字段可能就足够了。

But because you insist that you want to keep the controller class unchanged, here is the native AspectJ solution.但是因为您坚持要保持 controller class 不变,所以这里是原生 AspectJ 解决方案。 Please read chapter Using AspectJ with Spring Applications for how to configure that with @EnableLoadTimeWeaving and JVM parameter -javaagent:/path/to/aspectjweaver.jar .请阅读将 AspectJ 与 Spring 应用程序一起使用一章,了解如何使用@EnableLoadTimeWeaving和 JVM 参数-javaagent:/path/to/aspectjweaver.jar进行配置

In order to demonstrate that this solution really does work independently of Spring, I am using no Spring classes or annotations at all, only POJOs and native AspectJ.为了证明这个解决方案确实独立于 Spring 工作,我根本不使用 Spring 类或注释,只使用 POJO 和本机 AspectJ。 You can simply do the same within your Spring application.您可以在 Spring 应用程序中简单地执行相同的操作。 Please note that native AspectJ aspects do not need @Component annotations, in contrast to Spring AOP aspects.请注意,与 Spring AOP 方面相比,本机 AspectJ 方面不需要@Component注释。

package de.scrum_master.app;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface MyAnnotation {}
package de.scrum_master.app;

public interface MyController {
  String getVarExample();
}
package de.scrum_master.app;

public class MyControllerImpl implements MyController {
  @MyAnnotation
  private String var1;

  @Override
  public String getVarExample() {
    System.out.println(this.var1);
    return "ok";
  }
}
package de.scrum_master.app;

public class Application {
  public static void main(String[] args) {
    MyController myController = new MyControllerImpl();
    myController.getVarExample();
  }
}
package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class MyAspect {

  @Pointcut("get(@de.scrum_master.app.MyAnnotation * *)")
  public void fieldAnnotatedWithMyAnnotation() {}

  @Around("fieldAnnotatedWithMyAnnotation()")
  public Object enrichVar1(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println(pjp);
    return "helloworld";
  }
}

When running Application , the console log is going to be:运行Application时,控制台日志将是:

get(String de.scrum_master.app.MyControllerImpl.var1)
helloworld

The AspectJ manual explains the syntax of field get and set join point signatures and field patterns . AspectJ 手册解释了字段获取和设置连接点签名字段模式的语法。


Note: I think that your use case might be a hack rather than a valid application design.注意:我认为您的用例可能是 hack 而不是有效的应用程序设计。 You ought to refactor rather than hack into an application like this.你应该重构而不是侵入这样的应用程序。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM