自定义切面实现用户日志记录--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容器 ...
随机推荐
- Linux后台运行java的jar包后台运行java -jar 命令
为什么java -jar 的命令终端的窗口关闭就停止运行了??tomcat中war的就不会? 关闭终端的窗口相当于ctrl+c的命令,关闭了窗口就相当于停止了java -jar这个进程,即ctrl+c ...
- Docker系列(二):Docker基础命令
docker的部署安装(Linux kernel至少3.8以上): yum install docker docker1.8安装:(下面 是两个命令) # cat >/etc/yum.repos ...
- windows 映射samba Linux服务器,输入正确的账号密码却提示“ 指定的网络密码不正确
重启Linux samba服务也没用,重启Linux和windows系统也没用,急!!! 最佳答案 linux中要添加对应的系统用户和samba用户useradd titiansmbpasswd -a ...
- Caffe系列2——Windows10制作LMDB数据详细过程(手把手教你制作LMDB)
Windows10制作LMDB详细教程 原创不易,转载请注明出处:https://www.cnblogs.com/xiaoboge/p/10678658.html 摘要: 当我们在使用Caffe做深度 ...
- POJ3321Apple Tree
Apple Tree Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 39566 Accepted: 11727 Descript ...
- openSUSE 安装LAMP记录
按照 openSUSE SDB:LAMP setup安装好了LAMP.运行的大多数命令都是来自与openSUSE SDB:LAMP setup中. 本页面描述如何安装LAMP,这是 Linux Apa ...
- 二分图——多重匹配模板hdu1669
好像多重匹配一般是用网络流来做的.. 这是匈牙利算法的模板:lim是每个组的上界 思路是每个组都可以匹配lim个点,那么当点x遇到的组匹配的点数还没有超过lim时,直接匹配即可 如果已经等于了lim, ...
- 利用IDEA构建springboot应用--controller例子
微服务 微服务是一个新兴的软件架构,就是把一个大型的单个应用程序和服务拆分为数十个的支持微服务.一个微服务的策略可以让工作变得更为简便,它可扩展单个组件而不是整个的应用程序堆栈,从而满足服务等级协议. ...
- codeforces 1099E-Nice table
传送门:QAQQAQ 题意:给你一个矩阵只有AGCT,若对于每一个2*2的子矩阵中的四个字母互不相同,则称为这个矩阵是nice的,问至少变矩阵中的几个点可以使矩阵变nice 思路:没什么思路……就是大 ...
- CF #578 Div2
// 比赛链接:https://codeforces.com/contest/1200 A - Hotelier 题意: 有一家旅馆有10间房,编号0~9,从左到右顺序排列.旅馆有左右两扇门,每次新来 ...