来一手 AOP 注解方式进行日志记录
系统日志对于定位/排查问题的重要性不言而喻,相信许多开发和运维都深有体会。
通过日志追踪代码运行状况,模拟系统执行情况,并迅速定位代码/部署环境问题。
系统日志同样也是数据统计/建模的重要依据,通过分析系统日志能窥探出许多隐晦的内容。
如系统的健壮性(服务并发访问/数据库交互/整体响应时间...)
某位用户的喜好(分析用户操作习惯,推送对口内容...)
当然系统开发者还不满足于日志组件打印出来的日志,毕竟冗余且篇幅巨长。
so,对于关键的系统操作设计日志表,并在代码中进行操作的记录,配合 SQL 统计和搜索数据是件很愉快的事情。
本篇旨在总结在 Spring 下使用 AOP 注解方式进行日志记录的过程,如果能对你有所启发阁下不甚感激。
1. 依赖类库
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectjweaver.version}</version>
</dependency>
AspectJ 中的很多语法结构基本上已成为 AOP 领域的标准。
Spring 也有自己的 Spring-AOP,采用运行时生成代理类,底层可以选用 JDK 或者 CGLIB 动态代理。
通俗点,AspectJ 在编译时增强要切入的类,而 Spring-AOP 是在运行时通过代理类增强切入的类,效率和性能可想而知。
Spring 在 2.0 的时候就已经开始支持 AspectJ ,现在到 4.X 的时代已经很完美的和 AspectJ 拥抱到了一起。
开启扫描 AspectJ 注解的支持:
<!-- proxy-target-class等于true是强制使用cglib代理,proxy-target-class默认false,如果你的类实现了接口 就走JDK代理,如果没有,走cglib代理 -->
<!-- 注:对于单利模式建议使用cglib代理,虽然JDK动态代理比cglib代理速度快,但性能不如cglib -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
2. 定义切入点日志注解

