有些时候,我想要把每个运行过的方法接收到的参数、返回值和执行时间等信息记录(通过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.class 和 MethodLogger.class; 它返回三个类——修改过的Foo.class、Foo$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记录下来。

160919、使用AOP与注解记录Java日志的更多相关文章

  1. spring AOP自定义注解方式实现日志管理

    今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...

  2. Spring AOP 自定义注解实现统一日志管理

    一.AOP的基本概念: AOP,面向切面编程,常用于日志,事务,权限等业务处理.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容(Spring核心之一),是函数式编程 ...

  3. Spring AOP使用注解记录用户操作日志

    最后一个方法:核心的日志记录方法 package com.migu.cm.aspect; import com.alibaba.fastjson.JSON; import com.migu.cm.do ...

  4. Spring AOP的实现记录操作日志

    适用场景: 记录接口方法的执行情况,记录相关状态到日志中. 注解类:LogTag.java package com.lichmama.spring.annotation; import java.la ...

  5. spring AOP自定义注解 实现日志管理

    今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...

  6. SpringBoot系列(十三)统一日志处理,logback+slf4j AOP+自定义注解,走起!

    往期精彩推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列(三)配置文件详解 SpringBoot系列(四)we ...

  7. ELK的高级篇(测试记录各种日志)

    一.elk架构已经完成情况情况下  访问限制: 加个x-pack插件  1)一个脚本收集多个日志,if 判断写入es的索引 [root@k8s6 conf.d]# cat file.conf inpu ...

  8. 来一手 AOP 注解方式进行日志记录

    系统日志对于定位/排查问题的重要性不言而喻,相信许多开发和运维都深有体会. 通过日志追踪代码运行状况,模拟系统执行情况,并迅速定位代码/部署环境问题. 系统日志同样也是数据统计/建模的重要依据,通过分 ...

  9. Spring aop+自定义注解统一记录用户行为日志

    写在前面 本文不涉及过多的Spring aop基本概念以及基本用法介绍,以实际场景使用为主. 场景 我们通常有这样一个需求:打印后台接口请求的具体参数,打印接口请求的最终响应结果,以及记录哪个用户在什 ...

随机推荐

  1. Sequential Read Ahead For SQL Server

    Balancing CPU and I/O throughput is essential to achieve good overall performance and to maximize ha ...

  2. Linux命令行–走进shell

    在现代Linux系统上,当Linux系统启动时它会创建几个虚拟控制台.虚拟控制台是运行在Linux系统内存中的一个终端会话.大部分Linux发行版会启动7个(有时更多)虚拟控制台.通常按下Ctrl+A ...

  3. mysql修改表名,列名,列类型,添加表列,删除表列

    alter table test rename test1; --修改表名 ); --添加表列 alter table test drop column name; --删除表列 ) --修改表列类型 ...

  4. How do you evaluate music?

    I’ve seen several “can’t stand” or “best of” threads in regard to music, and based on some related d ...

  5. Lua xavante WEB server实现xmlrpc服务器端

    xavante xavante是一个使用lua实现的遵守http1.1的web server,支持wsapi. 依赖库: xavante核心 -- lua, copas(纯lua编写,网络连接coro ...

  6. 数据块损坏(block corruption)

    分为物理损坏和逻辑损坏-物理损坏一般指数据块头部不可以访问.数据块校验值不合法,数据块格式不再是oracle承认的格式-逻辑损坏一般是在物理性结构完整的情况下,数据的内容在含义上不正确,比如保存了不允 ...

  7. 关于更新Ubuntu14.04内核后,virtualbox无法开机vm的问题

    virtualbox和linux内核结合比较紧密,所以当内核意外改动,可能会引起virtualbox无法启动虚拟机,只要尝试启动虚拟机,就会弹出让你执行/etc/init.d/vboxdrv setu ...

  8. PAT 解题报告 1048. Find Coins (25)

    1048. Find Coins (25) Eva loves to collect coins from all over the universe, including some other pl ...

  9. JAX-WS(二)之使用wsimport创建WebService客户端

    客户端开发的通常过程是从已有的WSDL处罚,创建辅助类JAXB对象和Service代理类,然后基于这些类开发自己的客户端应用. 开发步骤: 创建eclipse项目: 运行wsimport命令生成客户端 ...

  10. redhat 6.7 安装nvidia显卡驱动时出现的问题

    一.给Redhat装Nvidia驱动时,出现类似ERROR: The Nouveau kernel driver is currently in use by your system. 的错误,这是应 ...