使用AOP与注解记录Java日志

标签: 基础技术 Aspect AspectJ jcabi-aspects slf4j | 发表时间:2014-10-17 08:00 | 作者:李 广
出处:http://www.importnew.com

有些时候,我想要把每个运行过的方法接收到的参数、返回值和执行时间等信息记录(通过slf4j 和 log4j)下来。在AspectJ、jcabi-aspects和Java注解的帮助下我实现了这个想法。

    public class Foo {
      @Loggable
      public int power(int x, int p) {
        return Math.pow(x, p);
      }
    }

在log4j中可以看到以下输出:

    [INFO] com.example.Foo #power(2, 10): 1024 in 12μs
    [INFO] com.example.Foo #power(3, 3): 27 in 4μs

看上去很酷对吧?接下来我们来看看它是如何工作的。

注解

注解是Java 6中采用的一种技术( 译注:其实Java 5就有注解了)。它是一种不会影响程序运行的元编程指令,我们可以用它来对一些指定的元素(方法、类或者变量)进行标记。换句话说,注解就是代码中可以看到的标记。一些注解只在编译阶段可见——它们不存在于编译好的 .class文件中,另外一些注解在编译后仍然可见。

例如, @Override是第一种类型(它的保留类型是 SOURCE),而JUnit的 @Test是第二种类型(保留类型是 RUNTIME), @Loggable——我在上面使用过的是第二种注解,包含在jcabi-aspects中,在编译后会留在 .class文件中。

值得注意的是,上文的 power()方法即便被注解并且编译,也不会发送任何内容到slf4j。仅仅是一种用来提醒相关软件“请记录我的执行过程”的标记, 理解这一点很重要。

AOP

AOP(面向切面编程)是一种可以在对源代码不作明显改动的情况下向其加入可执行块的技术。在上面的示例中,我们不想在实现类中记录方法的执行,而是用其他类去拦截 power()方法的每次调用,测量执行时间并把这些信息发送给slf4j。

我想要使拦截类能识别 @Loggable注解并且记录下 power()方法的每次调用,当然它也应该能拦截其他方法。

这个想法与AOP的出发点很符合——避免在多个类中重复实现一些共同的功能。

日志是Java主要功能的一个补充。我们不想往代码中加入繁杂的日志指令,这样会导致代码可读性降低,所以我们想在别的地方偷偷地记录日志。

从AOP的角度来看,我们的解决方案是新建一个指定切入点和环绕通知的切面以实现预期的功能。

AspectJ

接下来,让我们来看看这些神奇的注解。首先,让我们来了解 jcabi-aspects是如何用 AspectJ来实现注解的。下面是一个简单例子,你可以在 MethodLogger.java中找到全部代码。

    @Aspect
    public class MethodLogger {
      @Around("execution(* *(..)) && @annotation(Loggable)")
      public Object around(ProceedingJoinPoint point) {
        long start = System.currentTimeMillis();
        Object result = point.proceed();
        Logger.info(
          "#%s(%s): %s in %[msec]s",
          MethodSignature.class.cast(point.getSignature()).getMethod().getName(),
          point.getArgs(),
          result,
          System.currentTimeMillis() - start
        );
        return result;
      }
    }

这个切面(aspect)里有一个around()通知,切面用 @Aspect注解,通知用 @Around注解, 上面提到过这些注解仅仅是在 .class文件中做了标记,这些标记在运行时能提供一些信息给那些对它们感兴趣对象。

@Around注解有一个参数,如果该方法

  1. 可见性是 * (public、protected或private);
  2. 名字是 * (任何名字都可以);
  3. 参数是 .. (任何参数都可以);
  4. 注解为@Loggable

那么通知就会应用到该方法。

当注解方法被调用的时就会被拦截,around()通知会在被拦截方法之前执行,其中 @Around 类型的通知需要一个 ProceedingJoinPoint 类的实例作为参数,之后返回一个对象给 power()方法。

为了调用 power()方法,通知需要调用join point对象的proceed()方法。

接下来编译并把它加入环境变量,让我们的主文件 Foo.class能够调用它。目前为止一切顺利,我们还需要最后一步——把通知运转起来。

二进制切面织入

切面织入(aspect waving)就是将切面应用到目标对象从而创建一个新的代理对象的过程。切面织入将一些代码插入原代码,AspectJ就是这么做的。我们给它两个二进制Java类 Foo.classMethodLogger.class; 它返回三个类——修改过的 Foo.classFoo$AjcClosure1.class和未修改的 MethodLogger.class

为了理解如何将不同的通知应用于对应的哪个方法,AspectJ织入在 .class文件中使用了注解,并使用 反射来浏览环境变量中的所有类。它分析 @Around注解中的哪个方法满足条件。 power()就在此时被发现了。

上述操作需要分为两步。首先,我们把.java文件编译。然后AspectJ对编译后的文件进行织入/修改,织入后的Foo类看起来像下面这样:

    public class Foo {
      private final MethodLogger logger;
      @Loggable
      public int power(int x, int p) {
        return this.logger.around(point);
      }
      private int power_aroundBody(int x, int p) {
        return Math.pow(x, p);
      }
    }

AspectJ织入把我们原来的功能移到新方法power_aroundBody()中,并把所有的 power()调用重定向到切面类MethodLogger。

下图是每次调用 power()的过程:

图中那一小块绿色就是原方法 power()

如你所见,切面织入过程把类和切面等联系起来了。如果没有织入,它们仅仅是一堆编译好的二进制代码和注解。

jcabi-aspects

