在软件业,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. 更改网卡名称以及重启网卡提示Determining if ip address x.x.x.x is already in use for device eth0

    安装系统完成后,在CentOS6.6下网卡名称变为em1,有些不太方便,还是改回eth0 修改grub配置文件,vi /boot/grub/grub.conf,增加如下红色字体 kernel /vml ...

  2. Mybatis功能架构及执行流程

    原文地址:http://blog.51cto.com/12222886/2052647 一.功能架构设计 功能架构讲解: 我们把Mybatis的功能架构分为三层: (1) API接口层:提供给外部使用 ...

  3. python-包管理工具-pip

    目录 Python pip pip相关命令 解决pip相关问题 Python pip回到顶部 Python最让人的喜欢的就是它有丰富的类库和各种第三方的包,而对于这些包的下载.删除等管理操作,就要用到 ...

  4. neo4j安装APOC插件

    1.APOC下载地址:https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/3.4.0.1 只要下载.jar这一个压缩文件就好 ...

  5. Vue项目在Docker的自动化部署

    操作系统:CentOS 部署环境:Docker CI/CD工具:Jenkins 1.环境配置 安装Jenkins:Centos安装Jenkins 安装Docker:Centos安装Git.DotNet ...

  6. 【默默努力】fishingGame

    这个捕鱼游戏挺有意思的,通过发射子弹,打鱼.打鱼的子弹会消耗金币,但是打鱼如果打到了鱼,就会奖励金币的数量. 我如果写这个的话,应该会画一个 背景海底,然后生成很多鱼的图片,还要有一个大炮,金币.大炮 ...

  7. Axios的get和post请求写法

    执行get请求 // 为给定 ID 的 user 创建请求 axios.get('/user?ID=12345') .then(function (response) { console.log(re ...

  8. Excel生成Oracle数据库表sql工具类

    1.解决问题: 开发文档中字段比较多的时候,建表sql(Oracle下划线命名规范)比较麻烦,容易出错~~ (主要是懒) 特意手写一个工具,根据excel字段,生成建表的sql语句. ~~~末尾附Gi ...

  9. Android 学习 (持续添加与更新)

    N.GitHub 最受欢迎的开源项目 http://www.csdn.net/article/2013-05-03/2815127-Android-open-source-projects 六.and ...

  10. WCF进阶:扩展bindingElementExtensions支持对称加密传输

      前面两篇文章WCF进阶:将编码后的字节流压缩传输和WCF 进阶: 对称加密传输都是实现了自定义编码,那两个例子中托管服务或者客户端调用都采用的代码实现,WCF更友好的方式是在app.config或 ...