AOP - 从拦截的 class 访问 protected/private 属性

AOP - accessing a protected/private attribute from the intercepted class

我正在从事一个项目,该项目基本上包含很多 运行 周期性的流程。每个进程都是一个不同的class,它扩展了我们创建的一个抽象class RunnableProcess,其中包含一个private属性Map<String, String> result和抽象方法run签名如下:

public abstract void run(Map processContext) throws IOException;

为了改进项目的模块化,我开始使用面向方面编程 (AOP) 拦截来自每个 RunnableProcessrun 调用。我还在学习AOP,到现在我的代码如下:

import static org.slf4j.LoggerFactory.getLogger;

import org.slf4j.Logger;
import process.RunnableProcess;
import java.util.Map;

public aspect ProcessRunInterceptor {
    private Logger logger;
    pointcut runProcess() : call(void RunnableProcess.run(Map));

    after(): runProcess() {
        logger = getLogger(thisJoinPoint.getClass());
        logger.info("process run successfully");
    }
}

它正在运行,但我想记录的信息不只是 "process run successfully"。我在上面提到的 result 属性中截获的 class 中有此信息。是否可以在不更改 RunnableProcess?

的实现的情况下在建议中访问它

我可以(不想,但如果这是唯一的选择...)将属性从 private 更改为 protected,但我不会将其更改为 public。我也不想为它创建一个 get 方法。

根据我对您 的回答,我将向您解释您可以做什么。一些提示:

  • 而不是 before()after() 你可以只使用 around().

  • 您不应在单例方面为流程实例使用私有成员,因为如果您在多个线程中有异步流程,则该成员可能会被覆盖。因此,您的方法不是线程安全的,您应该改用局部变量。

  • 您不应在 after() 通知中打印 "process run successfully",因为 after() 也会在异常后运行。所以你不能安全地假设进程 运行 成功,只是它 运行 而已。你应该写 "finished process" 或类似的。顺便说一句,如果你想区分成功的进程和以异常结束的进程,你可能需要查看切入点类型 after() returningafter() throwing().

  • 使用抽象基 class 而不直接定义成员 result 是没有意义的。如果您可以将其作为父级中的受保护成员,为什么还要将其添加为每个子 class 中的私有成员?我们这里仍然在做 OOP(当然除了 AOP 之外),对吧?优点是您可以使用基 class 直接从切面访问成员,就像您在切入点中所做的那样。

这里有一个 MCVE 给你:

处理 classes:

package de.scrum_master.app;

import java.io.IOException;
import java.util.Map;

public abstract class RunnableProcess {
  protected String result = "foo";

  public abstract void run(Map processContext) throws IOException;
}
package de.scrum_master.app;

import java.io.IOException;
import java.util.Map;

public class FirstRunnableProcess extends RunnableProcess {
  @Override
  public void run(Map processContext) throws IOException {
    System.out.println("I am #1");
    result = "first";
  }
}
package de.scrum_master.app;

import java.io.IOException;
import java.util.Map;

public class SecondRunnableProcess extends RunnableProcess {
  @Override
  public void run(Map processContext) throws IOException {
    System.out.println("I am #2");
    result = "second";
  }
}

驱动申请:

package de.scrum_master.app;

import java.io.IOException;

public class Application {
  public static void main(String[] args) throws IOException {
    new FirstRunnableProcess().run(null);
    new SecondRunnableProcess().run(null);
  }
}

看点:

在这里,您只需将 target() 对象绑定到切入点中的一个参数,并在两个建议中使用它。

package de.scrum_master.aspect;

import static org.slf4j.LoggerFactory.getLogger;

import org.slf4j.Logger;
import de.scrum_master.app.RunnableProcess;
import java.util.Map;

public privileged aspect ProcessRunInterceptorProtocol {
  pointcut runProcess(RunnableProcess process) :
    call(void RunnableProcess.run(Map)) && target(process);

  before(RunnableProcess process): runProcess(process) {
    Logger logger = getLogger(process.getClass());
    logger.info("logger = " + logger);
    logger.info("running process = " + thisJoinPoint);
  }

  after(RunnableProcess process): runProcess(process) {
    Logger logger = getLogger(process.getClass());
    logger.info("finished process = " + thisJoinPoint);
    logger.info("result = " + process.result);
  }
}

带有 JDK 日志记录的控制台日志(删除了一些噪音):

INFORMATION: logger = org.slf4j.impl.JDK14LoggerAdapter(de.scrum_master.app.FirstRunnableProcess)
INFORMATION: running process = call(void de.scrum_master.app.FirstRunnableProcess.run(Map))
I am #1
INFORMATION: finished process = call(void de.scrum_master.app.FirstRunnableProcess.run(Map))
INFORMATION: result = first
INFORMATION: logger = org.slf4j.impl.JDK14LoggerAdapter(de.scrum_master.app.SecondRunnableProcess)
INFORMATION: running process = call(void de.scrum_master.app.SecondRunnableProcess.run(Map))
I am #2
INFORMATION: finished process = call(void de.scrum_master.app.SecondRunnableProcess.run(Map))
INFORMATION: result = second