Skip to content

SpringBoot

脚手架

官方:https://start.spring.io/
阿里巴巴: https://start.aliyun.com/

项目结构

SpringBoot 项目结构通常分为单一模块多模块两种形式,根据项目规模和团队协作需求选择。

单一模块

适用于小型项目或个人项目,所有代码集中在一个模块中,结构简单、易于维护。

text
demo/
├── pom.xml                                # 项目依赖及构建配置
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── demo
    │   │               ├── controller/    # 控制器层:接收请求、返回响应
    │   │               ├── service/       # 业务逻辑层:核心业务处理
    │   │               ├── mapper/        # 数据访问层:数据库操作
    │   │               ├── entity/        # 实体类:数据库表映射
    │   │               ├── config/        # 配置类:Bean 定义、拦截器等
    │   │               └── DemoApplication.java  # 启动类
    │   └── resources
    │       ├── application.yml            # 应用配置
    │       ├── mapper/                    # MyBatis 映射文件(XML)
    │       └── log4j2.xml                 # 日志配置
    └── test
        └── java
            └── com
                └── example
                    └── demo
                        └── DemoApplicationTests.java

多模块

适用于中大型项目或团队协作场景,按职责将项目拆分为多个子模块,降低耦合度,便于并行开发和复用。

目录结构

text
my-project/
├── pom.xml                          # 父 POM (packaging: pom)
├── my-project-common/               # 通用工具模块
│   ├── pom.xml
│   └── src/main/java/com/example/common
├── my-project-domain/               # 实体/领域模块
│   ├── pom.xml
│   └── src/main/java/com/example/domain
├── my-project-service/              # 业务逻辑模块
│   ├── pom.xml
│   └── src/main/java/com/example/service
└── my-project-web/                  # Web 启动模块
    ├── pom.xml
    └── src
        ├── main
        │   ├── java/com/example/web
        │   │   ├── controller/
        │   │   └── WebApplication.java
        │   └── resources
        │       ├── application.yml
        │       └── log4j2.xml
        └── test
            └── java/com/example/web

模块职责

模块职责依赖
common通用工具类、常量、枚举、统一异常、统一返回结果等
domain实体类(Entity)、DTO、VO、数据库映射对象common
service业务逻辑实现、接口定义、外部服务调用commondomain
web控制器(Controller)、配置类、启动类、静态资源service

依赖关系

text
web ──▶ service ──▶ domain ──▶ common

依赖方向自上而下,上层模块依赖下层模块,禁止反向依赖和循环依赖

父 POM 关键配置

父 POM 负责聚合子模块并统一管理依赖版本,packaging 必须为 pom

xml
<groupId>com.example</groupId>
<artifactId>my-project</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>

<!-- 聚合子模块 -->
<modules>
    <module>my-project-common</module>
    <module>my-project-domain</module>
    <module>my-project-service</module>
    <module>my-project-web</module>
</modules>

<!-- 统一版本管理,子模块引用时无需指定版本号 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- 内部模块版本统一管理 -->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>my-project-common</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

子模块 POM 示例

web 模块为例,继承父 POM 并声明对 service 的依赖(版本由父 POM 管理,无需指定):

xml
<parent>
    <groupId>com.example</groupId>
    <artifactId>my-project</artifactId>
    <version>1.0.0</version>
</parent>

<artifactId>my-project-web</artifactId>

<dependencies>
    <!-- 依赖业务模块 -->
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>my-project-service</artifactId>
    </dependency>
    <!-- Web Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

<build>
    <plugins>
        <!-- 打包为可执行 jar -->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

一、核心注解

@SpringBootApplication

Spring Boot 的核心启动注解,是一个组合注解,等同于以下三个注解的叠加:

