前言
平时在java项目开发过程中,涉及到记录操作日志的场景很多,有时候大家习惯把操作日志的生成代码写到业务代码中,这样造成了日志和业务代码的耦合性比较高、可维护性也不强,易读性更差,更多的时候是使用AOP切面编程进行操作日志记录,当前网上也能百度到一些使用样例,但是使用场景比较简单,或者示例代码不够完整,本文主要针对使用aop做操作日志记录做一个总结分享;

主要功能
1)基于spring的aop面向切面编程,与业务代码解耦;
2)使用自定义注解用于标注要产生操作日志的方法;
3)可在自定义标签中设置要数据的入参属性值;

如下:

@SysLog("用户添加.concat(#user.role.roleName)")

4)使用spring event对日志生成和存储进一步解耦,在微服务环境,日志存储功能和生成可以位于不同的服务上;

5)参数名及参数值的解析;

Spring AOP

方便大家理解,这里简单回顾一下AOP的关键注解

1)切面(Aspect):在Spring AOP中,切面可以使用通用类或者在普通类中以@Aspect 注解(@AspectJ风格)来实现
2)连接点(Joinpoint):在Spring AOP中一个连接点代表一个方法的执行
3)通知(Advice):在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类型,其中包括"around"、"before”和"after"等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链
4)切入点(Pointcut):定义出一个或一组方法,当执行这些方法时可产生通知,Spring缺省使用AspectJ切入点语法。

通知类型:
前置通知(@Before):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)
返回后通知(@AfterReturning):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回
抛出异常后通知(@AfterThrowing):方法抛出异常退出时执行的通知
后通知(@After):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)
环绕通知(@Around):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型,环绕通知可以在方法调用前后完成自定义的行为,它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行

代码示例

代码结构

1)定义自定义注解

package com.liuy.esports1.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author dell*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SysLog {String value() default "";
}

2)定义切面类

关键切面类:
AspectJ使用org.aspectj.lang.JoinPoint接口表示目标类连接点对象,如果是环绕增强时,使用org.aspectj.lang.ProceedingJoinPoint表示连接点对象,该类是JoinPoint的子接口。任何一个增强方法都可以通过将第一个入参声明为JoinPoint访问到连接点上下文的信息。我们先来了解一下这两个接口的主要方法:

1) JoinPoint
java.lang.Object[] getArgs():获取连接点方法运行时的入参列表;
Signature getSignature() :获取连接点的方法签名对象;
java.lang.Object getTarget() :获取连接点所在的目标对象;
java.lang.Object getThis() :获取代理对象本身;

2) ProceedingJoinPoint
ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法:

java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法;
java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。

