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

2022-07-19 00:00:00 annotations spring java aspectj spring-aop

我想使用定制的Java注释在使用Spring AOP(和/或AspectJ)的私有类属性中插入值。快速示例:

MyAnnotation.java:

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

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:

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

我希望发生什么?

我将调用并进入getVarExample(),在它返回后,我希望在控制台或日志中看到";HelloWorld";。我想以某种方式使用AOP将var1设置为自定义值。将使用@MyAnnotation注释的任何属性变量都将设置为";HelloWorld";。我希望上面的例子是清楚的。

我尝试了什么?

我确保包名称中没有拼写错误,还摆弄了不同的AOP建议注释,如@Around@Before。我还尝试了MyAnnotation中的不同目标,结果是ElementType.FIELD应该是正确的。

您能帮助我使其正常工作吗?

我知道这是可以做到的,但在网上找不到任何可用的示例。同样,我希望看到两个答案:

1.如何让切入点在MyController入口触发?我想在MyAspect类的enrichVar1(..)方法内捕获断点。

2.如何修改MyAspect类的enrichVar1(..)方法中带注释的var1值?

我不知道我做错了什么。任何帮助都将不胜感激。谢谢!

在我的项目中正确设置了AOP。我之所以知道这一点,是因为我已经将AOP用于不同的事情(例如日志记录)。

更新#1:

请注意,var1私有变量没有getter或setter。该变量将仅在MyControllerImpl中使用。为了更好地说明这一点,我更改了getVarExample的返回值。


解决方案

如我在评论中所说:

切入点指示符@annotation()拦截带注释的方法,而不是带注释的字段。为此,本机AspectJ有get()set()。也就是说,如果迁移到AspectJ,切入点也需要更改。但我同意,坚持使用Spring AOP并注释getter方法而不是字段在这里可能就足够了。

但是因为您坚持要保持控制器类不变,所以这里是本机AspectJ解决方案。请阅读Using AspectJ with Spring Applications一章,了解如何使用@EnableLoadTimeWeaving和JVM参数-javaagent:/path/to/aspectjweaver.jar进行配置。

为了证明该解决方案确实独立于Spring工作,我根本没有使用Spring类或注释,只使用了POJO和本机AspectJ。您只需在您的Spring应用程序中执行相同的操作。请注意,与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";
  }
}

运行Application时,控制台日志为:

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

AspectJ手册解释field get and set join point signatures和field patterns的语法。


注意:我认为您的用例可能是黑客设计,而不是有效的应用程序设计。您应该重构而不是侵入这样的应用程序。

相关文章