子注解作用
@SpringBootConfiguration标记当前类为配置类(等同于 @Configuration
@EnableAutoConfiguration开启自动配置,根据 classpath 下的依赖自动配置 Bean
@ComponentScan开启组件扫描,默认扫描当前包及子包
java
    @SpringBootApplication
    public class MyApp {
        public static void main(String[] args) {
            SpringApplication.run(MyApp.class, args);
        }
    }

如需排除某些自动配置:

java
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

二、Bean 相关注解(IoC 容器)

  1. 组件注册
注解用途层次
@Component通用组件,最基础的注解通用层
@Service标记业务逻辑层Service 层
@Repository标记数据访问层,可自动转换数据库异常DAO 层
@Controller标记 Web 控制层Controller 层
@RestController@Controller + @ResponseBody,返回 JSON/XMLController 层
@Configuration标记配置类,类中的 @Bean 方法会被容器管理配置层
  1. 依赖注入
注解说明
@Autowired按类型自动注入,可配合 @Qualifier 按名称注入
@Qualifier("beanName")指定注入某个名称的 Bean
@Primary多个实现时,标记为首选注入的 Bean
@ResourceJSR-250 注解,默认按名称注入(Java 标准)
@InjectJSR-330 注解,按类型注入(需额外依赖)
@Value("${key}")注入配置文件中的值
@Lazy延迟加载,在首次使用时才创建 Bean

三、Web 层注解

  1. 请求映射
注解说明
@RequestMapping通用请求映射(支持所有 HTTP 方法)
@GetMapping等价于 @RequestMapping(method = GET)
@PostMapping等价于 @RequestMapping(method = POST)
@PutMapping等价于 @RequestMapping(method = PUT)
@DeleteMapping等价于 @RequestMapping(method = DELETE)
@PatchMapping等价于 @RequestMapping(method = PATCH)
  1. 参数绑定
注解说明示例
@PathVariable获取路径变量/users/{id}@PathVariable("id") Long id
@RequestParam获取查询参数/表单参数?name=Tom@RequestParam("name") String name
@RequestBody获取请求体(JSON → 对象)@RequestBody UserDTO user
@RequestHeader获取请求头@RequestHeader("Authorization") String token
@CookieValue获取 Cookie 值@CookieValue("sessionId") String sid
@ModelAttribute绑定模型属性到方法参数@ModelAttribute("user") User user
@RequestPart用于 multipart 请求文件上传场景
java
@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) { ... }

    @PostMapping
    public User createUser(@RequestBody @Valid UserDTO userDTO) { ... }

    @GetMapping("/search")
    public List<User> search(@RequestParam String keyword,
                             @RequestParam(defaultValue = "1") int page) { ... }
}
  1. 响应处理
注解说明
@ResponseBody将返回值直接写入 HTTP 响应体(JSON/XML)
@ResponseStatus设置 HTTP 响应状态码

四、配置相关

注解说明
@Configuration标记为配置类
@Bean在配置类中声明 Bean
@PropertySource加载指定的 properties 文件
@PropertySources加载多个 properties 文件
@ConfigurationProperties将配置文件中的属性绑定到对象
@EnableConfigurationProperties启用 @ConfigurationProperties 的支持
@Profile指定 Bean 在哪个环境下生效
@ConditionalOnProperty根据配置属性条件化创建 Bean
@Import导入其他配置类
@ImportResource导入 XML 配置文件

五、条件化注解

注解触发条件
@ConditionalOnBean容器中存在某个 Bean 时
@ConditionalOnMissingBean容器中不存在某个 Bean 时
@ConditionalOnClassclasspath 下存在某个类时
@ConditionalOnMissingClassclasspath 下不存在某个类时
@ConditionalOnProperty某个配置属性满足条件时
@ConditionalOnWebApplication当前是 Web 应用时
@ConditionalOnNotWebApplication当前不是 Web 应用时
@ConditionalOnExpressionSpEL 表达式为 true 时
java
@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(prefix = "spring.datasource", name = "url")
public class DataSourceConfig { ... }

六、AOP相关注解

注解说明
@Aspect声明切面类
@Pointcut定义切点表达式
@Before前置通知
@After后置通知(无论是否异常都执行)
@AfterReturning返回后通知
@AfterThrowing异常后通知
@Around环绕通知(最强大)
@EnableAspectJAutoProxy开启 AOP 支持(Spring Boot 默认已开启)

七、事务管理

注解说明
@Transactional声明式事务管理
@EnableTransactionManagement开启事务管理(Spring Boot 默认已开启)
java
@Service
public class OrderService {

    @Transactional(
        propagation = Propagation.REQUIRED,
        isolation = Isolation.READ_COMMITTED,
        timeout = 30,
        readOnly = false,
        rollbackFor = Exception.class
    )
    public void createOrder(Order order) { ... }
}

