代码之家  ›  专栏  ›  技术社区  ›  CuriousMind

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

  •  1
  • CuriousMind  · 技术社区  · 7 年前

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

    和往常一样,我发现的经典示例是“日志记录”,在这个示例中,使用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 回复  |  直到 7 年前
        1
  •  5
  •   kriegaex    4 年前

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

    如您所见,我从您复杂的方法中提取了一些方法。为了向您展示更多的日志输出,我甚至在 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));
      }
    }
    

    日志方面可能如下所示:

    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。因此,在春季AOP中,它不会像这样工作,因为:

    • Spring AOP不能处理内部方法调用(自调用),正如我前面所说的,并在 Spring manual .
    • Spring AOP也不会记录静态方法,例如 main 因为它只能截获公共的、非静态的接口方法(当使用Java动态代理时)或其他受保护的和包作用域的方法(当使用CGLIB代理时)。

    因此,如果您考虑将代码重构为我建议的内容,并考虑将一些助手方法设置为私有,但仍希望将它们记录下来,那么除了将Spring配置为使用full AspectJ via LTW (load-time weaving) 以使用AOP的全部功能。

    示例代码,迭代2

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

    这个 Application worker methods类将按如下方式提取和调用:

    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