160919、使用AOP与注解记录Java日志
有些时候,我想要把每个运行过的方法接收到的参数、返回值和执行时间等信息记录(通过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注解有一个参数,如果该方法
可见性是 * (public、protected或private);
名字是 * (任何名字都可以);
参数是 .. (任何参数都可以);
注解为@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日志的更多相关文章
- spring AOP自定义注解方式实现日志管理
今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...
- Spring AOP 自定义注解实现统一日志管理
一.AOP的基本概念: AOP,面向切面编程,常用于日志,事务,权限等业务处理.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容(Spring核心之一),是函数式编程 ...
- Spring AOP使用注解记录用户操作日志
最后一个方法:核心的日志记录方法 package com.migu.cm.aspect; import com.alibaba.fastjson.JSON; import com.migu.cm.do ...
- Spring AOP的实现记录操作日志
适用场景: 记录接口方法的执行情况,记录相关状态到日志中. 注解类:LogTag.java package com.lichmama.spring.annotation; import java.la ...
- spring AOP自定义注解 实现日志管理
今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...
- SpringBoot系列(十三)统一日志处理,logback+slf4j AOP+自定义注解,走起!
往期精彩推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列(三)配置文件详解 SpringBoot系列(四)we ...
- ELK的高级篇(测试记录各种日志)
一.elk架构已经完成情况情况下 访问限制: 加个x-pack插件 1)一个脚本收集多个日志,if 判断写入es的索引 [root@k8s6 conf.d]# cat file.conf inpu ...
- 来一手 AOP 注解方式进行日志记录
系统日志对于定位/排查问题的重要性不言而喻,相信许多开发和运维都深有体会. 通过日志追踪代码运行状况,模拟系统执行情况,并迅速定位代码/部署环境问题. 系统日志同样也是数据统计/建模的重要依据,通过分 ...
- Spring aop+自定义注解统一记录用户行为日志
写在前面 本文不涉及过多的Spring aop基本概念以及基本用法介绍,以实际场景使用为主. 场景 我们通常有这样一个需求:打印后台接口请求的具体参数,打印接口请求的最终响应结果,以及记录哪个用户在什 ...
随机推荐
- out 和 ref 之间的区别整理
ref和out都是C#中的关键字,所实现的功能也差不多,都是指定一个参数按照引用传递. 对于编译后的程序而言,它们之间没有任何区别,也就是说它们只有语法区别. 总结起来,他们有如下语法区别: 1.re ...
- python中新式类和经典类的区别
1).python在类中的定义在py2-3版本上是使用的有新式类和经典类两种情况,在新式类和经典类的定义中最主要的区别是在定义类的时候是否出现引用object;如:经典类:Class 类名::而新式类 ...
- procps包里面的sysctl命令
procps包里面的sysctl命令 --http://www.cnblogs.com/createyuan/p/3740917.html?utm_source=tuicool&utm_med ...
- Spring中的工厂模式和单例模式
Spring预备知识(适合中小型项目) 作用:集成和管理其他框架 工厂模式: A a = new A( ); 将类所要创建的对象写入工厂,统一进行管理 package com.spring; pu ...
- NSAttributedString
1. 使用这个类,必须先导入CoreText框架. 2. 给UILabel设置attributedText了会导致给UILabel中text,font,textColor,shadowCo ...
- linux:磁碟与档案系统管理
档案系统特性:为什么磁碟分割完需要格式化(format)才能使用吗? 答:因为每种作业系统所设定的档案属性和权限并不相同,为了存放这些档案所需的资料(所以需要格式化成作业系统能够利用的档案系统格式fi ...
- Compile Sources 和 Copy Bundle Resources的区别
Compile Sources主要存放.m文件 Copy Bundle Resources 主要存放xib plist bundle js 文件
- pgbouncer+pg(fdw)+pg(datanode)分表方案
pgbouncer+pg(fdw)+pg(datanode)分表方案 (环境RHEL6.5,PG9.4.5,pgbouncer1.5.4,libevent2.0.22) 方案架构图如下: pgboun ...
- C++之路进阶——bzoj1468(tree)
F.A.Qs Home Discuss ProblemSet Status Ranklist Contest ModifyUser gryz2016 Logout 捐赠本站 Notice:由于本OJ ...
- myeclipse项目里有红色感叹号
myeclipse项目里有红色感叹号 这种情况是因为 .classpath 文件里面配置引用了某个jar,但是实际上你的 lib 里面并没有这个jar 所以才会有红色的提示. 不用拿.classpat ...