将日志信息放入方法中:使用 AspectJ,Spring

Placing log information inside a method: Using AspectJ , Spring

我是 AOP 的新手,我开始知道它有助于分离 crosscutting 关注点,并且似乎是一个很好的特性,它为 OOPs 编程增添了魅力。

一如既往,我找到的经典示例是 "logging",其中使用 AOP,可以使用 AOP 完成日志记录。

考虑这种情况:

public void doSomething(String name, Object someObj) {

   logger.info("Just entered doSomething() method));
   int result;

   // Some complex code

   if(result != 0) {
       logger.severe("There is some issues, fix it...");
       // some other code
  }

 logger.trace("Did some more work, value is " +value);

 // Other business code..

logger.info("Returning from method...");
} 

现在,参考在线documentation/tutorial,"logging advice"的用例是我们可以从代码中删除日志代码,"logging advice"将进行日志记录,例如在方法的进入、返回过程中,使用注解。

在上面的例子中,我同意使用@Before,@After "advice"可以帮助方法调用的进入,返回。

我对 AOP 如何帮助方法内部的记录器感到困惑,理想情况下可以在我们在某个时间点捕获信息的任何地方使用。

我提到了 this SO question ,但没有弄清楚 AOP 如何帮助解决这种情况。

谁能帮我理解一下?

简短的回答是:AOP 并不意味着研究您的方法,因为方法一直在重构,应该被视为黑盒。

所以 Spring AOP 和 AspectJ 都不会为您做您期望的事情。 AOP的思想是实现横切关注点。日志记录只是其中一个问题。如果您认为需要方法内日志记录,您仍然可以手动进行。但是,如果干净的代码对您来说很重要,您可以重构代码以使其更易于维护(并且对日志记录更友好)。方法应该简短,输入参数不要太多,也不要太复杂。

因此,您可以将复杂的意大利面条代码方法分解为一组更小的方法,甚至可以提取新的 classes 并从您的方法中使用它们。我为您的代码做了这个(见下文)。此外,returning 0 或 -1 或其他而不是抛出异常不是 OOP,而是 C 风格的编程。因此,不是根据 return 值记录问题,而是根据抛出的异常记录它们并根据您的应用程序逻辑处理这些异常(或者让它们升级,如果存在致命错误)。我的示例代码也表明了这一点。

示例代码,迭代 1

此示例将与 AspectJ 一起很好地工作,因为 AspectJ 不是基于委派动态代理,因此在自调用方面没有问题,例如class.

中的内部方法调用
package de.scrum_master.app;

public class UnexpectedResultException extends Exception {
  private static final long serialVersionUID = 1L;

  public UnexpectedResultException(String message) {
    super(message);
  }
}

如您所见,我从您的复杂方法中提取了一些方法。为了向您展示更多日志输出,我什至通过在 for 循环中多次调用执行复杂操作的方法,再次将复杂性添加到 doSomething(..)

package de.scrum_master.app;

import java.util.Random;

public class Application {
  public void doSomething(String name, Object someObj) {
    int result = new Random().nextInt(100);
    for (int counter = 0; counter < 5; counter++) {
      try {
        result = doComplexThing(result + 1);
      } catch (UnexpectedResultException unexpectedResultException) {
        result = 4;
      }
    }
    result = doSomeMoreWork(result);
    otherBusinessCode(result);
  }

  public int doComplexThing(int input) throws UnexpectedResultException {
    if (input % 2 == 0)
      throw new UnexpectedResultException("uh-oh");
    return input % 5;
  }

  public int doSomeMoreWork(int input) {
    return input * input;
  }

  public void otherBusinessCode(int input) {}

  public static void main(String[] args) {
    Application application = new Application();
    application.doSomething("John Doe", new Integer(11));
  }
}

日志记录方面可能如下所示:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class LoggingAspect {
  @Pointcut("within (de.scrum_master.app..*) && execution(* *(..))")
  private void loggingTargets() {}

  @Before("loggingTargets()")
  public void logEnterMethod(JoinPoint thisJoinPoint) {
    System.out.println("ENTER " + thisJoinPoint);
  }

  @AfterReturning(pointcut = "loggingTargets()", returning = "result")
  public void logExitMethod(JoinPoint thisJoinPoint, Object result) {
    System.out.println("EXIT  " + thisJoinPoint + " -> return value = " + result);
  }

  @AfterThrowing(pointcut = "loggingTargets()", throwing = "exception")
  public void logException(JoinPoint thisJoinPoint, Exception exception) {
    System.out.println("ERROR " + thisJoinPoint + " -> exception = " + exception);
  }
}

控制台日志如下所示:

ENTER execution(void de.scrum_master.app.Application.main(String[]))
ENTER execution(void de.scrum_master.app.Application.doSomething(String, Object))
ENTER execution(int de.scrum_master.app.Application.doComplexThing(int))
ERROR execution(int de.scrum_master.app.Application.doComplexThing(int)) -> exception = de.scrum_master.app.UnexpectedResultException: uh-oh
ENTER execution(int de.scrum_master.app.Application.doComplexThing(int))
EXIT  execution(int de.scrum_master.app.Application.doComplexThing(int)) -> return value = 0
ENTER execution(int de.scrum_master.app.Application.doComplexThing(int))
EXIT  execution(int de.scrum_master.app.Application.doComplexThing(int)) -> return value = 1
ENTER execution(int de.scrum_master.app.Application.doComplexThing(int))
ERROR execution(int de.scrum_master.app.Application.doComplexThing(int)) -> exception = de.scrum_master.app.UnexpectedResultException: uh-oh
ENTER execution(int de.scrum_master.app.Application.doComplexThing(int))
EXIT  execution(int de.scrum_master.app.Application.doComplexThing(int)) -> return value = 0
ENTER execution(int de.scrum_master.app.Application.doSomeMoreWork(int))
EXIT  execution(int de.scrum_master.app.Application.doSomeMoreWork(int)) -> return value = 0
ENTER execution(void de.scrum_master.app.Application.otherBusinessCode(int))
EXIT  execution(void de.scrum_master.app.Application.otherBusinessCode(int)) -> return value = null
EXIT  execution(void de.scrum_master.app.Application.doSomething(String, Object)) -> return value = null
EXIT  execution(void de.scrum_master.app.Application.main(String[])) -> return value = null

如您所见,基于新的、更加模块化的方法结构,您获得了初始问题中想要的所有日志记录。旧方法变得更具可读性,而新方法更简单,因为它们专注于执行您提取它们的目的。

请注意:此示例代码是 运行 AspectJ,而不是 "AOP lite" 框架 Spring AOP。所以在 Spring AOP 中它不会像这样工作,因为:

  • Spring AOP 无法处理内部方法调用(自调用),正如我上面所说的,并在 Spring manual.
  • 中进行了解释
  • Spring AOP也不会记录main等静态方法,因为它只能拦截public,非静态接口方法(当使用Java动态代理时) 或额外受保护和包范围的方法(使用 CGLIB 代理时)。

因此,如果您考虑按照我的建议重构您的代码,并考虑将一些辅助方法设为私有但仍希望将它们记录下来,那么除了配置 Spring 以使用完整 [=21] 之外别无他法=] 为了充分发挥 AOP 的威力。

