SpringAOP+注解实现简单的日志管理
今天在再次深入学习SpringAOP之后想着基于注解的AOP实现日志功能,在面试过程中我们也经常会被问到:假如项目已经上线,如何增加一套日志功能?我们会说使用AOP,AOP也符合开闭原则:对代码的修改禁止的,对代码的扩展是允许的。今天经过自己的实践简单的实现了AOP日志。
在这里我只是简单的记录下当前操作的人、做了什么操作、操作结果是正常还是失败、操作时间,实际项目中,如果我们需要记录的更详细,可以记录当前操作人的详细信息,比如说部门、身份证号等信息,这些信息可以直接从session中获取,也可以从session中获取用户ID之后调用userService从数据库获取。我们还可以记录用户调用了哪个类的哪个方法,我们可以使用JoinPoint参数获取或者利用环绕通知ProceedingJoinPoint去获取。可以精确的定位到类、方法、参数,如果有必要我们就可以记录在日志中,看业务需求和我们的日志表的设计。如果再细致的记录日志,我们可以针对错误再建立一个错误日志表,在发生错误的情况下(异常通知里)记录日志的错误信息。
实现的大致思路是:
1.前期准备,设计日志表和日志类,编写日志Dao和Service以及实现
2.自定义注解,注解中加入几个属性,属性可以标识操作的类型(方法是做什么的)
3.编写切面,切点表达式使用上面的注解直接定位到使用注解的方法,
4.编写通知,通过定位到方法,获取上面的注解以及注解的属性,然后从session中直接获取或者从数据库获取当前登录用户的信息,最后根据业务处理一些日志信息之后调用日志Service存储日志。
其实日志记录可以针对Controller层进行切入,也可以选择Service层进行切入,我选择的是基于Service层进行日志记录。网上的日志记录由的用前置通知,有的用环绕通知,我选择在环绕通知中完成,环绕通知中可以完成前置、后置、最终、异常通知的所有功能,因此我选择了环绕通知。(关于AOP的通知使用方法以及XML、注解AOP使用方法参考;http://www.cnblogs.com/qlqwjy/p/8729280.html)
下面是具体实现:
1.日志数据库:
CREATE TABLE `logtable` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`operateor` varchar(5) DEFAULT NULL,
`operateType` varchar(20) DEFAULT NULL,
`operateDate` datetime DEFAULT NULL,
`operateResult` varchar(4) DEFAULT NULL,
`remark` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
简单的记录操作了操作人,操作的类型,操作的日期,操作的结果。如果想详细的记录,可以将操作的类名与操作的方法名以及参数信息也新进日志,在环绕通知中利用反射原理即可获取这些参数(参考我的另一篇博客:http://www.cnblogs.com/qlqwjy/p/8729280.html)。
2.日志实体类:
Logtable.java
package cn.xm.exam.bean.log; import java.util.Date; public class Logtable {
private Integer id; private String operateor; private String operatetype; private Date operatedate; private String operateresult; private String remark; public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public String getOperateor() {
return operateor;
} public void setOperateor(String operateor) {
this.operateor = operateor == null ? null : operateor.trim();
} public String getOperatetype() {
return operatetype;
} public void setOperatetype(String operatetype) {
this.operatetype = operatetype == null ? null : operatetype.trim();
} public Date getOperatedate() {
return operatedate;
} public void setOperatedate(Date operatedate) {
this.operatedate = operatedate;
} public String getOperateresult() {
return operateresult;
} public void setOperateresult(String operateresult) {
this.operateresult = operateresult == null ? null : operateresult.trim();
} public String getRemark() {
return remark;
} public void setRemark(String remark) {
this.remark = remark == null ? null : remark.trim();
}
}
3.日志的Dao层使用的是Mybatis的逆向工程导出的mapper,在这里就不贴出来了
4.日志的Service层和实现类
- LogtableService.java接口
package cn.xm.exam.service.log; import java.sql.SQLException; import cn.xm.exam.bean.log.Logtable; /**
* 日志Service
*
* @author liqiang
*
*/
public interface LogtableService {
/**
* 增加日志
* @param log
* @return
* @throws SQLException
*/
public boolean addLog(Logtable log) throws SQLException;
}
- LogtableServiceImpl实现类
package cn.xm.exam.service.impl.log; import java.sql.SQLException; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import cn.xm.exam.bean.log.Logtable;
import cn.xm.exam.mapper.log.LogtableMapper;
import cn.xm.exam.service.log.LogtableService; @Service
public class LogtableServiceImpl implements LogtableService {
@Autowired
private LogtableMapper logtableMapper;
@Override
public boolean addLog(Logtable log) throws SQLException {
return logtableMapper.insert(log) > 0 ? true : false;
} }
5.自定义注解:
package cn.xm.exam.annotation; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* 日志注解
*
* @author liqiang
*
*/
@Target(ElementType.METHOD) // 方法注解
@Retention(RetentionPolicy.RUNTIME) // 运行时可见
public @interface LogAnno {
String operateType();// 记录日志的操作类型
}
6.在需要日志记录的方法中使用注解:(此处将注解写在DictionaryServiceImpl方法上)
package cn.xm.exam.service.impl.common; import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; import javax.annotation.Resource; import org.springframework.stereotype.Service; import cn.xm.exam.annotation.LogAnno;
import cn.xm.exam.bean.common.Dictionary;
import cn.xm.exam.bean.common.DictionaryExample;
import cn.xm.exam.mapper.common.DictionaryMapper;
import cn.xm.exam.mapper.common.custom.DictionaryCustomMapper;
import cn.xm.exam.service.common.DictionaryService; /**
* 字典表的实现类
*
* @author
*
*/
@Service
public class DictionaryServiceImpl implements DictionaryService { @Resource
private DictionaryMapper dictionaryMapper;/**
* 1、添加字典信息
*/
@LogAnno(operateType = "添加了一个字典项")
@Override
public boolean addDictionary(Dictionary dictionary) throws SQLException {
int result = dictionaryMapper.insert(dictionary);
if (result > 0) {
return true;
} else {
return false;
}
}
}
7.编写通知,切入到切点形成切面(注解AOP实现,环绕通知记录日志。)
注意:此处是注解AOP,因此在spring配置文件中开启注解AOP
<!-- 1.开启注解AOP -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
LogAopAspect.java
package cn.xm.exam.aop; import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.Date; import org.apache.struts2.ServletActionContext;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import cn.xm.exam.annotation.LogAnno;
import cn.xm.exam.bean.log.Logtable;
import cn.xm.exam.bean.system.User;
import cn.xm.exam.service.log.LogtableService; /**
* AOP实现日志
*
* @author liqiang
*
*/
@Component
@Aspect
public class LogAopAspect { @Autowired
private LogtableService logtableService;// 日志Service
/**
* 环绕通知记录日志通过注解匹配到需要增加日志功能的方法
*
* @param pjp
* @return
* @throws Throwable
*/
@Around("@annotation(cn.xm.exam.annotation.LogAnno)")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
// 1.方法执行前的处理,相当于前置通知
// 获取方法签名
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
// 获取方法
Method method = methodSignature.getMethod();
// 获取方法上面的注解
LogAnno logAnno = method.getAnnotation(LogAnno.class);
// 获取操作描述的属性值
String operateType = logAnno.operateType();
// 创建一个日志对象(准备记录日志)
Logtable logtable = new Logtable();
logtable.setOperatetype(operateType);// 操作说明 // 整合了Struts,所有用这种方式获取session中属性(亲测有效)
User user = (User) ServletActionContext.getRequest().getSession().getAttribute("userinfo");//获取session中的user对象进而获取操作人名字
logtable.setOperateor(user.getUsername());// 设置操作人 Object result = null;
try {
//让代理方法执行
result = pjp.proceed();
// 2.相当于后置通知(方法成功执行之后走这里)
logtable.setOperateresult("正常");// 设置操作结果
} catch (SQLException e) {
// 3.相当于异常通知部分
logtable.setOperateresult("失败");// 设置操作结果
} finally {
// 4.相当于最终通知
logtable.setOperatedate(new Date());// 设置操作日期
logtableService.addLog(logtable);// 添加日志记录
}
return result;
}
}
通过拦截带有 cn.xm.exam.annotation.LogAnno 注解的方法,根据参数获取到方法,然后获取方法的LogAnno注解,获取注解的属性,在方法执行前后对其进行处理,实现AOP功能。
如果需要获取IP地址可以用如下方法:
/**
* 获取IP地址的方法
* @param request 传一个request对象下来
* @return
*/
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
8.测试:
在页面上添加一个字典之后打断点进行查看:
- 会话中当前登录的用户信息:
- 当前日志实体类的信息
- 查看数据库:
mysql> select * from logtable\G
*************************** 1. row ***************************
id: 1
operateor: 超级管理员
operateType: 添加了一个字典项
operateDate: 2018-04-08 20:46:19
operateResult: 正常
remark: NULL
到这里基于注解AOP+注解实现日志记录基本实现了。
9.现在模拟在Service中抛出错误的测试:
1.修改ServiceIMpl模拟制造一个除零异常
package cn.xm.exam.service.impl.common; import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; import javax.annotation.Resource; import org.springframework.stereotype.Service; import cn.xm.exam.annotation.LogAnno;
import cn.xm.exam.bean.common.Dictionary;
import cn.xm.exam.bean.common.DictionaryExample;
import cn.xm.exam.mapper.common.DictionaryMapper;
import cn.xm.exam.mapper.common.custom.DictionaryCustomMapper;
import cn.xm.exam.service.common.DictionaryService; /**
* 字典表的实现类
*
*
*/
@Service
public class DictionaryServiceImpl implements DictionaryService { @Resource
private DictionaryMapper dictionaryMapper;/**
* 1、添加字典信息
*/
@LogAnno(operateType = "添加了一个字典项")
@Override
public boolean addDictionary(Dictionary dictionary) throws SQLException {
int i=1/0;
int result = dictionaryMapper.insert(dictionary);
if (result > 0) {
return true;
} else {
return false;
}
}
}
2.修改切面(主要是修改捕捉异常,除零异常不是SQLException,所有修改,实际项目中视情况而定)
package cn.xm.exam.aop; import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.Date; import org.apache.struts2.ServletActionContext;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import cn.xm.exam.annotation.LogAnno;
import cn.xm.exam.bean.log.Logtable;
import cn.xm.exam.bean.system.User;
import cn.xm.exam.service.log.LogtableService; /**
* AOP实现日志
*
* @author liqiang
*
*/
@Component
@Aspect
public class LogAopAspect { @Autowired
private LogtableService logtableService;// 日志Service
/**
* 环绕通知记录日志通过注解匹配到需要增加日志功能的方法
*
* @param pjp
* @return
* @throws Throwable
*/
@Around("@annotation(cn.xm.exam.annotation.LogAnno)")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
// 1.方法执行前的处理,相当于前置通知
// 获取方法签名
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
// 获取方法
Method method = methodSignature.getMethod();
// 获取方法上面的注解
LogAnno logAnno = method.getAnnotation(LogAnno.class);
// 获取操作描述的属性值
String operateType = logAnno.operateType();
// 创建一个日志对象(准备记录日志)
Logtable logtable = new Logtable();
logtable.setOperatetype(operateType);// 操作说明 // 整合了Struts,所有用这种方式获取session中属性(亲测有效)
User user = (User) ServletActionContext.getRequest().getSession().getAttribute("userinfo");//获取session中的user对象进而获取操作人名字
logtable.setOperateor(user.getUsername());// 设置操作人 Object result = null;
try {
//让代理方法执行
result = pjp.proceed();
// 2.相当于后置通知(方法成功执行之后走这里)
logtable.setOperateresult("正常");// 设置操作结果
} catch (Exception e) {
// 3.相当于异常通知部分
logtable.setOperateresult("失败");// 设置操作结果
} finally {
// 4.相当于最终通知
logtable.setOperatedate(new Date());// 设置操作日期
logtableService.addLog(logtable);// 添加日志记录
}
return result;
}
}
3.结果:
mysql> select * from logtable\G
*************************** 1. row ***************************
id: 3
operateor: 超级管理员
operateType: 添加了一个字典项
operateDate: 2018-04-08 21:53:53
operateResult: 失败
remark: NULL
1 row in set (0.00 sec)
补充:在Spring+SpringMVC+Mybatis的框架中使用的时候,需要注解扫描包的配置以及spring代理方式的配置
<!-- 6.开启注解AOP (前提是引入aop命名空间和相关jar包) -->
<aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true"></aop:aspectj-autoproxy> <!-- 7.开启aop,对类代理强制使用cglib代理 -->
<aop:config proxy-target-class="true"></aop:config> <!-- 8.扫描 @Service @Component 注解-->
<context:component-scan base-package="cn.xm.jwxt" >
<!-- 不扫描 @Controller的类 -->
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>
解释: 6配置是开启注解aop,且暴露cglib代理对象,对cglib代理对象进行aop拦截
7配置是强制spring使用cglib代理
8是配置扫描的包。且不扫描@Controller 注解,如果需要配置扫描的注解可以:
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
注意:我在使用Spring+SpringMVc+Mybatis的过程中发现注解AOP没反应,最后发现编译只会找不到自己的Aspect类。。。。。。。。
最后:需要注意的是我在尝试本实例方法调用本实例方法的时候发现被调用的方法上的注解无效。因此我在另一个类中写了一个标记方法并打上注解才拦截到注解。
例如:我希望登录成功之后记录登录信息,在登录成功之后我调用service的一个标记方法即可以使注解生效。
@MyLogAnnotation(operateDescription = "成功登录系统")
@Override
public void logSuccess(){ }
补充:关于在Service层和Controller层进行Aop拦截的配置 (如果不生效需要注意配置的配置以及扫描的位置)
一般我们将扫描@Service写在applicationContext.xml。因此在applicationContext.xml配置的AOP自动代理对@Service层的注解有效,如果我们需要在Controller层实现注解AOP,我们需要将AOP注解配置在SpringMVC.xml也写一份,在SpringMVC.xml中只是扫描@Controller注解
- Spring配置文件applicationContext.xml配置
<!-- 6.开启注解AOP (前提是引入aop命名空间和相关jar包) -->
<aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true"></aop:aspectj-autoproxy> <!-- 7.开启aop,对类代理强制使用cglib代理 -->
<aop:config proxy-target-class="true"></aop:config> <!-- 8.扫描 @Service @Component 注解-->
<context:component-scan base-package="cn.xm.jwxt" >
<!-- 不扫描 @Controller的类 -->
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>
- SpringMVC的配置文件SpringMVC.xml
<!--1.扫描controller-->
<context:component-scan base-package="cn.xm.jwxt.controller" />
<!-- 2.开启aop,对类代理强制使用cglib代理 -->
<aop:config proxy-target-class="true"/>
<!-- 3开启注解AOP (前提是引入aop命名空间和相关jar包) 暴露代理类-->
<aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true"/>
最后给几个链接,不明白上面的可以参考:
注解的使用:http://www.cnblogs.com/qlqwjy/p/7139068.html
Spring中获取request和session对象:http://www.cnblogs.com/qlqwjy/p/8747136.html
SpringAOP的使用方法:http://www.cnblogs.com/qlqwjy/p/8729280.html
SpringAOP+注解实现简单的日志管理的更多相关文章
- SpringAOP拦截Controller,Service实现日志管理(自定义注解的方式)
转载:http://itindex.net/detail/50710-springaop-controller-service 从业近二,三年了,第一次写博客,平时做做脚手架或者架构一些基础框架然后给 ...
- c#: 简单的日志管理类(TextWriterTraceListener)
以c#实现轻量级的日志管理,着实简单,置一静态类记之: /// <summary> /// 日志管理 /// </summary> public static class Lo ...
- SpringAop之日志管理
导入的依赖均为JavaWeb界面在线配置代码生成器这篇文章,你只需将这篇文章的maven依赖导入即可. SpringAop利用注解的特性进行日志管理,只需在对应的方法上加上自己编写的注解,即可完美实现 ...
- SQL Server中的事务日志管理(4/9):简单恢复模式里的日志管理
当一切正常时,没有必要特别留意什么是事务日志,它是如何工作的.你只要确保每个数据库都有正确的备份.当出现问题时,事务日志的理解对于采取修正操作是重要的,尤其在需要紧急恢复数据库到指定点时.这系列文章会 ...
- SpringAop进行日志管理。
在java开发中日志的管理有非常多种.我通常会使用过滤器,或者是Spring的拦截器进行日志的处理.假设是用过滤器比較简单,仅仅要对全部的.do提交进行拦截,然后获取action的提交路径就能够获取对 ...
- LogCook 一个简单实用的Android日志管理工具
众所周知,日志的管理是软件系统很重要的一部分,千万不可忽略其重要性.完整的日志将会在系统维护中起着异常重要的作用,就好像磨刀不误砍柴工一样,日志就像对系统进行分析的工具,工具便捷了,对系统分析起来就能 ...
- spring AOP自定义注解方式实现日志管理
今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...
- spring AOP自定义注解 实现日志管理
今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...
- Spring AOP 自定义注解实现统一日志管理
一.AOP的基本概念: AOP,面向切面编程,常用于日志,事务,权限等业务处理.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容(Spring核心之一),是函数式编程 ...
随机推荐
- python 操作系统模块 -- OS
os,语义为操作系统,模块提供了访问多个操作系统服务的功能,可以处理文件和目录这些我们日常手动需要做的操作.os和它的子模块os.path还包括一些用于检查.构造.删除目录和文件的函数,以及一些处理路 ...
- 第八届蓝桥杯国赛java B组第三题
标题:树形显示 对于分类结构可以用树形来形象地表示.比如:文件系统就是典型的例子. 树中的结点具有父子关系.我们在显示的时候,把子项向右缩进(用空格,不是tab),并添加必要的连接线,以使其层次关系更 ...
- 【BZOJ3507】通配符匹配(哈希,动态规划)
[BZOJ3507]通配符匹配(哈希,动态规划) 题面 BZOJ 题解 对于匹配唯一存在影响的只有通配符,而\(?\)的影响也并不大,所以唯一需要仔细考虑的是\(*\). 考虑一个\(dp\),设\( ...
- [学习笔记&教程] 信号, 集合, 多项式, 以及各种卷积性变换 (FFT,NTT,FWT,FMT)
目录 信号, 集合, 多项式, 以及卷积性变换 卷积 卷积性变换 傅里叶变换与信号 引入: 信号分析 变换的基础: 复数 傅里叶变换 离散傅里叶变换 FFT 与多项式 \(n\) 次单位复根 消去引理 ...
- afinalDb 用法
研究Afinal是为了弄懂它到底是怎么实现的,它怎么就能够实现了呢?不过,现在先要看一下怎么用,再从表面推导内在. 本文就Afinal中建表.添加.删除.查找等常见数据库操作加以说明.探索. 一.创建 ...
- ReactNative组件之scrollView实现轮播
想要实现轮播效果,首先安装时间定时器 接下来就是在我们的项目中使用定时器 接下来我们将竖着的轮播图变成横着的 接下来我们调整间距 我们知道轮播图下方,还有5个圆点,那我们怎么做呢? 拿到每一个圆点 看 ...
- (大数 求余) Large Division Light OJ 1214
Large Division Given two integers, a and b, you should check whether a is divisible by b or not. We ...
- vue资源
Vue中文官网:https://cn.vuejs.org/ Vue源码:https://github.com/vuejs/vue Vue官方工具:https://github.com/vuejs vu ...
- css小笔记
一.优先级 important>内联>id>class = 属性 = 伪类 >标签 = 伪元素 > 通配符(*) important声明 1,0,0,0 ID选择器 0, ...
- 面向对象【day07】:类的实例化过程剖析(三)
本节内容 1.概述 2.类的语法 3.总结 一.概述 之前我们说关于python中的类,都一脸懵逼,都想说,类这么牛逼到底是什么,什么才是类?下面我们就来讲讲,什么是类?它具有哪些特性. 二.类的语法 ...