常用属性:

  • propagation:传播行为(REQUIRED / REQUIRES_NEW / NESTED 等)
  • isolation:隔离级别
  • rollbackFor:指定回滚的异常类型
  • noRollbackFor:指定不回滚的异常类型
  • readOnly:只读事务(优化性能)
  • timeout:超时时间(秒)

八、数据访问层

  1. JPA / Hibernate
注解说明
@Entity标记实体类
@Table指定表名
@Id主键
@GeneratedValue主键生成策略
@Column列映射
@OneToMany / @ManyToOne关联关系
  1. MyBatis
注解说明
@Mapper标记 Mapper 接口
@MapperScan批量扫描 Mapper 包路径
@Select / @Insert / @Update / @DeleteSQL 语句
@Param参数命名
@Results / @Result结果映射
java
@Mapper
public interface UserMapper {
    @Select("SELECT * FROM user WHERE id = #{id}")
    User findById(@Param("id") Long id);
}

九、缓存注解

注解说明
@EnableCaching开启缓存支持
@Cacheable查询缓存,有缓存则直接返回
@CachePut更新缓存
@CacheEvict清除缓存
@Caching组合多个缓存操作
@CacheConfig类级别的缓存配置
java
@Service
@CacheConfig(cacheNames = "users")
public class UserService {

    @Cacheable(key = "#id")
    public User findById(Long id) { ... }

    @CachePut(key = "#user.id")
    public User update(User user) { ... }

    @CacheEvict(key = "#id")
    public void delete(Long id) { ... }
}

十、异步与定时任务

  1. 异步方法
注解说明
@EnableAsync开启异步支持
@Async标记方法为异步执行
java
@Service
public class NotifyService {
    @Async("taskExecutor")
    public CompletableFuture<String> sendEmail(String to) {
        // 异步发送邮件
        return CompletableFuture.completedFuture("OK");
    }
}
  1. 定时任务
注解说明
@EnableScheduling开启定时任务支持
@Scheduled标记方法为定时任务
java
@Component
public class ScheduledTasks {
    @Scheduled(cron = "0 0 2 * * ?")  // 每天凌晨2点
    public void cleanExpiredData() { ... }

    @Scheduled(fixedRate = 5000)       // 每5秒执行
    public void heartbeat() { ... }
}

十一、校验注解(Validation)

Spring Boot
集成校验详解
集成校验:Bean Validation(Hibernate Validator)常用注解与全局异常处理详解。
#@Valid#@Validated#Hibernate Validator

十二、安全相关注解

注解说明
@EnableWebSecurity开启 Web 安全
@EnableMethodSecurity开启方法级安全
@Secured角色控制(Spring 原生)
@PreAuthorize方法执行前鉴权(SpEL 表达式)
@PostAuthorize方法执行后鉴权
@PreFilter方法执行前过滤参数
@PostFilter方法执行后过滤返回值
java
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) { ... }

@PreAuthorize("#id == authentication.principal.id")
public User getMyInfo(Long id) { ... }

十三、测试注解

注解说明
@SpringBootTest启动完整 Spring 上下文进行集成测试
@WebMvcTest仅测试 Controller 层(MockMvc)
@DataJpaTest仅测试 JPA 数据访问层
@MybatisTest仅测试 MyBatis
@MockBean创建 Mock 对象并注入容器
@SpyBean创建 Spy 对象(部分 Mock)
@AutoConfigureMockMvc自动配置 MockMvc
@TestPropertySource指定测试用的配置文件
java
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void testGetUser() throws Exception {
        when(userService.findById(1L)).thenReturn(new User("Tom"));
        mockMvc.perform(get("/api/users/1"))
               .andExpect(status().isOk());
    }
}

十四、全局异常注解

注解说明
@ControllerAdvice全局异常处理类(增强型 Controller)
@RestControllerAdvice@ControllerAdvice + @ResponseBody
@ExceptionHandler处理特定异常
java
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result handleValidation(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
        return Result.fail(400, msg);
    }

    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        return Result.fail(500, "服务器内部错误");
    }
}
Spring Boot
集成校验详解
集成校验:Bean Validation(Hibernate Validator)常用注解与全局异常处理详解。
#@Valid#@Validated#Hibernate Validator

