SpringBoot + AOP 统一处理日志

本文介绍如何在SpringBoot项目中使用AOP统一处理日志,通过AspectJ注解方式实现前置、后置、环绕及异常通知,记录请求信息、方法执行时间和异常情况。

Springboot + AOP 统一处理日志。然后系统日志持久化到文件保存起来,当程序方便发生问题的时候,能够快速、准确的定位到问题的所在。SpringBoot + Log4j 每天输出一个日志文件(分级别的)


一、AOP框架AspectJ

AspectJ是基于java开发的aop框架,自spring2.0以后,springaop引入对他支持。

使用aspectJ实现aop由两种方式:基于xml和基于注解

相对来说注解方式更加简单,开发中常用注解方式。

基于注解的声明式aspectJ

注解名称

描述

@Aspect

定义一个切面

@Pointcut

定义切面表达式

@Before

定义前置通知,相当于BeforeAdvice

@AfterReturning

定义后置通知,相当于AfterReturningAdvice

@Around

定义环绕通知,相当于MethodIntercetor

@AfterThrowing

定义异常通知来处理程序中未处理的异常,相当于ThrowAdvice

@After

定义最终final通知

二、Springboot + AOP 统一处理日志

pom.xml

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

 切面类

package com.example.springBootdemo.aop;

import java.util.Objects;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
 * 切面类,日志
 * @author luolei
 * @date 2019年2月11日
 */
@Aspect //声明为切面类
@Component
@Order(1) //标记切点的优先级,i越小,优先级越高
public class MyAspectLog {
	
	//定义切点表达式:*,第一个返回值,第二个类名,第三个方法名
	 @Pointcut("execution(public * com.example.springBootdemo.service.*.*(..))")
	//使用一个返回值为void,方法体为空的方法来命名切入点
	public void myPointCut(){};
	
	private final Logger logger = LoggerFactory.getLogger(getClass());
	
