总结以前的Spring Aop实现全局日志记录功能
Spring基本篇操作日志admin 发布于:2019-04-05 09:21:27
阅读:loading
使用Spring Aop记录全局的日志的实现本次是第三次改进了,对于这个功能我是小有心得,记得头一次实现这个还是portal项目的那个年代,你我还不是一般的渣渣水平的时候,由一个领导哥实现的,大概的实现方案是采用Aop全局拦截Service函数,获得所有函数的返回值,将返回值记录在数据库,以实现用户的操作日志记录功能,换句话说,这使得我们整个项目的所有增删类的功能都要修改为带返回值类型的实现,即在根据ID删除数据的同时,我们需要先根据ID将数据查询出来,再去做删除的动作,最后将查询出来的这个对象作为返回值,同时也需要重写这个返回值类型的对象的toString()函数(当年还没有json的概念,还是xml格式数据交换盛行的年代)。
上述这种记录日志功能的实现我一直记在心里,但是我并不认为这种实现比较好,因为它限定了函数的必须返回值类型和返回类型的toString,所以在本站的全局日志记录功能又是另外一种实现(没记错的话这应该是在2014年实现的),同样使用Aop拦截所有service函数,但拦截的所有函数为最后一个返回值类型为SysOperationLog的类型,该类为javabean对象,包括的属性有:用户ID、功能模块名称、用户IP、当前时间、日志内容,参考定义如下:
public class SysOperationLog {
private Long operId;//主键ID
private Long operUser;//cao作人账号
private String operTime;//cao作时间
private String operUserIp;//用户IP
private String operContent;//内容信息
private Integer operType;//日志功能类型(如:系统角色日志、功能日志),对应EnumLogType枚举
}
我将所有待记录的信息均存储至operContent属性中,当记录有用户相关信息时在Controller中设置,如用户ID、用户IP、cao作时间,参考调用方式为:
public class SysUserController {
@RequestMapping("/saveUserRoles")
public String saveUserRoles(Integer roleId ,Long accId) throws Exception {
sysUserService.saveUserRoles(roleId , accId, super.getSysOperationLog(EnumLogType.SYS_USER));
return super.outPutJsonSuccess("保存用户角色成功!");
}
}
此处的super.getSysOperationLog函数中封装了用户ID、用户IP、cao作时间,只需要置顶模块名称即可,方便后续的日志查询(根据模块名称查询日志分类),如此处的SYS_USER为系统用户管理,参考该函数的实现为:
protected SysOperationLog getSysOperationLog(EnumLogType enumLogType){
SysOperationLog sysOperationLog = new SysOperationLog();
SysUser sysUser = this.getCurrentUser();
if(sysUser != null){
sysOperationLog.setOperUser(sysUser.getUserId());
}
sysOperationLog.setOperUserIp(this.getRemoteIp(request));
sysOperationLog.setOperContent(null);//内容需要在函数执行完毕后再执行,防止新增时获取不到新增后的主键值
sysOperationLog.setOperType(enumLogType.getKey());
sysOperationLog.setOperTime(DateUtil.getFormatDate(DateUtil.DATE_TIME_PATTERN));
return sysOperationLog;
}
在service实现中就主要是将待记录的参数存入数据库当中,包含中间过程中入库后返回的数据库主键等信息,再转换为json格式,参考如下:
public void saveUserRoles(Integer roleId, Long accId, SysOperationLog sysOperationLog) {
int idKey = sysUserDao.updateSqlKey("sysUserDao.saveUserRole", new Object[]{ roleId , accId });
//保存完毕之后,记录cao作日志,将相关数据转为json
JSONObject json = new JSONObject();
json.put("accId", accId);
json.put("roleId", roleId);
json.put("idKey", idKey);
sysOperationLog.setOperContent(json.toJSONString());
}
再来看看Aop相关的吧,首先是xml配置,拦截所有模块service中最后一个参数类型为SysOperationLog的,参考定义如下:
<aop:aspectj-autoproxy/>
<aop:config>
<!-- 切入点描述:execution:表示执行函数,第一个*表示函数的返回类型,为任意类型,后面的为包名以及子包,最后一个*表示方法,最后两个..表示参数可以为任意类型的任意个 -->
<aop:pointcut expression="execution(* com.frame.mo*.*.service.*.*(..,com.frame.base.operationlog.model.SysOperationLog))" id="sysOperactionLogServicePointcut"/>
<aop:advisor id="tx2" order="2" advice-ref="txAdvice" pointcut-ref="sysOperactionLogServicePointcut"/>
<!-- 相关Service按规则记录的日志,最后一个参数是系统cao作日志类型的 -->
<aop:aspect ref="sysOperationLogAop">
<aop:around method="around" pointcut-ref="sysOperactionLogServicePointcut"/>
</aop:aspect>
</aop:config>
最后再附上Aop拦截器的实现代码,saveSysOperationLog函数为具体的数据入库实现,如下:
package com.frame.base.aop;
import ...;
/**
* 记录Service的cao作时间
* @author chendd
*/
@Component
public class SysOperationLogAop {
private Log log = LogFactory.getLog(getClass());
@Resource
private ISysOperationLogService operationLogService;
public Object around(ProceedingJoinPoint point) throws Throwable{
Object object = null;
try {
object = point.proceed();
//如果当前调用函数的参数不为0个,则取其最后一个参数作为函数的日志记录内容
Object args[] = point.getArgs();
int argLens = args.length;
//由于此service的定义位置特殊,否则会引起循环调用拦截器日志的后果//
operationLogService.saveSysOperationLog((SysOperationLog) args[argLens - 1]);
log.info("AOP记录日志信息");
System.out.println("AOP记录日志信息...");
} catch (Exception e) {
log.error("AOP记录异常信息" , e);
throw new RuntimeException(e);
}
return object;
}
}
至此一个靠谱的实现全站用户cao作的日志功能已经完结,与之前领导哥的根据返回值的实现相比较则更为合适,另外一点是早已经有了许多的JSON格式数据,无需再重写toString函数(这个则不算优点),但分析其实现的优缺点:
优点
(1)可按约定规则需方面进行日志记录;
(2)实现简单,可记录函数在执行过程中生成的各种参数值;
缺点
(1)始终还是切入到中间代码了(需记录的数据);
(2)以上实现未处理service报错时的日志记录,实际生产环境可能记录错误参数更好,因为便于分析问题;
点赞