package com.liuy.esports1.aspect;import com.alibaba.fastjson.JSONObject;
import com.liuy.esports1.annotation.SysLog;
import com.liuy.esports1.event.SysLogEvent;
import com.liuy.esports1.utils.IPUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;@Log4j2
@Aspect
@Component
@RequiredArgsConstructor
public class SysLoggerAspect {private final ApplicationContext applicationContext;@Pointcut("@annotation(com.liuy.esports1.annotation.SysLog)")public void logPointCut() {}@Around("logPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {long beginTime = System.currentTimeMillis();// 执行方法Object result = point.proceed();// 执行时长(毫秒)long time = System.currentTimeMillis() - beginTime;//异步保存日志saveLog(point, time);return result;}void saveLog(ProceedingJoinPoint joinPoint, long time) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();SysLogEvent sysLog = new SysLogEvent();SysLog syslog = method.getAnnotation(SysLog.class);if (syslog != null) {// 注解上的描述sysLog.setOperation(syslog.value());}String value = syslog.value();if (value.contains(".concat")) {String propertys = value.substring(value.indexOf("(") + 1, value.lastIndexOf(")"));String paramValue = getOneParam(propertys, joinPoint);value = value.substring(0, value.indexOf(".")) + "[" + paramValue + "]";sysLog.setOperation(value);}// 请求的方法名String className = joinPoint.getTarget().getClass().getName();String methodName = signature.getName();sysLog.setMethod(className + "." + methodName + "()");// 请求的参数Object[] args = joinPoint.getArgs();try {sysLog.setParams(Arrays.toString(args));} catch (Exception e) {}// 获取requestHttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();// 设置IP地址sysLog.setIp(IPUtils.getIpAddr(request));sysLog.setTime((int) time);// 系统当前时间Date date = new Date();sysLog.setGmtCreate(date);// 保存系统日志System.out.println("------------result--------------------------------");System.out.println("------------result--------------------------------");System.out.println("------------result--------------------------------");System.out.println("=============" + sysLog);applicationContext.publishEvent(sysLog);}//匹配目标参数public String getOneParam(String propertys, ProceedingJoinPoint proceedingJoinPoint) {String[] propertyss = propertys.split("\\.");JSONObject paramJson = getAllParam(proceedingJoinPoint);String targetValue = "";for (int i = 0; i < propertyss.length; i++) {String property = propertyss[i];try {if (i != propertyss.length - 1) {if (paramJson.containsKey(property)) {paramJson = paramJson.getJSONObject(property);}} else {if (paramJson.containsKey(property)) {targetValue = (String) paramJson.get(property);}}} catch (Exception e) {log.error("error:{}",e);}}return targetValue;}//通过proceedingJoinPoint获取切入方法的参数名及参数值public JSONObject getAllParam(ProceedingJoinPoint proceedingJoinPoint) {Map<String, Object> map = new HashMap<>();Object[] values = proceedingJoinPoint.getArgs();String[] names = ((CodeSignature) proceedingJoinPoint.getSignature()).getParameterNames();for (int i = 0; i < names.length; i++) {map.put(names[i], values[i]);}return (JSONObject) JSONObject.toJSON(map);}}

3)Controller类

package com.liuy.esports1.controller;import com.liuy.esports1.annotation.SysLog;
import com.liuy.esports1.entity.User;
import org.springframework.web.bind.annotation.*;/*** @author dell*/
@RestController
@RequestMapping("/log")
public class TestLogController {@SysLog("用户查询")@GetMapping("/test")public String test(String name){name = name + "在工作!";return name;}@SysLog("用户添加.concat(#user.role.roleName)")@PostMapping("/test")public String add(@RequestBody User user){String name = user.getName() + "在工作!";return name;}
}

4)实体类

package com.liuy.esports1.entity;import lombok.Data;@Data
public class Role {private Integer roleId;private String roleName;}
package com.liuy.esports1.entity;import lombok.Data;@Data
public class User {private Integer id;private String name;private String age;private Role role;}

5)spring event观察者事件

package com.liuy.esports1.event;import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class SysLogEvent {private Long id;/*** 用户id*/private Long userId;/*** 用户名称*/private String username;/*** 注解上的描述*/private String operation;/*** 执行时间*/private Integer time;/*** 执行方法*/private String method;/*** 参数*/private String params;/*** ip地址*/private String ip;@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")private Date gmtCreate;
}
package com.liuy.esports1.event;import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;@Log4j2
@Configuration
public class SysLogListener {@SneakyThrows@EventListener(SysLogEvent.class)public void onApplicationEvent(SysLogEvent event) {String username = event.getUsername();log.info("-----------------------log insert to db---------");log.info("----data:{}",event.toString());//数据库存储、远程接口调用}}

6)工具类

package com.liuy.esports1.utils;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.servlet.http.HttpServletRequest;public class IPUtils {private static Logger logger = LoggerFactory.getLogger(IPUtils.class);/*** 获取IP地址* * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址*/public static String getIpAddr(HttpServletRequest request) {String ip = request.getHeader("x-forwarded-for");if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;}}

【日志记录】基于AOP实现自定义日志注解,并支持动态设置注解内容相关推荐

  1. windows服务器系统的iis日志,Windows server2012 IIs 8 自定义日志记录

