Skip to content
清晨的一缕阳光
返回

Spring Boot 拦截器与 AOP

前言

拦截器和 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 MVCAOP 联盟
执行时机最早次之最晚
访问范围Request/ResponseHandlerBean 方法
使用场景编码、安全权限、日志缓存、事务

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 要点:

合理使用拦截器和 AOP,能让代码更加简洁、可维护。


分享这篇文章到:

上一篇文章
Go Defer 实现原理
下一篇文章
Spring Boot 集成测试实战