在软件业,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的更多相关文章

  1. 自定义注解实现(spring aop)

    1.基本概念 1.1 aop 即面向切面编程,优点是耦合性低,能使业务处理和切面处理分开开发,扩展和修改方面,当引入了注解方式时,使用起来更加方便. 1.2 应用场景 打日志.分析代码执行时间.权限控 ...

  2. SpringAop切面实现日志记录

    SpringAop切面实现日志记录代码实现:https://www.cnblogs.com/wenjunwei/p/9639909.html 问题记录 1.signature.getMethod(). ...

  3. Spring 08: AOP面向切面编程 + 手写AOP框架

    核心解读 AOP:Aspect Oriented Programming,面向切面编程 核心1:将公共的,通用的,重复的代码单独开发,在需要时反织回去 核心2:面向接口编程,即设置接口类型的变量,传入 ...

  4. 接口日志记录AOP实现-LogAspect

    使用spring aop日志记录 所需jar包 pom.xml <!-- logger begin --> <dependency> <groupId>org.sl ...

  5. Mysqldump自定义导出n条记录

    很多时候DBA需要导出部分记录至开发.测试环境,因数据量需求较小,如果原库的记录多,且表数量也多,在用mysqldump命令导出时可以添加一个where参数,自定义导出n条记录,而不必全量导出. 示例 ...

  6. Serilog 自定义Enricher 来增加记录的信息

    Serilog 自定义Enricher 来增加记录的信息 Intro Serilog 是 .net 里面非常不错的记录日志的库,结构化日志记录,而且配置起来很方便,自定义扩展也很方便 Serilog ...

  7. Serilog 自定义 Enricher 来增加记录的信息

    原文:Serilog 自定义 Enricher 来增加记录的信息 Serilog 自定义 Enricher 来增加记录的信息 Intro Serilog 是 .net 里面非常不错的记录日志的库,结构 ...

  8. 什么是 DNS 的 A记录 和 CNAME记录 域名解析 为我的自定义域名创建 CNAME 记录

    # CNAME https://support.google.com/blogger/answer/58317?hl=zh-Hans 为我的自定义域名创建 CNAME 记录 如果您的域名不是在 Blo ...

  9. Spring自定义注解配置切面实现日志记录

    一: spring-mvc.xml: <!--配置日志切面 start,必须与mvc配置在同一个配置文件,否则无法切入Controller层--><!-- 声明自动为spring容器 ...

随机推荐

  1. (1)mysql数据库操作

    1.安装mysql https://dev.mysql.com/downloads/windows/installer/8.0.html 2.mysql启停 运行mysql         net s ...

  2. 阿里云CentOs7上安装GitLab

    一.安装 基本上可以根据官网的教程来安装:https://www.gitlab.com.cn/installation/#centos-7 只不过我们暂时没有邮件服务器,所以postfix没有安装. ...

  3. 新增加的html里的标签元素,该元素作为事件,js获取不到id的问题

    if(flag==0){                var p = document.createElement("p");                  var text ...

  4. MongoDB特性及使用场景

    概述 MongoDB(Humongous Database),中文意思就是巨大无比的数据库,顾名思义,MongoDB就是为处理大数据而生,以解决海量数据的存储和高效查询使用为使命.MongoDB是一款 ...

  5. Python flask 构建可扩展的restful apl✍✍✍

    Python flask 构建可扩展的restful apl  整个课程都看完了,这个课程的分享可以往下看,下面有链接,之前做java开发也做了一些年头,也分享下自己看这个视频的感受,单论单个知识点课 ...

  6. GridView编辑删除

    A前台代码 <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="test.asp ...

  7. Redis学习目录

    目录   持续更新... Redis简介 Redis安装及基本配置 Redis持久化 Redis开发及管理实战 Redis高可用及集群 Redis多API开发  

  8. zabbix被监控端代理设置

    zabbix被监控端代理设置 安装zabbix-agent客户端 rpm -ivh https://repo.zabbix.com/zabbix/4.0/rhel/7/x86_64/zabbix-re ...

  9. centos6 php7 安装 memcache 和 memcached

    下载安装memcache 注意:官网的memcache包,暂时好像不支持php7.所以到下面地址下载memcache包,切换到php7分支 php7 memcache github 下载地址 官网下载 ...

  10. MyEclipse使用总结——将原有的MyEclipse中的项目转成maven项目[转]

    前面一篇文章中我们了解了 在myeclipse中新建Maven框架的web项目 那么如果我们原来有一些项目现在想转成maven项目应该怎么做呢 我收集到了三种思路: 一.新建一个maven项目,把原项 ...