目标操作日志表,其中设计了一些必要的字段,具体字段请拿捏具体项目场景,根据表结构设计注解如下。
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog { String operationModular() default ""; String operationContent() default "";
}
上述我只做了两个必要的参数,一个为操作的模块,一个为具体的操作内容。
其实根据项目场景这里参数的设计可以非常丰富,不被其他程序员吐槽在此一举。
3. 编写处理日志切点类
@Pointcut("@annotation(com.rambo.spm.common.aop.OperationLog)")
public void operationLogAspect() {
}
类的构造函数上描述了该类要拦截的为 OperationLog 的注解方法, 同样你也可以配置 XML 进行拦截。
切入点的姿势有很多,不仅是正则同样也支持组合表达式,强大的表达式能让你精准的切入到任何你想要的地方。
更多详情:http://blog.csdn.net/zhengchao1991/article/details/53391244
看到这里如果你对 Spring AOP 数据库事务控制熟悉,其实 Spring AOP 记录日志是相似的机制。
@Before("operationLogAspect()")
public void doBefore(JoinPoint joinPoint) {
logger.info("before aop:{}", joinPoint);
//do something
}
@Around("operationLogAspect()")
public Object doAround(ProceedingJoinPoint point) {
logger.info("Around:{}", point);
Object proceed = null;
try {
proceed = point.proceed();
//do somthing
} catch (Throwable throwable) {
throwable.printStackTrace();
logger.error("日志 aop 异常信息:{}", throwable.getMessage());
}
return proceed;
}
@AfterThrowing("operationLogAspect()")
public void doAfterThrowing(JoinPoint pjp) {
logger.info("@After:{}", pjp);
//do somthing
}
@After("operationLogAspect()")
public void doAfter(JoinPoint pjp) {
logger.info("@After:{}", pjp);
}
@AfterReturning("operationLogAspect()")
public void doAfterReturning(JoinPoint point) {
logger.info("@AfterReturning:{}", point);
}
AspectJ 提供了几种通知方法,通过在方法上注解这几种通知,解析对应的方法入参,你就能洞悉切点的一切运行情况。
前置通知(@Before):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常);
返回后通知(@AfterReturning):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回;
抛出异常后通知(@AfterThrowing):方法抛出异常退出时执行的通知;
后通知(@After):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出);
环绕通知(@Around):包围一个连接点(joinpoint)的通知,如方法调用;
通知方法中的值与构造函数一致,指定该通知对哪个切点有效,
上述 @Around 为最强大的一种通知类型,可以在方法调用前后完成自定义的行为,它可选择是否继续执行切点、直接返回、抛出异常来结束执行。
@Around 之所以如此强大是和它的入参有关,别的注解注解入参只容许 JoinPoint ,而 @Around 注解容许入参 ProceedingJoinPoint。
package org.aspectj.lang;
import org.aspectj.runtime.internal.AroundClosure;
public interface ProceedingJoinPoint extends JoinPoint {
void set$AroundClosure(AroundClosure var1);
Object proceed() throws Throwable;
Object proceed(Object[] var1) throws Throwable;
}
反编译 ProceedingJoinPoint 你会恍然大悟,Proceedingjoinpoint 继承了 JoinPoint 。
在 JoinPoint 的基础上暴露出 proceed 这个方法。proceed 方法很重要,这是 aop 代理链执行的方法。
暴露出这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到 JoinPoint,这跟切面类型有关), 能决定是否走代理链还是走自己拦截的其他逻辑。
如果项目没有特定的需求,妥善使用 @Around 注解就能帮你解决一切问题。
@Around("operationLogAspect()")
public Object doAround(ProceedingJoinPoint point) {
logger.info("Around:{}", point);
Object proceed = null;
try {
proceed = point.proceed();
Object pointTarget = point.getTarget();
Signature pointSignature = point.getSignature();
String targetName = pointTarget.getClass().getName();
String methodName = pointSignature.getName();
Method method = pointTarget.getClass().getMethod(pointSignature.getName(), ((MethodSignature) pointSignature).getParameterTypes());
OperationLog methodAnnotation = method.getAnnotation(OperationLog.class);
String operationModular = methodAnnotation.operationModular();
String operationContent = methodAnnotation.operationContent();
OperationLogPO log = new OperationLogPO();
log.setOperUserid(SecureUtil.simpleUUID());
log.setOperUserip(HttpUtil.getClientIP(getHttpReq()));
log.setOperModular(operationModular);
log.setOperContent(operationContent);
log.setOperClass(targetName);
log.setOperMethod(methodName);
log.setOperTime(new Date());
log.setOperResult("Y");
operationLogService.insert(log);
} catch (Throwable throwable) {
throwable.printStackTrace();
logger.error("日志 aop 异常信息:{}", throwable.getMessage());
}
return proceed;
}
别忘记将上面切点处理类/和要切入的类托管给 Spring,Aop 日志是不是很简单,复杂的应该是 aspectj 内部实现机制,有机会要看看源码哦。
处理切点类完整代码:
@Aspect
@Component
public class OperationLogAspect {
private static final Logger logger = LoggerFactory.getLogger(OperationLogAspect.class); //ProceedingJoinPoint 与 JoinPoint
//注入Service用于把日志保存数据库
//这里我用resource注解,一般用的是@Autowired,他们的区别如有时间我会在后面的博客中来写
@Resource
private OperationLogService operationLogService; //@Pointcut("execution (* com.rambo.spm.*.controller..*.*(..))")
@Pointcut("@annotation(com.rambo.spm.common.aop.OperationLog)")
public void operationLogAspect() { } @Before("operationLogAspect()")
public void doBefore(JoinPoint joinPoint) {
logger.info("before aop:{}", joinPoint);
gePointMsg(joinPoint);
} @Around("operationLogAspect()")
public Object doAround(ProceedingJoinPoint point) {
logger.info("Around:{}", point);
Object proceed = null;
try {
proceed = point.proceed(); Object pointTarget = point.getTarget();
Signature pointSignature = point.getSignature(); String targetName = pointTarget.getClass().getName();
String methodName = pointSignature.getName();
Method method = pointTarget.getClass().getMethod(pointSignature.getName(), ((MethodSignature) pointSignature).getParameterTypes());
OperationLog methodAnnotation = method.getAnnotation(OperationLog.class);
String operationModular = methodAnnotation.operationModular();
String operationContent = methodAnnotation.operationContent(); OperationLogPO log = new OperationLogPO();
log.setOperUserid(SecureUtil.simpleUUID());
log.setOperUserip(HttpUtil.getClientIP(getHttpReq()));
log.setOperModular(operationModular);
log.setOperContent(operationContent);
log.setOperClass(targetName);
log.setOperMethod(methodName);
log.setOperTime(new Date());
log.setOperResult("Y");
operationLogService.insert(log);
} catch (Throwable throwable) {
throwable.printStackTrace();
logger.error("日志 aop 异常信息:{}", throwable.getMessage());
}
return proceed;
} @AfterThrowing("operationLogAspect()")
public void doAfterThrowing(JoinPoint pjp) {
logger.info("@AfterThrowing:{}", pjp); } @After("operationLogAspect()")
public void doAfter(JoinPoint pjp) {
logger.info("@After:{}", pjp);
} @AfterReturning("operationLogAspect()")
public void doAfterReturning(JoinPoint point) {
logger.info("@AfterReturning:{}", point);
} private void gePointMsg(JoinPoint joinPoint) {
logger.info("切点所在位置:{}", joinPoint.toString());
logger.info("切点所在位置的简短信息:{}", joinPoint.toShortString());
logger.info("切点所在位置的全部信息:{}", joinPoint.toLongString());
logger.info("切点AOP代理对象:{}", joinPoint.getThis());
logger.info("切点目标对象:{}", joinPoint.getTarget());
logger.info("切点被通知方法参数列表:{}", joinPoint.getArgs());
logger.info("切点签名:{}", joinPoint.getSignature());
logger.info("切点方法所在类文件中位置:{}", joinPoint.getSourceLocation());
logger.info("切点类型:{}", joinPoint.getKind());
logger.info("切点静态部分:{}", joinPoint.getStaticPart());
} private HttpServletRequest getHttpReq() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
return servletRequestAttributes.getRequest();
}
}
上述三步骤之后,你就可以在想记录日志的方法上面添加注解来进行记录操作日志,像下面这样。

