如何通过@aspect为字段设置值?

6za6bjd0  于 2021-07-23  发布在  Java
关注(0)|答案(1)|浏览(510)

我有一个调用方法的服务。在这个服务中,还有一个字段需要通过aspect使用自定义注解来赋值
服务

@Service
public class TestServiceImpl implements TestService {

    @MyAnnotation(value = "someName")
    private Boolean isActive;

    @Override
    public String success(String name) {
        return (isActive) ? "Yes" : "No";
    }
}

注解

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.FIELD)
public @interface MyAnnotation{
    String value() default "";
}

通过在注解中传递的值,数据库中应该有一个记录具有is\u active字段,并且该值应该分配给服务中的isactive字段

s4n0splo

s4n0splo1#

如果您想坚持使用springaop或更灵活的应用程序设计,我还建议您实现另一个应用程序设计。例如,为什么不注解 success(..) 方法并在 @Around 根据注解值返回数据库配置值的通知?
如果您绝对坚持注解字段,那么您必须同意使用加载时编织(ltw)从SpringAOP切换到本机aspectj,因为aspectj可以通过 get() 以及 set() 切入点。但是,如果字段是私有的,则需要特权方面,并且不能使用注解样式语法。
下面是我的完整mcve,在spring之外使用aspectj向您展示了它是如何基本完成的:
带有配置键的标记注解:

package de.scrum_master.app;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

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

@Retention(RUNTIME)
@Target(FIELD)
public @interface Marker {
  String value() default "";
}

驱动程序应用程序:

package de.scrum_master.app;

import java.io.PrintStream;

public class Application {
  @Marker int number;
  @Marker("settingA") private boolean isActive;
  @Marker("settingB") private boolean isNice;

  public String success(String name) {
    return isActive ? "Yes" : "No";
  }

  public String nice(String name) {
    return isNice ? "Yes" : "No";
  }

  public static void main(String[] args) {
    Application app = new Application();
    app.number = 11;
    PrintStream out = System.out;
    out.println(app.number);
    out.println(app.success("foo"));
    out.println(app.nice("bar"));
  }

}

外观:
为了更好地理解细节,请阅读评论。建议#1-#4更像是使用的一般操作指南 get() 以及 set() ,最后一条建议(滚动到末尾)是您在这个特定案例中实际要求的。

package de.scrum_master.aspect;

import de.scrum_master.app.Application;
import de.scrum_master.app.Marker;

/**
 * Please note 'privileged'. This only works in native AspectJ syntax, see
 * https://www.eclipse.org/aspectj/doc/next/adk15notebook/ataspectj-aspects.html:
 * "Privileged aspects are not supported by the annotation style."
 */
public privileged aspect MyAspect {
  /**
   * During field write access, there is no generic way to get the old field value.
   * But we can get the new value via 'args' and manipulate it when proceeding. 
   */
  void around(int newValue) :
    set(@Marker * *) && args(newValue)
  {
    int newValueUpdated = 3 * newValue;
    System.out.println(thisJoinPoint + " AROUND: newV = " + newValue + " -> " + newValueUpdated);
    proceed(newValueUpdated);
  }

  /**
   * If we exactly know which field to target, we can of course access its old value directly by binding the
   * target instance to an advice parameter or by just directly accessing a static field.
   * But if the field is private, the aspect must be privileged.
   */
  void around(int newValue, Application application) :
    set(int Application.number) && args(newValue) && target(application)
  {
    int oldValue = application.number;
    int newValueUpdated = newValue + 7;
    System.out.println(thisJoinPoint + " AROUND: " + "oldV = " + oldValue + ", newV = " + newValue + " -> " + newValueUpdated);
    proceed(newValueUpdated, application);
  }

  /**
   * During field read access, it is easy to get the old field value by just proceeding.
   * A new value can be set by returning it from the advice.
   * 
   * '!within(MyAspect)' avoids triggering the advice when reading the field from within this aspect.
   */
  int around():
    get(@Marker int *) && !within(MyAspect)
  {
    int oldValue = proceed();
    int newValue = oldValue + 1000;
    System.out.println(thisJoinPoint + " AROUND:  " + oldValue + " -> " + newValue);
    return newValue;
  }

  /**
   * For just intercepting the newly set field value, 'after() returning' can be used.
   * For this simple case we do not need an 'around()' advice.
   * A new value cannot be set here, of course, because that has already happened.
   * 
   * Please note how this advice also captures the access to System.out in the application.
   */
  after() returning(Object newValue) :
    get(* *) && !within(MyAspect)
  {
    System.out.println(thisJoinPoint + " AFTER RET:  " + newValue);
  }

  /**
   * After the more general part, this advice actually answers the question under
   * https://stackoverflow.com/q/66563533/1082681:
   * 
   * We look for annotated boolean fields (can be adjusted to other types easily),
   * get the annotation value from the value bound via '@annotation()' and
   * use the value to fetch the desired config value from the DB.
   */
  boolean around(Marker marker):
    get(@Marker boolean *) && !within(MyAspect) && @annotation(marker)
  {
    System.out.println(thisJoinPoint + " Fetching config value for " + marker + " from DB");
    return getConfigValueFromDB(marker.value());
  }

  /**
   * DB access mock-up
   */
  private boolean getConfigValueFromDB(String key) {
    switch (key) {
      case "settingA": return true;
      case "settingB": return false;
      default: return false;
    }
  }
}

控制台日志:

set(int de.scrum_master.app.Application.number) AROUND: newV = 11 -> 33
set(int de.scrum_master.app.Application.number) AROUND: oldV = 0, newV = 33 -> 40
get(PrintStream java.lang.System.out) AFTER RET:  java.io.PrintStream@279f2327
get(int de.scrum_master.app.Application.number) AROUND:  40 -> 1040
get(int de.scrum_master.app.Application.number) AFTER RET:  1040
1040
get(boolean de.scrum_master.app.Application.isActive) Fetching config value for @de.scrum_master.app.Marker(value=settingA) from DB
Yes
get(boolean de.scrum_master.app.Application.isNice) Fetching config value for @de.scrum_master.app.Marker(value=settingB) from DB
No

免责声明:我想向您展示aspectj的强大功能。你可以这样做,但正如我之前所说,这并不意味着你应该这样做。通过注解访问器方法而不是字段,您可以轻松地重新设计应用程序,以使其与springaop一起工作。或者像alexander katsenelenbogen所说的,有一些方法可以完全不用aop来实现这一点,尽管我认为aop是一种合适的方式,因为从外部源读取配置值是一个跨领域的问题。

相关问题