Spring MVC 为控制器添加通知

Spring MVC 为控制器添加通知

与 Spring AOP 一样,Spring MVC 也可以为控制器加入通知,它主要涉及 4 个核心注解,各自承担不同的功能角色:

注解 作用描述
@ControllerAdvice 作用于类,标识为全局性的控制器拦截器,可指定作用范围(如特定包),应用于对应控制器
@InitBinder 允许在构造控制器参数时,加入自定义控制(如参数类型转换、格式化)
@ExceptionHandler 控制器发生异常时,匹配异常类型并跳转至该方法处理,实现统一异常响应
@ModelAttribute 先于控制器方法执行,返回对象或向数据模型添加属性,传递给被拦截的控制器

@ControllerAdvice 与 @InitBinder

问题场景:日期参数类型转换失败

创建控制器 ConverterController,包含日期格式化接口,但直接请求会出现类型转换异常:

package com.controller;

import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Date;

@Controller
public class ConverterController {

    @RequestMapping("/date")
    @ResponseBody
    @JsonFormat(pattern = "yyyy-MM-dd")
    public Date format(Date date) {
        return date;
    }
}

请求结果
image

原因:默认无法将字符串 2020-10-20 转换为 Date 类型。

解决方案:通过 @ControllerAdvice + @InitBinder 自定义参数转换

创建全局通知类 CommonControllerAdvice,通过 @InitBinder 注册日期类型编辑器:

package com.controller.advice;

import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;

import java.text.SimpleDateFormat;
import java.util.Date;

@ControllerAdvice(basePackages = "com.controller") // 指定拦截的包
public class CommonControllerAdvice {

    @InitBinder // 允许构造控制器参数的时候加入一定的自定义控制
    public void init(WebDataBinder binder) {
        // 针对日期类型的格式化,CustomDateEditor 是自定义编辑器
        // boolean 值表示是否允许为空(此处为 false,不允许为空)
        binder.registerCustomEditor(Date.class,
                new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"),
                        false));
    }
}

关键说明

  • @ControllerAdvice(basePackages = "com.controller"):指定仅拦截 com.controller 包下的控制器
  • WebDataBinder:数据绑定器,用于注册参数转换规则
  • CustomDateEditor:Spring 提供的日期编辑器,指定格式为 yyyy-MM-dd

请求结果
image

日期参数成功转换为时间戳返回,无需手动定义 Formatter。

@ExceptionHandler(统一异常处理)

问题场景:控制器抛出未处理异常

ConverterController 中添加会抛出算术异常的接口:

package com.controller;

import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Date;

@Controller
public class ConverterController {

    // 省略 date 接口...

    @RequestMapping("/exception")
    @ResponseBody
    public String exception() {
        System.out.println(1/0); // 触发算术异常(除以零)
        return "exception";
    }
}

请求结果
image

直接返回服务器异常页面,用户体验差。

解决方案:通过 @ExceptionHandler 统一处理异常

创建异常页面 error.jsp

<%--
  Created by IntelliJ IDEA.
  User: Jing61
  Date: 2025/12/29
  Time: 14:01
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>
<html>
<head>
    <title>异常</title>
</head>
<body>
    出错:<%=exception.getMessage()%>
</body>
</html>

在 CommonControllerAdvice 中添加异常处理方法

package com.controller.advice;

import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;

import java.text.SimpleDateFormat;
import java.util.Date;

@ControllerAdvice(basePackages = "com.controller") // 指定拦截的包
public class CommonControllerAdvice {

    // 省略 @InitBinder 方法...

    @ExceptionHandler(Exception.class) // 匹配所有 Exception 类型异常
    public String exception() {
        return "error"; // 返回异常视图(error.jsp)
    }
}

关键说明

  • @ExceptionHandler(Exception.class):指定处理所有 Exception 及其子类异常,也可指定具体异常(如 ArithmeticException.class
  • 方法返回值为视图名称,Spring MVC 会跳转到对应的 JSP 页面

请求结果
image

成功跳转到自定义异常页面,展示友好的错误信息。

@ModelAttribute 实战(全局数据模型)

功能说明

@ModelAttribute 注解的方法会在控制器方法执行前运行,可向数据模型(Model)中添加全局属性,所有被拦截的控制器都可访问该属性。

代码

在 CommonControllerAdvice 中添加 @ModelAttribute 方法

package com.controller.advice;

import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;

import java.text.SimpleDateFormat;
import java.util.Date;

@ControllerAdvice(basePackages = "com.controller") // 指定拦截的包
public class CommonControllerAdvice {

    // 省略 @InitBinder 方法...
    // 省略 @ExceptionHandler 方法...

    @ModelAttribute
    public void binding(Model model) {
        // 向模型添加全局属性:username = admin
        model.addAttribute("username", "admin");
    }
}

在控制器中获取全局属性

package com.controller;

import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Date;

@Controller
public class ConverterController {

    // 省略 date、exception 接口...

    @RequestMapping("/attr")
    @ResponseBody
    // 通过 @ModelAttribute 注解获取全局属性 username
    public String getAttr(@ModelAttribute(name = "username") String username) {
        return username; // 返回全局属性值
    }
}

关键说明

  • @ModelAttribute 方法的 Model 参数用于存储全局属性,键值对为 username -> admin
  • 控制器方法通过 @ModelAttribute(name = "username") 直接获取全局属性,无需手动从 Model 中提取

请求结果
image

成功获取到全局数据模型中的 username 属性值。

核心注解总结

注解 执行时机 核心作用
@ControllerAdvice 全局生效,标识通知类 指定拦截范围(包、注解等),统一管理通知
@InitBinder 控制器参数绑定前 自定义参数转换、格式化(如日期、字符串)
@ExceptionHandler 控制器抛出异常时 统一异常处理,返回友好响应(视图/JSON)
@ModelAttribute 控制器方法执行前 向全局数据模型添加属性,供所有控制器访问
posted @ 2025-12-29 15:29  Jing61  阅读(5)  评论(0)    收藏  举报