示例代码,迭代 2

如果您宁愿坚持使用 Spring AOP 及其代理,但仍然需要那些通过 AOP 记录的内部调用方法,您需要在重构中更进一步并将三个新方法提取到额外的 Spring component/bean 连接到您的应用程序中。然后方法调用将不再是内部的,而是跨越 component/bean 边界,因此被 Spring AOP 日志记录方面拦截。

Application 工作者方法 class 将被提取并像这样调用:

package de.scrum_master.app;

// Make this into a @Component
public class MyWorker {
  public int doComplexThing(int input) throws UnexpectedResultException {
    if (input % 2 == 0)
      throw new UnexpectedResultException("uh-oh");
    return input % 5;
  }

  public int doSomeMoreWork(int input) {
    return input * input;
  }

  public void otherBusinessCode(int input) {}
}
package de.scrum_master.app;

import java.util.Random;

public class Application {
  // In a Spring context this would be injected via configuration
  private MyWorker worker = new MyWorker();

  public void doSomething(String name, Object someObj) {
    int result = new Random().nextInt(100);
    for (int counter = 0; counter < 5; counter++) {
      try {
        result = worker.doComplexThing(result + 1);
      } catch (UnexpectedResultException unexpectedResultException) {
        result = 4;
      }
    }
    result = worker.doSomeMoreWork(result);
    worker.otherBusinessCode(result);
  }

  public static void main(String[] args) {
    Application application = new Application();
    application.doSomething("John Doe", new Integer(11));
  }
}

纵横可以保持不变

日志输出变成这样:

ENTER execution(void de.scrum_master.app.Application.main(String[]))
ENTER execution(void de.scrum_master.app.Application.doSomething(String, Object))
ENTER execution(int de.scrum_master.app.MyWorker.doComplexThing(int))
EXIT  execution(int de.scrum_master.app.MyWorker.doComplexThing(int)) -> return value = 2
ENTER execution(int de.scrum_master.app.MyWorker.doComplexThing(int))
EXIT  execution(int de.scrum_master.app.MyWorker.doComplexThing(int)) -> return value = 3
ENTER execution(int de.scrum_master.app.MyWorker.doComplexThing(int))
ERROR execution(int de.scrum_master.app.MyWorker.doComplexThing(int)) -> exception = de.scrum_master.app.UnexpectedResultException: uh-oh
ENTER execution(int de.scrum_master.app.MyWorker.doComplexThing(int))
EXIT  execution(int de.scrum_master.app.MyWorker.doComplexThing(int)) -> return value = 0
ENTER execution(int de.scrum_master.app.MyWorker.doComplexThing(int))
EXIT  execution(int de.scrum_master.app.MyWorker.doComplexThing(int)) -> return value = 1
ENTER execution(int de.scrum_master.app.MyWorker.doSomeMoreWork(int))
EXIT  execution(int de.scrum_master.app.MyWorker.doSomeMoreWork(int)) -> return value = 1
ENTER execution(void de.scrum_master.app.MyWorker.otherBusinessCode(int))
EXIT  execution(void de.scrum_master.app.MyWorker.otherBusinessCode(int)) -> return value = null
EXIT  execution(void de.scrum_master.app.Application.doSomething(String, Object)) -> return value = null
EXIT  execution(void de.scrum_master.app.Application.main(String[])) -> return value = null