十五、Swagger/OpenAPI 注解

Spring Boot
集成Swagger
集成Swagger:常用注解与集成
#Swagger#OpenAPI

十六、过滤器 Filter

基础概念与作用

过滤器(Filter)是传统 Java Web 开发三大组件之一,其余两个为 ServletListener。Servlet 与 Listener 在现代企业项目中基本已弃用,仅 Filter 仍被广泛使用。

核心作用:拦截对资源的所有请求,在访问目标资源前执行通用操作,如登录校验统一编码处理敏感字符过滤等。

若无 Filter,需在每个功能接口前重复编写登录校验逻辑(if 判断),导致代码冗余、维护困难;Filter 可将该通用逻辑集中统一处理。

生命周期方法详解

方法调用时机执行次数用途
init()Web 服务器启动、Filter 实例化完毕后仅一次资源准备与环境初始化
doFilter()每次请求被拦截时多次请求处理逻辑及放行操作
destroy()Web 服务器关闭时仅一次资源释放与环境清理

init()destroy() 各执行一次;doFilter() 执行频次等于被拦截请求次数。

开发步骤

第一步:定义 Filter 类

创建一个 Java 类,并实现标准 javax.servlet.Filter 接口(注意导入包,非 Spring 或第三方 Filter)。

必须重写三个核心方法:

  • init(FilterConfig filterConfig)
  • doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  • destroy()

init()destroy() 在 Filter 接口中已提供空实现,实际开发中可选择性重写;doFilter() 无默认实现,必须重写

第二步:配置 Filter

配置方式一:@WebFilter 注解:在 Filter 类上添加 @WebFilter 注解,并通过 urlPatterns 属性指定拦截路径。

  • urlPatterns:声明该 Filter 拦截的请求 URL 模式;入门示例中配置为 "/*",表示拦截所有请求。

配置方式二:@ServletComponentScan 注解:在 Spring Boot 启动类(引导类)上添加 @ServletComponentScan 注解,开启对 Servlet 规范组件(包括 Filter)的支持。

Spring Boot 默认不扫描 Servlet 组件,必须显式启用 @ServletComponentScan 才能使 @WebFilter 生效。

放行操作(关键!)

doFilter() 方法内必须调用 chain.doFilter(request, response) 实现放行;否则请求被拦截后无法访问后端资源,导致无响应数据返回。chain.doFilter() 需传入当前 ServletRequestServletResponse 对象,即方法形参 requestresponse

request / response 可获取的信息

doFilter() 中的 requestresponse 形参类型为 ServletRequestServletResponse,实际是 Tomcat 封装的 HttpServletRequestHttpServletResponse 对象,可向下转型后使用。

ServletRequest 基接口中只有 getParameter()getInputStream() 等基础方法,没有 getHeader()getCookies()getSession()getMethod() 等 HTTP 相关方法;这些方法定义在 HttpServletRequest 中,必须先向下转型才能调用。

向下转型:父类引用指向子类对象,强转回子类类型即可:

java
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;

向下转型后可调用子类特有方法:

java
String method = httpRequest.getMethod();              // GET / POST ...
String token = httpRequest.getHeader("Authorization"); // 请求头
String ip = httpRequest.getRemoteAddr();               // 客户端 IP
httpResponse.setStatus(401);                           // 响应状态码

实际运行时 request 就是 HttpServletRequest 的实现类(Tomcat 的 RequestFacade),所以强转不会报错,也无需判断 instanceof

request 常用信息(HttpServletRequest)

类别方法说明
请求方式getMethod()GET / POST / PUT / DELETE 等
请求路径getRequestURI()/api/user/login(不含域名)
请求参数getParameter(name)获取单个参数(GET / POST 均可)
请求参数getParameterMap()获取全部参数(Map 形式)
请求头getHeader(name)User-AgentReferer
请求头getHeaderNames()获取所有请求头名称
请求头getContentType()请求体类型,如 application/json
请求体getInputStream()读取请求体字节流(JSON 等)
协议信息getProtocol()HTTP/1.1 等协议版本
客户端信息getRemoteAddr()客户端 IP 地址
会话信息getSession()获取 HttpSession 会话对象
CookiegetCookies()获取所有 Cookie
字符编码getCharacterEncoding()获取请求字符编码
java
HttpServletRequest httpRequest = (HttpServletRequest) request;
String method = httpRequest.getMethod();                 // GET
String uri = httpRequest.getRequestURI();                // /api/user/login
String name = httpRequest.getParameter("username");      // 请求参数
String token = httpRequest.getHeader("Authorization");   // 请求头(常用于登录校验)
String ip = httpRequest.getRemoteAddr();                 // 客户端 IP

