前言
拦截器和 AOP 是 Spring Boot 中实现横切关注点的重要技术。日志记录、权限校验、性能监控等功能都可以通过拦截器和 AOP 实现。本文将详细介绍拦截器和 AOP 的使用。
拦截器(Interceptor)
1. 创建拦截器
package com.example.demo.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Slf4j
@Component
public class LogInterceptor implements HandlerInterceptor {
/**
* 预处理
* 在 Controller 方法执行前调用
*/
@Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler
) throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
String uri = request.getRequestURI();
String method = request.getMethod();
String ip = getClientIp(request);
log.info("请求开始:{} {} IP:{}", method, uri, ip);
return true; // 继续执行
}
/**
* 后处理
* 在 Controller 方法执行后调用
*/
@Override
public void postHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView
) throws Exception {
Long startTime = (Long) request.getAttribute("startTime");
long cost = System.currentTimeMillis() - startTime;
log.info("请求结束:{} 耗时:{}ms", request.getRequestURI(), cost);
}
/**
* 完成处理
* 在视图渲染后调用
*/
@Override
public void afterCompletion(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex
) throws Exception {
if (ex != null) {
log.error("请求异常:{}", request.getRequestURI(), ex);
}
}
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
2. 注册拦截器
package com.example.demo.config;
import com.example.demo.interceptor.LogInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final LogInterceptor logInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor)
.addPathPatterns("/api/**") // 拦截的路径
.excludePathPatterns( // 排除的路径
"/api/public/**",
"/api/health",
"/api/error"
);
}
}
权限拦截器
1. 创建权限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequirePermission {
/**
* 权限码
*/
String value();
/**
* 逻辑关系
*/
Logical logical() default Logical.AND;
enum Logical {
AND, OR
}
}
2. 权限拦截器实现
@Component
@RequiredArgsConstructor
public class PermissionInterceptor implements HandlerInterceptor {
private final UserService userService;
@Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler
) throws Exception {
// 检查是否有权限注解
if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod) handler;
RequirePermission annotation = method.getMethodAnnotation(RequirePermission.class);
if (annotation != null) {
// 获取当前用户
Long userId = getCurrentUserId(request);
// 检查权限
boolean hasPermission = userService.hasPermission(
userId,
annotation.value()
);
if (!hasPermission) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":403,\"message\":\"无权限访问\"}");
return false;
}
}
}
return true;
}
private Long getCurrentUserId(HttpServletRequest request) {
String token = request.getHeader("Authorization");
// 解析 Token 获取用户 ID
return userService.getUserIdFromToken(token);
}
}
3. 使用权限注解
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
@GetMapping
@RequirePermission("user:list")
public R<List<UserDTO>> getUsers() {
return R.ok(userService.findAll());
}
@PostMapping
@RequirePermission("user:create")
public R<UserDTO> create(@RequestBody UserDTO dto) {
return R.ok(userService.create(dto));
}
@DeleteMapping("/{id}")
@RequirePermission("user:delete")
public R<Void> delete(@PathVariable Long id) {
userService.delete(id);
return R.ok();
}
}
过滤器(Filter)
1. 创建过滤器
@Component
@Order(1)
public class RequestFilter implements Filter {
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain
) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 包装请求
RequestWrapper wrappedRequest = new RequestWrapper(httpRequest);
// 记录请求体
String body = getRequestBody(wrappedRequest);
System.out.println("请求体:" + body);
chain.doFilter(wrappedRequest, httpResponse);
}
private String getRequestBody(HttpServletRequest request) throws IOException {
byte[] buffer = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = request.getInputStream().read(buffer)) != -1) {
sb.append(new String(buffer, 0, len, StandardCharsets.UTF_8));
}
return sb.toString();
}
}
2. 请求包装器
public class RequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = StreamUtils.copyToByteArray(request.getInputStream());
}
@Override
public ServletInputStream getInputStream() {
return new CachedBodyServletInputStream(body);
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
3. 配置过滤器
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<RequestFilter> requestFilter() {
FilterRegistrationBean<RequestFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new RequestFilter());
registration.addUrlPatterns("/api/*");
registration.setOrder(1);
return registration;
}
}
AOP 面向切面
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 创建切面
package com.example.demo.aspect;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class LogAspect {
/**
* 切点定义
*/
@Pointcut("execution(* com.example.demo.controller..*.*(..))")
public void controllerPointcut() {
}
/**
* 环绕通知
*/
@Around("controllerPointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.currentTimeMillis();
String className = pjp.getTarget().getClass().getSimpleName();
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
log.info("方法调用:{}.{} 参数:{}", className, methodName, args);
Object result;
try {
result = pjp.proceed();
long cost = System.currentTimeMillis() - startTime;
log.info("方法完成:{}.{} 耗时:{}ms 返回:{}", className, methodName, cost, result);
return result;
} catch (Throwable e) {
long cost = System.currentTimeMillis() - startTime;
log.error("方法异常:{}.{} 耗时:{}ms 异常:{}", className, methodName, cost, e.getMessage(), e);
throw e;
}
}
}
性能监控切面
1. 创建性能监控注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MonitorPerformance {
/**
* 警告阈值(毫秒)
*/
long warnThreshold() default 1000;
}
2. 性能监控切面
@Aspect
@Component
@RequiredArgsConstructor
public class PerformanceAspect {
private final ApplicationEventPublisher eventPublisher;
@Around("@annotation(monitor)")
public Object around(ProceedingJoinPoint pjp, MonitorPerformance monitor) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = pjp.proceed();
long cost = System.currentTimeMillis() - startTime;
if (cost > monitor.warnThreshold()) {
log.warn("性能警告:{}.{} 耗时:{}ms 超过阈值:{}ms",
pjp.getSignature().getDeclaringTypeName(),
pjp.getSignature().getName(),
cost,
monitor.warnThreshold()
);
// 发布性能事件
eventPublisher.publishEvent(new PerformanceEvent(
this,
pjp.getSignature().toShortString(),
cost
));
}
return result;
}
}
3. 使用
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
@GetMapping
@MonitorPerformance(warnThreshold = 500)
public R<List<UserDTO>> getUsers() {
return R.ok(userService.findAll());
}
}
缓存切面
1. 创建缓存注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cacheable {
/**
* 缓存 key
*/
String key();
/**
* 缓存时间(秒)
*/
long expire() default 300;
}
2. 缓存切面实现
@Aspect
@Component
@RequiredArgsConstructor
public class CacheAspect {
private final RedisTemplate<String, Object> redisTemplate;
@Around("@annotation(cacheable)")
public Object around(ProceedingJoinPoint pjp, Cacheable cacheable) throws Throwable {
// 生成缓存 key
String key = generateKey(cacheable.key(), pjp);
// 从缓存获取
Object cached = redisTemplate.opsForValue().get(key);
if (cached != null) {
return cached;
}
// 执行方法
Object result = pjp.proceed();
// 存入缓存
redisTemplate.opsForValue().set(key, result, cacheable.expire(), TimeUnit.SECONDS);
return result;
}
private String generateKey(String pattern, ProceedingJoinPoint pjp) {
// 解析 SpEL 表达式
return "cache:" + pattern;
}
}
3. 使用
@Service
public class UserService {
@GetMapping("/{id}")
@Cacheable(key = "'user:' + #id", expire = 300)
public UserDTO findById(@PathVariable Long id) {
// 数据库查询
return userMapper.selectById(id);
}
}
最佳实践
1. 拦截器 vs 过滤器 vs AOP
| 特性 | 过滤器 | 拦截器 | AOP |
|---|---|---|---|
| 实现标准 | Servlet 规范 | Spring MVC | AOP 联盟 |
| 执行时机 | 最早 | 次之 | 最晚 |
| 访问范围 | Request/Response | Handler | Bean 方法 |
| 使用场景 | 编码、安全 | 权限、日志 | 缓存、事务 |
2. 执行顺序
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor).order(1);
registry.addInterceptor(authInterceptor).order(2);
registry.addInterceptor(permissionInterceptor).order(3);
}
}
3. 避免重复代码
// ✅ 推荐 - 使用 AOP
@Aspect
public class LogAspect {
@Around("@annotation(monitor)")
public Object around(ProceedingJoinPoint pjp, MonitorPerformance monitor) {
// 统一日志处理
}
}
// ❌ 不推荐 - 每个方法都写
@GetMapping
public R<List<UserDTO>> getUsers() {
long start = System.currentTimeMillis();
try {
return R.ok(userService.findAll());
} finally {
log.info("耗时:{}ms", System.currentTimeMillis() - start);
}
}
总结
拦截器和 AOP 要点:
- ✅ 拦截器 - HandlerInterceptor,预处理/后处理
- ✅ 过滤器 - Filter,请求过滤
- ✅ AOP - 面向切面,环绕通知
- ✅ 权限校验 - 自定义注解 + 拦截器
- ✅ 性能监控 - AOP 实现
- ✅ 缓存切面 - 统一缓存处理
合理使用拦截器和 AOP,能让代码更加简洁、可维护。