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

时间:2018-01-26 22:40:04

标签: spring logging aop 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...");
} 

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

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

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

我提到了this SO question,但没有明白AOP如何帮助这样的情况。

任何人都可以帮我理解这个吗?

1 个答案:

答案 0 :(得分:3)

简短的回答是: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);
  }
}

正如您所看到的,我从您的复杂方法中提取了一些方法。为了向您展示更多日志输出,我甚至通过调用在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之类的静态方法,因为它只能拦截公共的,非静态的接口方法(当使用Java动态代理时)或者另外受保护的和包范围的方法(当使用CGLIB代理时) )。

因此,如果您考虑将代码重构为我建议的内容,并考虑将某些辅助方法设为私有但仍希望将其记录,除了将Spring配置为使用完整AspectJ via LTW (load-time weaving)以便使用之外别无他法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) {}
}
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