response 常用信息(HttpServletResponse)

类别方法说明
响应状态setStatus(int)设置状态码,如 401、403、500
响应头setHeader(name, value)设置响应头,如 Content-Type
响应编码setCharacterEncoding()设置响应字符编码
响应体getWriter()获取字符输出流(写 JSON 字符串)
响应体getOutputStream()获取字节输出流(写文件、图片等)
重定向sendRedirect(url)重定向到指定地址(302)
会话信息addCookie(cookie)向客户端添加 Cookie
java
HttpServletResponse httpResponse = (HttpServletResponse) response;

// 未登录时直接响应,不继续放行
httpResponse.setStatus(401);
httpResponse.setCharacterEncoding("UTF-8");
httpResponse.setContentType("application/json;charset=UTF-8");
httpResponse.getWriter().write("{\"code\":401,\"msg\":\"未登录\"}");
return;

过滤器执行流程

过滤器拦截前端发起的所有请求,在 doFilter 方法中执行通用操作,其中关键步骤是调用 FilterChain.doFilter() 方法实现放行

text
拦截请求 → 执行放行前逻辑 → 调用 chain.doFilter() 放行至资源
         → 资源处理完毕 → 返回过滤器 → 执行放行后逻辑 → 响应前端数据

放行前逻辑

在调用 FilterChain.doFilter() 之前执行的代码属于放行前逻辑,常用于登录校验、统一编码处理等。

放行后逻辑

FilterChain.doFilter() 调用之后执行的代码属于放行后逻辑;资源访问完毕后请求会返回过滤器,继续执行该部分逻辑,常用于响应日志记录等。

资源访问完毕后会再次回到过滤器,但返回后仅执行放行后的逻辑(非从头重新执行整个 doFilter)。

java
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    log.info("放行前逻辑:请求 {}", ((HttpServletRequest) request).getRequestURI());

    chain.doFilter(request, response);  // 放行:交给资源处理

    log.info("放行后逻辑:资源处理完毕,返回过滤器");
}

拦截路径配置

@WebFilterurlPatterns 属性支持三种配置方式:

配置方式示例说明
精确路径/login只拦截 /login 请求,其余路径不拦截
目录前缀/emps/*拦截所有以 /emps/ 开头的路径,后续路径可有可无(如 /emps/emps/1/emps/list
全局拦截/*拦截所有请求路径

/*/** 的区别

模式含义可配置位置示例
/*只匹配当前目录下的一级路径,不匹配多级过滤器的 urlPatterns、拦截器的 addPathPatterns()/emps/1 匹配,/emps/1/2 不匹配
/**匹配所有层级的路径,任意深度拦截器的 addPathPatterns()过滤器不支持 /**/emps/1/2/3 也能匹配

