系列文章
1.SpringBoot整合RabbitMQ并实现消息发送与接收
更多该系列文章可以看我主页哦
目录
前言
说到AOP大家都可以想到他是面向切面的编程,它通过将横切关注点(例如日志记录、事务管理、权限控制等)从主要业务逻辑中分离出来,以模块化的方式进行管理。在AOP中,通过定义切面(Aspect)来捕获和处理横切关注点,然后将其应用于特定的目标对象或方法。
官方的解释有点抽象,我们举个例子说明:假设我们需要在多个方法中添加日志记录功能。传统的方式是在每个方法中都添加日志代码,但这样会导致代码重复,并且当我们需要修改日志记录逻辑时,需要逐个修改所有方法。而使用AOP,我们只需定义一个切面,将日志记录的逻辑写在切面中。然后,通过在需要添加日志的地方进行配置,就能自动将切面应用到目标方法中,实现日志记录的功能。
文章说明
本篇文章主要是使用Aop的环绕通知去实现将每次请求的接口信息(操作的模块,请求方法,请求的url,请求的ip,入参,出参,以及耗时)进行记录并存到数据库。
一、准备工作
首先我们导入Aop的坐标
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
因为我们有一些结果要json输出 ,所以用了fastjson依赖,下面给出xml坐标,当然你也可以喜欢着其他的转json工具
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.3</version>
</dependency>
二、准备实操
2.1、编写一个自己定义的Log注解
java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
// 业务类型
BusinessType businessType() default BusinessType.OTHER;
// 模块名称
String title() default "";
}
模块名称、业务类型等可以根据自己的实际情况去添加和删除
之后我们将注解写在需要记录的方法上面,这里是一个简单的分页查询 ,入参 为每页条数和页码,出参就是分页的结果
java
@Log(title = "分页查询商品",businessType = BusinessType.GETAll)
@GetMapping("/goods/list")
public Result pageList(int pageNum,int pageSize,String name , String useage){
return Result.success(goodsService.selectPage(pageNum,pageSize,name,useage));
}
2.2、编写切面类LogAspect.java
2.2.1、定义切面
java
/**
* 定义切面
*/
@Pointcut("@annotation(com.example.masks.annotation.Log)")
public void pt() {
}
2.2.2、代码编写
我们定义一个环绕切点,首先 记录当前时间,作为切点方法执行前的时间戳,使用 pjp.proceed() 执行切点方法,之后 接着计算切点方法执行的时长,并记录日志。这里调用了 handleLog() 方法来处理日志记录,它需要传入 pjp、runTime 和 result 三个参数。
java
/**
* 环绕切点
* @param
* @return result
*/
@Around("pt()")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
long beginTime = System.currentTimeMillis();
// 执行切点方法
Object result = pjp.proceed();
// 执行时长
Long runTime = System.currentTimeMillis() - beginTime;
handleLog(pjp,runTime,result);
return result;
}
在 handleLog() 方法中,首先 获取切点方法的签名和注解信息,在从注解中获取模块和业务类型信息, 之后 依次获取、请求参数 、 HTTP方法 、 IP地址 和 请求URL 等信息:
java
private void handleLog(ProceedingJoinPoint pjp,Long runTime, Object result) {
MethodSignature signature = (MethodSignature)
pjp.getSignature();
Method method = signature.getMethod();
// 获取注解内容
Log logAnnotation = method.getAnnotation(Log.class);
// 获取模块
String title = logAnnotation.title();
// 获取业务类型
BusinessType businessType = logAnnotation.businessType();
Object[] args = pjp.getArgs();
// 入参数
String params = JSON.toJSONString(args);
//出参
String res = JSON.toJSONString(result);
// 请求方法
String httpMenthod = httpServletRequest.getMethod();
// ip
String ip = IPUtils.getIpAddr(httpServletRequest);
// 请求url
String requestURL = httpServletRequest.getRequestURL().toString();
// 封装日志对象
SysLog sysLog = new SysLog(title, businessType, httpMenthod, requestURL, ip, params, res, runTime);
// 这里可以根据自己的需求去处理sysLog,可以存储到数据库等,储存到数据库的操作就不展示了,比较简单,我这里就控制台输出一下这一条信息
System.out.println(sysLog);
展示一下:因为我把日志存储到了数据库、就给大家展示一下数据库的结果
总结一下
总的来说,AOP 日志记录是一种实现代码模块化和复用的好方法,可以提高代码的可维护性和可读性。在实际开发中,我们应该灵活运用 AOP 技术,根据实际需求选择合适的切点表达式和日志记录方式,并注意日志级别和格式的设置,以便更好地记录和分析日志信息。
希望通过本篇文章,让大家对Aop有一个更深入的了解,尤其是AOP去处理日志的功能,是Aop最常见的一个功能,我这里只是进行简单的AOP日志功能的运用,如果大家有什么更好的方法和对我代码改进的地方,请大家积极私信,一起努力
源码展示
sysLog.java 封装的实体
java
public class SysLog {
private Long id;
/**
* 操作模块
*/
private String title;
/**
* 业务类型
*/
private BusinessType businessType;
/**
* 请求类型
*/
private String requestMethod;
/**
* 请求URl
*/
private String operUrl;
/**
* 请求IP
*/
private String operIp;
/**
* 请求参数
*/
private String operParam;
/**
* 出参
*/
private String resultParam;
/**
* 消耗时间-ms
*/
private Long costTime;
public SysLog(String title, BusinessType businessType, String requestMethod, String operUrl, String operIp, String operParam,String resultParam, Long costTime) {
this.title = title;
this.businessType = businessType;
this.requestMethod = requestMethod;
this.operUrl = operUrl;
this.operIp = operIp;
this.operParam = operParam;
this.resultParam = resultParam;
this.costTime = costTime;
}
Log注解
java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
// 业务类型
BusinessType businessType() default BusinessType.OTHER;
// 模块名称
String title() default "";
}
LogAspect.java 切面类
java
@Aspect
@Component
public class LogAspect {
@Autowired
HttpServletRequest httpServletRequest;
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
/**
* 定义切面
*/
@Pointcut("@annotation(com.xiaoke.annotation.Log)")
public void pt() {
}
/**
* 环绕切点
*/
@Around("pt()")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
long beginTime = System.currentTimeMillis();
// 执行切点方法M
Object result = pjp.proceed();
// 执行时长
Long runTime = System.currentTimeMillis() - beginTime;
// 记录日志
handleLog(pjp,runTime,result);
return result;
}
private void handleLog(ProceedingJoinPoint pjp,Long runTime, Object result) {
MethodSignature signature = (MethodSignature)
pjp.getSignature();
Method method = signature.getMethod();
// 获取注解内容
Log logAnnotation = method.getAnnotation(Log.class);
// 获取模块
String title = logAnnotation.title();
// 获取业务类型
BusinessType businessType = logAnnotation.businessType();
Object[] args = pjp.getArgs();
// 入参数
String params = JSON.toJSONString(args);
//出参
String res = JSON.toJSONString(result);
// 请求方法
String httpMenthod = httpServletRequest.getMethod();
// ip
String ip = IPUtils.getIpAddr(httpServletRequest);
// 请求url
String requestURL = httpServletRequest.getRequestURL().toString();
// 封装日志对象
SysLog sysLog = new SysLog(title, businessType, httpMenthod, requestURL, ip, params, res, runTime);
// 这里可以根据自己的需求去处理sysLog,可以存储到数据库等,储存到数据库的操作就不展示了,比较简单,我这里就控制台输出一下这一条信息
System.out.println(sysLog);
}
}
下面是俩个工具类,百度可以搜索到 这里也给出源码
BusinessType.java 这是一个枚举
java
/**
* @Description 业务操作类型
*/
public enum BusinessType {
/**
* 其它
*/
OTHER,
/**
* 新增
*/
INSERT,
/**
* 修改
*/
UPDATE,
/**
* 删除
*/
DELETE,
/**
* 授权
*/
GRANT,
}
IPutils.java 这个主要是获取ip
java
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class IPUtils {
private static final String IP_UTILS_FLAG = ",";
private static final String UNKNOWN = "unknown";
private static final String LOCALHOST_IP = "0:0:0:0:0:0:0:1";
private static final String LOCALHOST_IP1 = "127.0.0.1";
/**
* 获取IP地址
* <p>
* 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = null;
try {
//以下两个获取在k8s中,将真实的客户端IP,放到了x-Original-Forwarded-For。而将WAF的回源地址放到了 x-Forwarded-For了。
ip = request.getHeader("X-Original-Forwarded-For");
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
//获取nginx等代理的ip
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("x-forwarded-for");
}
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
//兼容k8s集群获取ip
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {
//根据网卡取本机配置的IP
InetAddress iNet = null;
try {
iNet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
System.out.println();
System.out.println("getClientIp error"+e.getMessage());
}
assert iNet != null;
ip = iNet.getHostAddress();
}
}
} catch (Exception e) {
System.out.println("IPUtils ERROR"+e.getMessage());
}
//使用代理,则获取第一个IP地址
if (!StringUtils.isEmpty(ip) && ip.indexOf(IP_UTILS_FLAG) > 0) {
ip = ip.substring(0, ip.indexOf(IP_UTILS_FLAG));
}
return ip;
}
}
以上就是全部源码了 有兴趣的朋友可以观看我其他的文章和私信我哦