自定义切面实现用户日志记录--AOP
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
在项目中遇到要记录用户操作的需求,分析应该使用切面编程的方式实现。下面实现代码。
日志类Log
/**
* @author shipc 2019/4/24 17:09
* @version 1.0
*/
public class Log {
private static final long serialVersionUID = -7943478965732892796L; private String title; // 日志标题
private String type; // 日志类型(info:入库,error:错误)
private String method; // request 方法 post/put/delete/get
private String url; // 请求路径
private String params; // 请求参数
private String response; // 响应信息
private String exception; // 异常信息
private Long time; // 执行时长
private String ip; // ip地址 @JsonSerialize(using = JsonDateTimeSerializer.class)
private Date createTime;
}
先定义一个注解类SysLog
import java.lang.annotation.*; /**
* 系统日志注解类
* @author shipc
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog { String value() default ""; // 操作类型
}
之后编写切面
/**
*
* @author shipc 2019/5/5 10:53
* @version 1.0
*/
@Aspect
@Component
public class SysLogAspect { private static final Logger logger = LoggerFactory.getLogger(SysLogAspect.class); private static final ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<>("ThreadLocal beginTime"); private static final ThreadLocal<Log> logThreadLocal = new NamedThreadLocal<>("ThreadLocal log"); private static final ThreadLocal<LoginUserOutDTO> currentUser = new NamedThreadLocal<>("ThreadLocal user"); @Autowired
private HttpServletRequest request; @Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor; // 这里用到了线程池 @Autowired
private SysLogService sysLogService; /**
* Controller层切点 注解拦截
*/
@Pointcut("@annotation(这是切点SysLog)")
public void logPointCut() {
// 注解拦截
} @Before(value = "logPointCut()")
public void doBefore() {
logger.info("SysLogAspect:记录用户操作日志开始");
Date beginTime = new Date();
beginTimeThreadLocal.set(beginTime); // 记录开始时间 LoginUserOutDTO user = ShiroUtils.getUser(); // 这里是用Shiro 做的,获取当前登录的用户
currentUser.set(user);
} @After(value = "logPointCut()")
public void doAfter(JoinPoint joinPoint) {
LoginUserOutDTO user = currentUser.get(); if ( user == null) {
user = ShiroUtils.getUser();
} Object[] args = joinPoint.getArgs();
List<Object> argsList = new ArrayList<>();
// 不记录 多媒体文件,Request,Response
for (Object arg: args) {
if (!(arg instanceof MultipartFile || arg instanceof ServletRequest || arg instanceof ServletResponse)) {
argsList.add(arg);
}
}
String title = "";
String type = "info";
String remoteAddr = IPUtils.getIpAddr(request); // 请求IP
String requestUri = this.request.getRequestURI(); // 请求URI
String method = this.request.getMethod(); // 请求方法类型
try {
title = getLogValue(joinPoint);
} catch (Exception e) {
logger.error(e.getMessage());
} long beginTime = beginTimeThreadLocal.get().getTime(); // 得到线程绑定的局部变量(开始时间)
long endTime = System.currentTimeMillis(); // 结束时间 // 设置字段
Log log = logThreadLocal.get();
if (log == null) {
log = new Log();
}
log.setUuid(UuidUtils.getUuid());
log.setTitle(title);
log.setType(type);
log.setUrl(requestUri);
log.setMethod(method);
log.setIp(remoteAddr);
String jsonString = JSON.toJSONString(argsList);
log.putParams(jsonString);
log.setCreateTime(beginTimeThreadLocal.get());
log.setTime(endTime - beginTime);
log.setCreateUser(user == null ? "" : user.getUserName()); logThreadLocal.set(log);
} @AfterReturning(value = "logPointCut()", returning = "result")
public void doReturn(Object result) {
Log log = logThreadLocal.get();
if (log == null) {
log = new Log();
}
if (result instanceof DataResponse) {
DataResponse response = new DataResponse();
DataResponse dataResponse = (DataResponse) result;
response.setCode(dataResponse.getCode());
String message = dataResponse.getMessage();
response.setMessage(message.length() < 200 ? message : message.substring(0,200));
log.putResponse(JSON.toJSONString(response));
} else {
String jsonString = JSON.toJSONString(result);
log.putResponse(jsonString);
}
threadPoolTaskExecutor.execute(new SaveLogThread(log, sysLogService)); logThreadLocal.remove();
currentUser.remove();
beginTimeThreadLocal.remove();
} @AfterThrowing(pointcut = "logPointCut()", throwing = "e")
public void doAfterThrowing(Throwable e) {
Log log = logThreadLocal.get();
if (log != null) {
log.setType("error");
log.setException(e.getClass().getSimpleName()); threadPoolTaskExecutor.execute(new UpdateLogThread(log, sysLogService));
}
logThreadLocal.remove();
currentUser.remove();
beginTimeThreadLocal.remove();
} /**
* 获取注解中对方法的描述信息
* @param joinPoint 切点
* @return 用户操作
*/
private static String getLogValue(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysLog sysLog = method.getAnnotation(SysLog.class);
return sysLog.value();
} /**
* 保存日志线程
*/
private static class SaveLogThread implements Runnable {
private Log log;
private SysLogService logService; private Logger logger = LoggerFactory.getLogger(this.getClass()); SaveLogThread(Log log, SysLogService logService) {
this.log = log;
this.logService = logService;
} @Override
public void run() {
try {
logService.saveLog(log);
} catch (Exception e) {
logger.info(e.getMessage());
}
}
} /**
* 更新日志线程
*/
private class UpdateLogThread implements Runnable {
private Log log;
private SysLogService logService;
private Logger logger = LoggerFactory.getLogger(this.getClass()); UpdateLogThread(Log log, SysLogService sysLogService) {
this.log = log;
this.logService = sysLogService;
} @Override
public void run() {
try {
this.logService.updateById(log);
} catch (Exception e) {
logger.info(e.getMessage());
}
}
}
}
使用在controller方法中使用
@SysLog("登录")
public DataResponse<LoginInfoOutDTO> login(@RequestBody @Validated DataRequest<LoginInfoInDTO> request) {
// 方法体
}
注意:使用ThreadLocal时,记得调用remove()方法。
注意:正常的顺序@Before,@After,@AfterReturning
异常的顺序@Before,@After,@AfterThrowing
自定义切面实现用户日志记录--AOP的更多相关文章
- 自定义注解实现(spring aop)
1.基本概念 1.1 aop 即面向切面编程,优点是耦合性低,能使业务处理和切面处理分开开发,扩展和修改方面,当引入了注解方式时,使用起来更加方便. 1.2 应用场景 打日志.分析代码执行时间.权限控 ...
- SpringAop切面实现日志记录
SpringAop切面实现日志记录代码实现:https://www.cnblogs.com/wenjunwei/p/9639909.html 问题记录 1.signature.getMethod(). ...
- Spring 08: AOP面向切面编程 + 手写AOP框架
核心解读 AOP:Aspect Oriented Programming,面向切面编程 核心1:将公共的,通用的,重复的代码单独开发,在需要时反织回去 核心2:面向接口编程,即设置接口类型的变量,传入 ...
- 接口日志记录AOP实现-LogAspect
使用spring aop日志记录 所需jar包 pom.xml <!-- logger begin --> <dependency> <groupId>org.sl ...
- Mysqldump自定义导出n条记录
很多时候DBA需要导出部分记录至开发.测试环境,因数据量需求较小,如果原库的记录多,且表数量也多,在用mysqldump命令导出时可以添加一个where参数,自定义导出n条记录,而不必全量导出. 示例 ...
- Serilog 自定义Enricher 来增加记录的信息
Serilog 自定义Enricher 来增加记录的信息 Intro Serilog 是 .net 里面非常不错的记录日志的库,结构化日志记录,而且配置起来很方便,自定义扩展也很方便 Serilog ...
- Serilog 自定义 Enricher 来增加记录的信息
原文:Serilog 自定义 Enricher 来增加记录的信息 Serilog 自定义 Enricher 来增加记录的信息 Intro Serilog 是 .net 里面非常不错的记录日志的库,结构 ...
- 什么是 DNS 的 A记录 和 CNAME记录 域名解析 为我的自定义域名创建 CNAME 记录
# CNAME https://support.google.com/blogger/answer/58317?hl=zh-Hans 为我的自定义域名创建 CNAME 记录 如果您的域名不是在 Blo ...
- Spring自定义注解配置切面实现日志记录
一: spring-mvc.xml: <!--配置日志切面 start,必须与mvc配置在同一个配置文件,否则无法切入Controller层--><!-- 声明自动为spring容器 ...
随机推荐
- JAVA 设计的七大原则
一.开闭原则 开闭原则(Open-Closed Principle, OCP)是指一个软件实体如类.模块和函数应该对 扩展开放,对修改关闭. 所谓的开闭,也正是对扩展和修改两个行为的一个原则.强调 的 ...
- MindManager全部快捷键(官方英文文档+中文翻译)
MindManager全部快捷键 [中文翻译版] ------------------------------------------------------------- 导图文件 创建一个新的导图 ...
- UVA-307-Sticks-dfs+剪枝
George took sticks of the same length and cut them randomly until all parts became at most 50 units ...
- pip install mysql-python报错1. Unable to find vcvarsall.bat 2 fatal error C1083: Cannot open include file: 'config-win.h': No such file or directory 3.error: command 'mt.exe' failed with exit statu
最近在安装mysql -python 时报错折腾了半天,通过以下方法解决: 1. pip install mysql-python报错 Unable to find vcvarsall.bat (参考 ...
- Wireless Password HDU - 2825
题意: 给出m个模式串,要求构造一长度为n的文本串,至少包括k种模式串,求有多少种可能的模式串. k<=10 然后可以想到状压 一个文本串,k种模式串,很容易想到AC自动机. 把所有的模式串放 ...
- 查出当前操作数据库的登入名SUSER_NAME()
select SUSER_NAME() 一般和触发器一起用来监控是谁对表做了操作
- 一个tcp连接可以发多少http请求
-----来自:松若章 -----zhuanlan.zhihu.com/p/61423830 曾经有这么一道经典面试题:从 URL 在浏览器被被输入到页面展现的过程中发生了什么?相信大多数准备过的同学 ...
- 全面理解python中self的用法
self代表类的实例,而非类. class Test: def prt(self): print(self) print(self.__class__) t = Test() t.prt() 执行结果 ...
- 线性回归和梯度下降代码demo
程序所用文件:https://files.cnblogs.com/files/henuliulei/%E5%9B%9E%E5%BD%92%E5%88%86%E7%B1%BB%E6%95%B0%E6%8 ...
- EF Code First数据库连接配置
前面几节,使用的都是通过EF Code First创建的新数据库,接下来,将开始使用已存在的数据库. 1.使用配置文件设置数据库连接 App.config 数据库连接字符串的name与Data中Nor ...