	//前置通知
	@Before("myPointCut()")
	public void myBefore(JoinPoint joinPoint) {
		logger.info("-----------@Before 前置通知-----------");
		logger.info("前置通知:模拟执行权限检查...");
		logger.info("目标类是={}", joinPoint.getTarget());
		logger.info("被植入增强目标为={}", joinPoint.getSignature());
		
		ServletRequestAttributes attributes= (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
		HttpServletRequest request =attributes.getRequest();
		//url
		logger.info("请求路径URL={}",request.getRequestURL());
		//method
		logger.info("请求方式Method={}",request.getMethod());
		//ip
		logger.info("请求IP={}",request.getRemoteAddr());
		//类方法
		logger.info("请求类方法ClassMethod={}",joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName());
		//参数
		logger.info("请求参数Args={}",joinPoint.getArgs());
	}
	
	//后置通知
/*	@AfterReturning("myPointCut()")
	public void myAfterReturning(JoinPoint joinPoint) {
		logger.info("-----------@AfterReturning 后置通知-----------");
		logger.info("后置通知:模拟记录日志...");
		logger.info("返回值 response={}", joinPoint.toString());
	}*/
	
	//后置通知
	@AfterReturning(returning = "objects", pointcut = "myPointCut()")
	public void myAfterReturning(Object objects) {
		logger.info("-----------@AfterReturning 后置通知-----------");
		logger.info("后置通知:模拟记录日志...");
		logger.info("返回值 response={}", objects);
	}
	/**
	 * 环绕通知
	 * @param proceedingJoinPoint 是JoinPoint的子接口,表示可以执行目标方法
	 * @return Object
	 * 必须接收一个参数,类型为ProceedingJoinPoint
	 * @必须 throws Throwable
	 */
	@Around("myPointCut()")
	public Object myAround(ProceedingJoinPoint proceedingJoinPoint) 
			throws Throwable{
		//开始
		logger.info("-----------@Around 环绕通知start-----------");
		logger.info("环绕开始:开启之前,模拟开启事务...");
		long time = System.currentTimeMillis();
		//执行当前目标方法
		Object obj=proceedingJoinPoint.proceed();
		//结束
		logger.info("环绕结束:模拟关闭事务...");
		time = System.currentTimeMillis() - time;
		logger.info("方法用时Time={}", time +"(毫秒)");
		logger.info("-----------@Around 环绕通知end-----------");
		return obj;
	}
	
	//异常通知
	@AfterThrowing(value="myPointCut()", throwing="e")
	public void myAfterThrowing(JoinPoint joinPoint,Throwable e) {
		logger.info("-----------@AfterThrowing 异常通知-----------");
		logger.info("异常通知:出错了"+ e);
		logger.info("异常通知:出错了getStackTrace={}", e.getStackTrace());
		logger.info("异常通知:出错了Throwable...具体如下", e);
	}
	
	//最终通知
	@After("myPointCut()")
	public void myAfter() {
		logger.info("-----------@After 最终通知-----------");
		logger.info("最终通知:模拟方法结束后的释放资源...");
	}
	
	
}

 

<think>好的,我现在需要帮用户解决如何在Spring Boot中使用AOP和DFA算法实现敏感词过滤与审核的问题。首先,我得回顾一下DFA算法的基本概念,因为用户提到了它。DFA,即确定有限状态自动机,常用于高效敏感词过滤。它的优势在于构建状态转移表,能快速检测文本中的敏感词。 接下来,用户还提到了AOP(面向切面编程)。AOP可以用来在不修改业务代码的情况下,统一处理敏感词过滤的逻辑。比如,在Controller层的方法执行前,通过切面拦截请求参数,进行敏感词检测或替换。这样设计符合Spring Boot的模块化思想,也便于维护。 我需要整合这两个技术点。首先,得实现DFA算法的敏感词过滤器。这包括读取敏感词库,构建Trie树结构,每个节点代表一个字符,叶子节点标记为敏感词结束。然后,在检测文本时,逐字符遍历,查看是否存在匹配的路径。 然后是AOP部分的实现。需要定义一个切面,针对特定的Controller方法,比如处理用户输入的POST请求,使用@Around通知来获取请求参数,调用DFA过滤器进行处理。如果发现敏感词,可以记录日志、返回错误信息或替换为星号。 另外,用户可能还关心审核功能的实现。审核通常涉及将检测到的敏感内容记录到数据库或日志,供后续人工审核。这部分可能需要异步处理,避免影响主流程的性能。 需要考虑的细节包括:敏感词库的加载方式(从数据库、文件或缓存),DFA树的初始化时机(应用启动时加载),以及如何高效处理大量敏感词。例如,引用中提到Spring Boot结合Redis处理热搜和敏感词过滤,可能可以考虑将敏感词库存储在Redis中,提高读取速度。 还有,敏感词替换的实现。比如,检测到“敏感文字”替换为“**”,需要遍历文本,找到所有敏感词并替换。DFA算法在检测时能高效定位敏感词的位置和长度,方便替换操作。 可能遇到的问题包括中文字符的处理,比如多字节字符的拆分,需要确保DFA构建时正确处理UTF-8字符。另外,AOP切入点的选择,确保覆盖所有需要过滤的接口,同时避免性能损耗。 最后,测试环节也很重要。需要编写单元测试,验证敏感词检测和替换的正确性,以及AOP切面是否按预期拦截请求。性能测试也是必要的,确保在高并发下过滤逻辑不会成为瓶颈。 总结下来,步骤大致分为:1. 实现DFA敏感词过滤器;2. 配置AOP切面拦截请求;3. 处理敏感词(拦截或替换);4. 记录审核日志;5. 集成测试和优化。同时,参考已有的实现,如引用中的SensitiveFilter类,可以借鉴其设计模式,比如单例模式确保过滤器实例唯一,提高效率。</think>### Spring Boot通过AOP与DFA算法实现敏感词过滤与审核方案 #### 一、DFA敏感词过滤实现 1. **DFA算法核心** DFA(确定有限状态自动机)通过构建敏感词树实现高效匹配,每个节点表示一个字符,叶子节点标记敏感词结束。例如,敏感词“测试”的树结构为: ```text root → 测 → 试(END) ``` 检测时逐字符遍历树结构,若匹配到END节点则命中敏感词[^1]。 2. **敏感词过滤器实现** ```java public class SensitiveFilter { private static final TrieNode root = new TrieNode(); // 初始化敏感词树(应用启动时加载) static { List<String> words = loadSensitiveWords(); // 从数据库/文件加载 for (String word : words) { TrieNode node = root; for (char c : word.toCharArray()) { node = node.getChildren().computeIfAbsent(c, k -> new TrieNode()); } node.setEnd(true); } } // 检测并替换敏感词 public String filter(String text) { StringBuilder result = new StringBuilder(); int start = 0; TrieNode current = root; for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); current = current.getChildren().get(c); if (current == null) { i = start; result.append(text.charAt(start)); start++; current = root; } else if (current.isEnd()) { result.append("*".repeat(i - start + 1)); start = i + 1; current = root; } } return result.toString(); } } ``` --- #### 二、AOP切面集成 1. **定义切面拦截请求** 针对Controller层方法,拦截包含用户输入的参数(如@RequestBody): ```java @Aspect @Component public class SensitiveAspect { @Autowired private SensitiveFilter filter; // 拦截所有POST请求且含@RequestBody注解的方法 @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping) && args(..,@RequestBody *)") public void requestPointcut() {} @Around("requestPointcut()") public Object filterInput(ProceedingJoinPoint joinPoint) throws Throwable { Object[] args = joinPoint.getArgs(); for (int i = 0; i < args.length; i++) { if (args[i] instanceof String text) { args[i] = filter.filter(text); // 替换敏感词 } } return joinPoint.proceed(args); } } ``` 2. **审核日志记录** 检测到敏感词时异步记录日志(如使用@Async): ```java @Async public void logSensitiveAction(String text, String userId) { auditLogRepository.save(new AuditLog(userId, text, LocalDateTime.now())); } ``` --- #### 三、性能优化与扩展 1. **敏感词库存储** - 将敏感词存储在Redis中,利用缓存加速加载[^2]。 - 使用`@PostConstruct`在服务启动时加载词库。 2. **多模式处理** ```java public enum FilterMode { REPLACE, // 替换为* BLOCK // 直接拦截请求 } ``` 根据业务需求通过配置切换模式。 --- #### 四、测试验证 1. **单元测试** ```java @Test public void testFilter() { SensitiveFilter filter = new SensitiveFilter(); assertEquals("****", filter.filter("测试文字")); } ``` 2. **性能压测** 使用JMeter模拟高并发请求,确保过滤延迟<10ms。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值