过滤器(Servlet 规范)的 urlPatterns 只支持 /*不支持 /**/** 按字面处理);拦截器(Spring MVC)中 /*/** 均支持。

过滤器链(Filter Chain)机制

过滤器链定义

当项目中配置多个过滤器时,它们按声明顺序构成一个过滤器链

请求正向传递流程

请求依次经过每个过滤器:第一个过滤器放行 → 进入第二个过滤器 → 第二个过滤器放行 → 若存在第三个则进入,否则放行至目标资源。

text
请求 → FilterA → FilterB → FilterC → 目标资源

响应反向回调流程

资源响应完成后,沿过滤器链逆序返回:先执行最后一个过滤器的放行后逻辑,再执行倒数第二个过滤器的放行后逻辑……最终执行第一个过滤器的放行后逻辑,再响应浏览器。

text
请求:  FilterA → FilterB → FilterC → 资源
响应:  FilterA ← FilterB ← FilterC ← 资源

FilterChain 参数本质

doFilter() 的第三个参数 FilterChain 即为当前过滤器链对象;调用其 doFilter() 方法即触发向下一个过滤器或目标资源的流转

过滤器执行顺序控制

在注解配置方式下,过滤器执行顺序默认由类名的自然字母序决定

  • 类名字典序靠前者(如 ABCFilter)先执行放行前逻辑
  • 类名字典序靠后者(如 XYZFilter)后执行放行前逻辑
  • 放行后阶段执行顺序相反XYZFilter 先于 ABCFilter 执行放行后逻辑

拦截器(Interceptor)

拦截器是 Spring MVC 提供的组件,运行在 DispatcherServlet 内、Controller 之外,用于在请求到达目标资源(Controller 方法)前后执行通用逻辑,如登录校验、权限校验、日志记录等。

核心方法

实现 HandlerInterceptor 接口,可重写三个方法:

方法调用时机返回值作用
preHandle()目标资源(Controller)运行之前返回 true 放行;返回 false 拦截(不再执行后续方法)
postHandle()目标资源运行之后
afterCompletion()视图渲染完毕之后无(无论是否异常都会执行)
java
@Slf4j
@Component
public class DemoInterceptor implements HandlerInterceptor {

    // 在目标资源运行之前运行:true 放行,false 拦截
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        // 1. token 不存在或为空 -> 未登录
        if (token == null || token.isEmpty()) {
            writeUnauthorized(response, "未登录,请先登录");
            return false;
        }
        // 2. token 无效(过期/被篡改/格式错误)-> 登录失效
        String pureToken = token.replace("Bearer ", "");
        if (!JwtUtil.validateToken(pureToken)) {
            writeUnauthorized(response, "用户登录已失效,请重新登录");
            return false;
        }
        // 3. 校验通过 -> 放行
        return true;
    }

    // 在目标资源运行之后运行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle....");
    }

    // 视图渲染完毕之后运行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion....");
    }

    private void writeUnauthorized(HttpServletResponse response, String msg) throws IOException {
        response.setStatus(401);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(OBJECT_MAPPER.writeValueAsString(Result.error(msg)));
    }
}

拦截器链机制

多个拦截器同样构成拦截器链,执行顺序与过滤器链类似:

text
请求:  InterceptorA.preHandle → InterceptorB.preHandle → Controller
后置:  Controller → InterceptorB.postHandle → InterceptorA.postHandle
完成:  InterceptorB.afterCompletion → InterceptorA.afterCompletion(逆序)

若某个拦截器的 preHandle 返回 false,后续拦截器与 Controller 均不再执行,但已放行的拦截器的 afterCompletion 仍会执行。

注册与配置

拦截器需实现 WebMvcConfigurer 注册,否则不生效:

java
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private DemoInterceptor demoInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(demoInterceptor)
                .addPathPatterns("/**")                 // 拦截所有请求
                .excludePathPatterns("/login");         // 放行登录接口
    }
}
方法说明
addInterceptor()注册拦截器
addPathPatterns()设置拦截路径(支持 /**/emps/* 等)
excludePathPatterns()设置放行路径(不拦截的请求)

过滤器 vs 拦截器对比

对比项过滤器(Filter)拦截器(Interceptor)
所属体系Servlet 规范(Web 容器层面)Spring MVC 框架(Spring 层面)
执行时机DispatcherServlet 之前执行DispatcherServlet 之内、Controller 前后执行
依赖容器不依赖 Spring,纯 Servlet 组件依赖 Spring(由 Spring 容器管理,可 @Autowired
配置方式@WebFilter + @ServletComponentScan实现 WebMvcConfigurer 注册
核心方法init() / doFilter() / destroy()preHandle() / postHandle() / afterCompletion()
拦截范围拦截所有请求(静态资源、JSP 等)只拦截 Controller 请求(静态资源默认不拦截)
放行方式调用 chain.doFilter(request, response)preHandle() 返回 true
拦截后响应自行写 JSON(异常全局处理器捕获不到)同上(同样在 Spring MVC 拦截链之外,@RestControllerAdvice 捕获不到)
执行顺序先于拦截器执行后于过滤器执行
text
请求 → Filter.doFilter → DispatcherServlet → Interceptor.preHandle
     → Controller → Interceptor.postHandle → Interceptor.afterCompletion
     → Filter 放行后逻辑 → 响应浏览器

知识是财富,分享是快乐!