源码托管地址:https://git.oschina.net/LanboEx/spmvc-mybatis.git 有这方面需求和兴趣的可以检出到本地跑一跑。
来一手 AOP 注解方式进行日志记录的更多相关文章
- Jeecg中通过Spring_AOP+注解方式实现日志的管理
转载;https://blog.csdn.net/ma451152002/article/details/77234236 Jeecg中通过Spring_AOP+注解方式实现日志的管理 一.设计思路 ...
- Spring系列之aAOP AOP是什么?+xml方式实现aop+注解方式实现aop
Spring系列之aop aop是什么?+xml方式实现aop+注解方式实现aop 什么是AOP? AOP为Aspect Oriented Programming 的缩写,意识为面向切面的编程,是通过 ...
- AOP注解方式ApsectJ开发
AOP注解方式ApsectJ开发 引入配置文件 编写切面类配置 使用注解的AOP对象目标类进行增强 在配置文件中开启以注解形式进行AOP开发 在切面类上添加注解 注解AOP通知类型 @Before前置 ...
- springboot搭建环境之使用@Slf4j注解方式,进行日志管理
如果不想每次都写private final Logger logger = LoggerFactory.getLogger(XXX.class); 可以用注解@Slf4j 需要引入依赖为: < ...
- spring AOP自定义注解方式实现日志管理
今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...
- springAOP注解方式实现日志操作
通过自定义注解调用方法执行日志存储: package com.zktx.platform.log2; import java.lang.reflect.Method; import java.util ...
- 基于AOP和ThreadLocal实现日志记录
基于AOP和ThreadLocal实现的一个日志记录的例子 主要功能实现 : 在API每次被请求时,可以在整个方法调用链路中记录一条唯一的API请求日志,可以记录请求中绝大部分关键内容.并且可以自定义 ...
- 转:Spring AOP 注解方式实现的一些“坑”
使用过Spring AOP的注解方式实现,也入过不少坑,现在做一下记录,希望有同样需求的朋友可以少走弯路 使用之前还是先过一下官方文档吧,至少可以少走弯路,不用担心英文读不懂,其实只看代码例子就能理解 ...
- spring aop注解方式与xml方式配置
注解方式 applicationContext.xml 加入下面配置 <!--Spring Aop 启用自动代理注解 --> <aop:aspectj-autoproxy proxy ...
随机推荐
- java IO文件操作简单基础入门例子,IO流其实没那么难
IO是JAVASE中非常重要的一块,是面向对象的完美体现,深入学习IO,你将可以领略到很多面向对象的思想.今天整理了一份适合初学者学习的简单例子,让大家可以更深刻的理解IO流的具体操作. 1.文件拷贝 ...
- eclipse下建立 android 项目,相关文件夹介绍
今天开始进入ANDROID开发,之前一直做些JAVA的WEBSERVICE之类的文件,第一次从头开始整理ANDROID项目,我会把最近遇到的问题做一一梳理. 现在来说一下建立ANDROID项目后产生的 ...
- Zab: A simple totally ordered broadcast protocol(译)
摘要 这是一个关于ZooKeeper正在使用的全序广播协议(Zab)的简短概述.它在概念上很容易理解,也很容易实现,并且提供很高的性能.在这篇文章里,我们会呈现ZooKeeper在Zab上的需求,也会 ...
- js 过滤敏感词 ,可将带有标点符号的敏感词过滤掉
function transSensitive(content) { // var Sensitive = H.getStorage("Sensitive");//敏感词数组 va ...
- tomcat抬头有“选择”或“选定”,导致tomcat无法运行问题
2. 遇到tomcat抬头有"选择"或"选定",导致tomcat无法运行问题 解决:在tomcat抬头右键--属性,去掉"快速编辑模式"勾选 ...
- 移动端APP CSS Reset及注意事项CSS重置
@charset "utf-8"; * { -webkit-box-sizing: border-box; box-sizing: border-box; } //禁止文本缩放 h ...
- h5之scrollIntoView控制页面元素滚动
如果滚动页面也是DOM没有解决的一个问题.为了解决这个问题,浏览器实现了一下方法,以方便开发人员如何更好的控制页面的滚动.在各种专有方法中,HTML5选择了scrollIntoView()作为标准方法 ...
- Java内存分配及垃圾回收机制(未完待待续)
Java内存区域 1.内存区域 jvm运行时数据区域 程序计数器 Java虚拟机栈 本地方法栈 方法区 Java堆 大图 2.概念解释 程序计数器 线程私有的一块很小的内存空间,它是当前线程所执行 ...
- 推荐几款.NET客户端开源报表图
如果你正在开发客户端报表图相关的应用,除了.NET自带的控件,你还可以考虑使用以下几个控件库. [OxyPlot] OxyPlot是一个支持.NET的跨平台绘图库.你可以在很多平台上使用它,如WPF, ...
- [leetcode-630-Course Schedule III]
There are n different online courses numbered from 1 to n. Each course has some duration(course leng ...