自定义切面实现用户日志记录--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容器 ...
随机推荐
- VS2010-MFC(常用控件:列表框控件ListBox)
转自:http://www.jizhuomi.com/software/186.html 列表框控件简介 列表框给出了一个选项清单,允许用户从中进行单项或多项选择,被选中的项会高亮显示.列表框可分为单 ...
- POJ-1976-A Mini Locomotive-dp
A train has a locomotive that pulls the train with its many passenger coaches. If the locomotive bre ...
- 有关Tensorboard问题
先说我的各个版本: 操作系统: win7 64 Python: 3.5 Tensorflow: 1.2 Tensorboard: 1.6 错误一: 只显示Graphs,不显示Histogram和Sca ...
- 测试环境添加spark parcel 2.1步骤
1.先到http://archive.cloudera.com/spark2/parcels/2.1.0.cloudera1/ 下载需要的文件比如我linux版本需要是6的 hadoop6需要下载这些 ...
- iOS开发系列-线程同步技术
概述 多线程的本质就是CPU轮流随机分配给每条线程时间片资源执行任务,看起来多条线程同时执行任务. 多条线程同时访问同一块资源,比如操作同一个对象.统一变量.同一个文件,就会引发数据错乱和数据安全的问 ...
- Java开发系列-电子邮箱
概述 电子邮箱就是在邮箱服务器上开启的一块空间.邮箱服务器就是一个安装在计算机的服务软件,提供有邮件服务. 邮箱的发送流程 现在tom要发送一份邮件给jerry,首先tom将邮件通过客户端连接自己设置 ...
- Linux 实用指令(8)--网络配置
目录 网络配置 1 Linux网络配置原理图(含虚拟机) 2 查看网络IP和网关 2.1 查询虚拟网络编辑器 2.2 修改IP地址(修改虚拟网络的IP) 2.3 查看网关 2.4 查看windows环 ...
- [NOIP2019模拟赛]夹缝
夹缝 问题描述: 二维空间内有两面相对放置的,向无限远延伸的镜子,以镜子的方向及其法向量建立坐标系,我们选定一个法向量方向下面称“上”.在镜子上的整数位置,存在着一些传感器,传感器不影响光线的反射,光 ...
- 关于str的打印格式
实际上一般的打印格式应该是这样的: %[(keyname)][flags][width][.precision]typecode (1) keyname: 用于打印Dict类型时做索引; (2) fl ...
- 2-sat——输出方案poj3683
一篇讲的详细的博客 https://blog.csdn.net/Hawo11/article/details/74908233 缩点后为什么要建立反图? 如果是按原图处理,选择一个点之后要把所有其后续 ...