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

Cur*_*ind 3 logging aop spring aspectj spring-aop

我是 AOP 的新手,我发现它有助于分离crosscutting关注点,并且似乎是一个很好的功能,为 OOP 编程增添了魅力。

一如既往,我发现的经典示例是“日志记录”,其中使用 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...");
} 
Run Code Online (Sandbox Code Playgroud)

现在,参考在线文档/教程,“日志记录建议”的用例是我们可以从代码中删除日志记录代码,“日志记录建议”将执行日志记录,例如在方法的输入、返回期间,使用注释。

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

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

我提到了这个SO问题,但没有清楚AOP如何帮助这种情况。

谁能帮助我理解这一点?

kri*_*aex 7

简短的答案是:AOP 并不是要研究您的方法,因为方法一直在重构,并且应该被视为黑匣子。

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

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

示例代码,迭代 1

该示例可以很好地与 AspectJ 配合使用,因为 AspectJ 不基于委派动态代理,因此不存在自调用问题,例如类内的内部方法调用。

package de.scrum_master.app;

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

  public UnexpectedResultException(String message) {
    super(message);
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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));
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

控制台日志如下所示:

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
Run Code Online (Sandbox Code Playgroud)

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

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

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

因此,如果您考虑将代码重构为我建议的内容,并考虑将一些辅助方法设为私有,但仍希望将它们记录下来,那么除了配置 Spring通过 LTW(加载时编织)使用完整的 AspectJ 之外,没有其他方法了使用 AOP 的全部功能。

示例代码,迭代 2

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

工作Application方法类将被提取并调用,如下所示:

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) {}
}
Run Code Online (Sandbox Code Playgroud)
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));
  }
}
Run Code Online (Sandbox Code Playgroud)

该方面可以保持不变。

日志输出更改为如下内容:

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
Run Code Online (Sandbox Code Playgroud)

  • 不客气。我想如果有人称自己是“好奇心”,我应该详细说明一下。;-) 请随意提出后续问题,只要它们与原始问题相关即可。 (2认同)