jcabi-aspects是一个含有Loggable注解和MethodLogger切面的JAR库,它还有其它注解和切面。你不必自己实现切面,只要在环境变量加入一些依赖并为切面织入配置好 jcabi-maven-plugin。可以到 Maven Central获取最新版本。

    <project>
      <depenencies>
        <dependency>
          <dependency>
            <groupId>com.jcabi</groupId>
            <artifactId>jcabi-aspects</artifactId>
          </dependency>
          <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
          </dependency>
        </dependency>
      </depenencies>
      <build>
        <plugins>
          <plugin>
            <groupId>com.jcabi</groupId>
            <artifactId>jcabi-maven-plugin</artifactId>
            <executions>
              <execution>
                <goals>
                  <goal>ajc</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </project>

由于织入过程比较复杂,我用Maven插件和ajc goal做了一个便捷的织入。你也可以直接用AspectJ,不过我还是推荐你使用 jcabi-maven-plugin

好了,现在你可以用 @com.jcabi.aspects.Loggable 注解你的方法执行过程将会通过slf4j记录下来。

如果按上述操作没有得到预期效果,欢迎到 Github issue提出问题。

相关文章

相关 [aop 注解 记录] 推荐:

使用AOP与注解记录Java日志

- - ImportNew
有些时候,我想要把每个运行过的方法接收到的参数、返回值和执行时间等信息记录(通过slf4j 和 log4j)下来. 在AspectJ、jcabi-aspects和Java注解的帮助下我实现了这个想法. 在log4j中可以看到以下输出:. 接下来我们来看看它是如何工作的. 注解是Java 6中采用的一种技术( 译注:其实Java 5就有注解了).

spring mvc +spring aop结合注解的 用户操作日志记录

- - 行业应用 - ITeye博客
参考了网上的一些 文章 但是他们写的不是很全  自己也是经过了一些摸索  可以实现 记录 spring mvc controller层操作记录. 一个关注点的模块化,这个关注点可能会横切多个对象. 事务管理是J2EE应用中一个关于横切关注点的很好的例子. AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @Aspect 注解(@AspectJ风格)来实现.

springboot aop日志记录

- - 编程语言 - ITeye博客
一、POM增加AOP JAR包. 三、SysAspect类. 注:@annotation(cn.com.hfai.controller.system.Logweb) 一定要指定Logweb类. 四、在Controller类的方法之上加上注解 @Logweb 即可. 注:这个只是打印在控制台上,若想放到数据库中,则需要增加操作数据库的业务代码.

Java Spring注解任务调度并实现AOP监控任务执行情况

- - 极客521 | 极客521
本文讲的是通过Spring注解的方式实现任务调度. 只要引入了spring-context包就能够在项目中使用注解方式的任务调度. 需要在Spring配置文件中加入task的schema. 然后在代码中就可以直接用了,要定时执行的方法必须是void的,并且没有任何参数的. cron表达式请自行问百度,下面只列出几个从网上找的例子.

AOP详解

- - CSDN博客推荐文章
AOP(面向方面编程:Aspect Oriented Programing)和IoC一样是Spring容器的内核,声明式事务的功能在此基础上开花结果. 但是AOP和OOP差别较大,要很好地理解这个概念,做到心领神会还是不容易的,不过相信看完帖子,你就不再迷惑了. 编程语言最终极的目标就是能以更自然、更灵活的方式模拟世界,从原始机器语言到过程语言再到面向对象的语言,编程语言一步步地用更自然、更灵活的方式描述软件.

Spring AOP详解

- - Java - 编程语言 - ITeye博客
        最近项目中遇到了以下几点需求,仔细思考之后,觉得采用AOP来解决. 一方面是为了以更加灵活的方式来解决问题,另一方面是借此机会深入学习Spring AOP相关的内容. 例如,以下需求不用AOP肯定也能解决,至于是否牵强附会,仁者见仁智者见智. 1.对部分函数的调用进行日志记录,用于观察特定问题在运行过程中的函数调用情况.

Spring AOP注解通过@Autowired,@Resource,@Qualifier,@PostConstruct,@PreDestroy注入属性的配置文件详解

- - CSDN博客推荐文章
原创整理不易,转载请注明出处: Spring AOP注解通过@Autowired,@Resource,@Qualifier,@PostConstruct,@PreDestroy注入属性的配置文件详解. 代码下载地址: http://www.zuidaima.com/share/1772661373422592.htm.

Spring AOP进行日志记录,管理 (使用Spring的拦截器功能获取对action中每个方法的调用情况,在方法调用前

- - 行业应用 - ITeye博客
原文地址: http://hi.baidu.com/wolf_childer/item/f0b5b0e664252cacc10d75d1.        在java开发中日志的管理有很多种. 我一般会使用过滤器,或者是Spring的拦截器进行日志的处理. 如果是用过滤器比较简单,只要对所有的.do提交进行拦截,然后获取action的提交路径就可以获取对每个方法的调用.

AOP的实现机制

- 风子 - ITeye论坛最新讨论
    附件中有本文的源代码和Pdf版. 本文写的很长的原因,是不希望大家学习AOP时到处找资料,大家有时间可以按照本文动手实践下,相信会有非常大的收获的,有什么问题互相交流,有问必答. AOP就是面向切面编程,我们可以从几个层面来实现AOP. 在编译器修改源代码,在运行期字节码加载前修改字节码或字节码加载后动态创建代理类的字节码,以下是各种实现机制的比较.

Java实现aop案例

- - 行业应用 - ITeye博客
MyPersonService.java代码如下:. MyPersonServiceImpl.java代码如下:. System.out.println("在com.shihuan.jdkaop.service.impl.MyPersonServiceImpl.findPerson()中: " + name);.