    问题: 通过CDN加速的网站,记录日志时无法追踪源IP,日志的IP都为CDN节点ip. 分析: 1.在解析记录header时,CDN实际会把源IP以其它header的形式回传,如网宿为[Cdn-Src ...

  2. go语言web开发系列之五:gin用zap+file-rotatelogs实现日志记录及按日期切分日志

    一,安装需要用到的库: 1,安装zap日志库: liuhongdi@ku:/data/liuhongdi/zaplog$ go get -u go.uber.org/zap 2,安装go-file-r ...

  3. java aop注解日志记录_spring aop通过注解实现日志记录

    首先是几个概念:连接点(Joinpoint).切点(Pointcut).增强(Advice).切面(Aspect) 另外也要使用到注解. 需求:通过注解定义LogEnable.然后程序运行能够识别定义 ...

  4. html 日志记录组件,使用HTML自定义格式的Log4j.properties进行日志记录

    我需要帮助编辑Apache Log4j文件的输出. 我正在使用html布局来保存创建的日志.这里是我的log4j.properties代码:使用HTML自定义格式的Log4j.properties进行 ...

  5. thinkphp mysql 日志_基于thinkphp实现异常日志详细统计功能

    后端的代码基于thinkphp框架开发,随着业务的增加,代码复杂度不断增多,而且有好几份代码,可能部署在不同的服务器上.即使在测试服务器上经过严格测试,正式环境有时也很难避免出现bug,所以需要较为详 ...

  6. mysql 日志记录 archive_完美起航-Mysql日志管理、备份与恢复

    一.Mysql日志分类 MySQL的默认日志保存位置为/usr/local/mysql/data vim /etc/my.cnf 1.错误日志 说明: 在对应的数据目录中,以主机名+.err命名的文件 ...

  7. asp.net core mcroservices 架构之 分布式日志(二)之自定义日志开发

    netcore日志原理 netcore的日志是作为一个扩展库存在的,每个组件都有它的入口,那么作为研究这个组件的入口是最好的,首先看两种方式: 这个是源码例子提供的. 1 var loggingCon ...

  8. 服务器日志记录_5种改善服务器日志记录的技术

    服务器日志记录 在最近的时间里,我们已经看到了许多工具可以帮助您理解日志. 开源项目(例如Scribe和LogStash),内部部署工具(例如Splunk)以及托管服务(例如SumoLogic和Pap ...

  9. 日志记录到字段变更_Wal日志解析工具开源: Walminer

    作者简介 李传成: 瀚高软件内核研发工程师,主要研究方向为数据库的备份和恢复,对wal日志的原理和应用有较深的理解.自研了wal日志解析工具walminer.pg块恢复工具pg_lightool. 一 ...

最新文章

  1. axios get请求_Axios使用指南
  2. PHP跳转到另一个画面,并且带着该行内的一个数值作为参数传递给下一个页面.能给例子吗...
  3. 【Linux】【Services】【SaaS】Docker+kubernetes(13. 部署Jenkins/Maven实现代码自动化发布)...
  4. Unraveling the JPEG file
  5. VS2013正在等待所需操作完成
  6. 苹果WWDC 2020回顾:来看看这个安卓味的iOS 14!
  7. server端多个文件的压缩 .NET
  8. ds18b20工作原理和测温原理介绍
  9. 蛋白质组学两个定量方法(iBAQ和LFQ)的区别及常见的标准化方法
  10. 求出数组最大值的方法
  11. 车联网大规模商用关键突破口深度调研车路协同智慧高速全国建设情况
  12. 拾叶集 - 江湖一剑客
  13. Wannacry“永恒之蓝”勒索病毒最全防范措施
  14. 架构组件—Android应用中使用视图绑定(binding)
  15. Redhat8认证考试(第三题)
  16. 拉勾网离职风波引人深思 互联网招聘网站还有未来吗?
  17. IDEA突然没有SVN了是怎么回事
  18. #108 – The Logical Tree(逻辑树)
  19. oracle查询员工员工部门领导领导部门,oracle多表查询之经典面试题
  20. http请求发送工具类

热门文章

  1. TCP/IP协议分层模型详解
  2. Windows Terminal美化杂记-Windows Terminal使用与配置
  3. 服务器ie安全增强关闭还是显示,如何关掉ie浏览器的增强安全配置
  4. 网站优化十大方法之关键字篇
  5. 物质的折射率和光的折射率的关系
  6. 使用 JS-SDK 与 FLOW 交互
  7. 设置Win10 输入法默认简体中文
  8. Linux 扩大内存采用扩大SWAP文件方法
  9. JSF 标签大全(非常详细 有例子)
  10. 如何完美快速地卸载office2007,2010,2013,2016