开发者

SpringBoot使用AOP切面对请求进行日志记录方式

开发者 https://www.devze.com 2025-05-21 10:28 出处:网络 作者: 小马不敲代码
目录简介核心概念AOP 的实现方式AOP 的应用场景AOP 的优点AOP 的缺点AOP 的实现框架代码实战总结简介
目录
  • 简介
  • 核心概念
  • AOP 的实现方式
  • AOP 的应用场景
  • AOP 的优点
  • AOP 的缺点
  • AOP 的实现框架
  • 代码实战
  • 总结

简介

AOP(ASPect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过分离横切关注点(cross-cutting concerns)来提高代码的模块化。

横切关注点是指那些跨越多个模块的功能,例如日志记录、事务管理、安全性检查等。

AOP 通过将这些功能从核心业务逻辑中分离出来,使代码更易于维护和扩展。

核心概念

1、切面(Aspect):

  • 切面是横切关注点的模块化实现。它包含通知(Advice)和切点(Pointcut)。
  • 例如,日志记录功能可以作为一个切面。

2、连接点(Join Point):

  • 连接点是程序执行过程中的特定点,例如方法调用、异常抛出等。
  • AOP 可以在这些点上插入额外的行为。

3、通知(Advice):

  • 通知是切面在特定连接点执行的动作。常见的通知类型包括:
  • 前置通知(Before Advice):在目标方法执行之前执行。
  • 后置通知(After Advice):在目标方法执行之后执行。
  • 返回通知(After Returning Advice):在目标方法成功返回后执行。
  • 异常通知(After Throwing Advice):在目标方法抛出异常后执行。
  • 环绕通知(Around Advice):在目标方法执行前后都执行,可以控制是否执行目标方法。

4、切点(Pointcut):

  • 切点用于定义哪些连接点会触发通知。它通过表达式或规则匹配连接点。
  • 例如,匹配所有以 save 开头的方法。

5、引入(Introduction):

  • 引入允许向现有类添加新的方法或属性,而无需修改类的源代码。

6、目标对象(Target Object):

目标对象是被一个或多个切面通知的对象,通常是核心业务逻辑的类。

7、代理(Proxy):

  • AOP 框架通过创建代理对象来实现切面功能。
  • 代理对象在目标对象的方法调用前后插入通知。

AOP 的实现方式

1、静态代理:

在编译时生成代理类,例如 AspectJ 的编译时织入。

2、动态代理:

在运行时生成代理类,例如 Spring AOP 使用的 JDK 动态代理或 CGLIB。

AOP 的应用场景

1、日志记录:

在方法调用前后记录日志,而不需要在每个方法中手动添加日志代码。

2、事务管理:

在方法执行前后管理事务的开启、提交或回滚。

3、安全性检查:

在方法调用前检查用户权限。

4、性能监控:

在方法执行前后记录时间,用于性能分析。

5、异常处理:

在方法抛出异常时执行特定的处理逻辑。

AOP 的优点

1、模块化:

将横切关注点从核心业务逻辑中分离出来,使代码更清晰、更易于维护。

2、代码复用:

通过切面实现通用功能,避免重复代码。

3、灵活性:

可以动态地添加或移除切面,而不需要修改核心业务代码。

AOP 的缺点

1、学习曲线:

对于初学者来说,AOP 的概念和实现方式可能较难理解。

2、调试困难:

由于切面逻辑是动态插入的,调试时可能难以追踪问题。

3、性能开销:

动态代理和运行时织入可能带来一定的性能开销。

AOP 的实现框架

1、Spring AOP:

Spring 框架提供的 AOP 实现,基于动态代理,易于与 Spring 集成。

2、AspectJ:

功能强大的 AOP 框架,支持编译时织入和运行时织入。

3、JBoss AOP:

JBoss 提供的 AOP 实现,支持复杂的切面逻辑。

代码实战

  • maven依赖
<?XML version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<artifactId>spring-boot-demo-log-aop</artifactId>
	<version>1.0.0-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>spring-boot-demo-log-aop</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>com.xiaoma</groupId>
		<artifactId>spring-boot-demo</artifactId>
		<version>1.0.0-SNAPSHOT</version>
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<Java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<编程;scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
		</dependency>

		<!-- 解析 UserAgent 信息 -->
		<dependency>
			<groupId>eu.bitwalker</groupId>
			<artifactId>UserAgentUtils</artifactId>
		</dependency>
	</dependencies>

	<build>
		<finalName>spring-boot-demo-log-aop</finalName>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>
  • AopLog.java
@Aspect
@Component
@Slf4j
public class AopLog {
	private static final String START_TIME = "request-start";

	/**
	 * 切入点
	 */
	@Pointcut("execution(public * com.xiaoma.log.aop.controller.*Controller.*(..))")
	public void log() {

	}

	/**
	 * 前置操作
	 *
	 * @param point 切入点
	 */
	@Before("log()")编程
	public void beforeLog(JoinPoint point) {
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

		HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();

		log.info("【请求 URL】:{}", request.getRequestURL());
		log.info("【请求 IP】:{}", request.getRemoteAddr());
		log.info("【请求类名】:{},【请求方法名】:{}", point.getSignature().getDeclaringTypeNampythone(), point.getSignature().getName());

		Map<String, String[]> parameterMap = request.getParameterMap();
		log.info("【请求参数】:{},", jsONUtil.toJsonStr(parameterMap));
		Long start = System.currentTimeMillis();
		request.setAttribute(START_TIME, start);
	}

	/**
	 * 环绕操作
	 *
	 * @param point 切入点
	 * @return 原方法返回值
	 * @throws Throwable 异常信息
	 */
	@Around("log()")
	public Object aroundLog(ProceedingJoinPoint point) throws Throwable {
		Object result = point.proceed();
		log.info("【返回值】:{}", JSONUtiandroidl.toJsonStr(result));
		return result;
	}

	/**
	 * 后置操作
	 */
	@AfterReturning("log()")
	public void afterReturning() {
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();

		Long start = (Long) request.getAttribute(START_TIME);
		Long end = System.currentTimeMillis();
		log.info("【请求耗时】:{}毫秒", end - start);

		String header = request.getHeader("User-Agent");
		UserAgent userAgent = UserAgent.parseUserAgentString(header);
		log.info("【浏览器类型】:{},【操作系统】:{},【原始User-Agent】:{}", userAgent.getBrowser().toString(), userAgent.getOperatingSystem().toString(), header);
	}
}
  • TestController.java
@RestController
public class TestController {

	/**
	 * 测试方法
	 *
	 * @param who 测试参数
	 * @return {@link Dict}
	 */
	@GetMapping("/http://www.devze.comtest")
	public Dict test(String who) {
		return Dict.create().set("who", StrUtil.isBlank(who) ? "me" : who);
	}

}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号