SpringMVC学习记录05——控制器通知(ControllerAdvice)

###0.前言
《Java EE互联网轻量级框架整合开发——SSM框架(Spring MVC+Spring+MyBatis)和Redis实现》

本文主要记录:Spring MVC 中控制器如何添加通知(类似Spring AOP)。

  • @ControllerAdvice:标识为通知类(拦截器),可设置作用范围。主要用于操作对应的控制器。
  • @InitBinder:用于注册页面参数传递到控制器的转换规则。如:String -> Date
  • @ExceptionHandler:注册一个控制器异常,当控制器发生注册对应的异常时,跳到该方法。
  • @ModelAttribute:在进入控制器方法前,保存数据到数据模型(Model)中

###1. @InitBinder、@ExceptionHandler和@ModelAttribute
在控制器(Controller)中可以使用@InitBinder、@ExceptionHandler和@ModelAttribute对控制器的功能进行扩展和补充。注意,在Controller中配置这3个注解只作用于当前Controller

  • @InitBinder:扩展控制器的类型转换规则,使页面的传递到控制器的参数(String类型)可以转换为一些特殊类型(如:Date类型)。
  • @ExceptionHandler:捕获控制器中的指定异常,并对异常进行处理
  • @ModelAttribute:被注解的方法会在控制器方法运行之前执行。可以此方法中往数据模型(Model)添加参数。

示例代码:

public class BaseController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        //数据绑定加入字符串转Date类型规则(编辑器)
        binder.registerCustomEditor(Date.class, new MyDateEditor());
        // 数据绑定器加入验证器
        binder.setValidator(new TransactionValidator());
    }

    //自定义时间类型的编辑器
    private class MyDateEditor extends PropertyEditorSupport {
        @Override
        public void setAsText(String text) throws IllegalArgumentException {
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date = null;
            try {
                date = format.parse(text);
            } catch (ParseException e) {
                format = new SimpleDateFormat("yyyy-MM-dd");
                try {
                    date = format.parse(text);
                } catch (ParseException e1) {
                }
            }
            setValue(date);
        }
    }

    /**
     * 在进入控制器方法前运行,先从数据库中获取数据,再设置到Model中
     */
    @ModelAttribute("testData")
    public TestData getDate(Long id) {
        TestData data = dataService.getData(id);
        return data;
    }

    /**
     * 在进入控制器方法前运行,设置到Model中,
     */
    @ModelAttribute
    public void populateModel(Model model) {
        model.addAttribute("test","populateModelValue");
    }

    //当前控制器发生Exception异常时,进入该方法(可以是自定义异常)
    @ExceptionHandler(Exception.class)
    public String HandleException(Exception e) {
        //返回指定的页面,避免不友好
        return "exception";
    }

}

上述代码中,在控制器中使用了3类注解,它们都只是作用于当前控制器。其中:

  • @InitBinder用于注册转换规则的编辑器验证器WebDataBinder中,主要作用需要绑定的数据的属性转换。
  • @ModelAttribute用于设置数据到数据模型(Model)中,以便控制器(Controller)的方法和页面数据绑定的使用。
  • @ExceptionHandler根据指定的Exception类型捕获和处理当前控制器(Controller)中的异常。

在Spring中会将自身产生的错误转换为合适的状态码,通过这些状态码可以进一步的确认异常发生的原因。例如:

  • BindException : 400-Bad Request,绑定数据异常
  • ConversionNotSupportedException:500-Internal Server Error,数据类型转换异常

自定义异常的方法:

//新增Spring MVC的异常映射,code代表异常映射码,而reason则代表异常原因
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "找不到角色信息异常!!")
public class RoleException extends RuntimeException  {
    private static final long serialVersionUID = 5040949196309781680L;
}

###2. 添加通知
在一些业务场景,可能需要配置多个控制器同时使用@InitBinder@ExceptionHandler@ModelAttribute。例如:配置通用的异常捕获,通用的数据转换规则等。那么就需要将功能模块化为一个通知类(@ControllerAdvice).

定义通知类(标注@ControllerAdvice):

//标识控制器通知,并且指定对应的包
@ControllerAdvice(basePackages={"com.ssm.chapter16.controller.advice"})
public class CommonControllerAdvice {

    //定义HTTP对应参数处理规则
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        //针对日期类型的格式化,其中CustomDateEditor是客户自定义编辑器
         //它的boolean参数表示是否允许为空
        binder.registerCustomEditor(Date.class, 
             new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false));
    }

     //处理数据模型,如果返回对象,则该对象会保存在
    @ModelAttribute
    public void populateModel(Model model) {
        model.addAttribute("projectName", "chapter16");
    }

    //异常处理,使得被拦截的控制器方法发生异常时,都能用相同的视图响应
    @ExceptionHandler(Exception.class)
    public String exception() {
        return "exception";
    }

}

上述代码表示,通知类CommonControllerAdvice作用于com.ssm.chapter16.controller.advice目录下的所有控制器。因为@ControllerAdvice注解带有@Component标记,所以会自动被加入IoC容器中。

###3.总结
Spring MVC为开发者提供了便利的数据绑定,但在一些特殊业务场景下可能无法满足需求,那么可以通过@InitBinder进行扩展。同时也可以通过@ExceptionHandler对控制器的异常进行处理。也可以使用@ModelAttribute为Model提前配置一些通用的参数。

END

–Nowy

–2018.12.25

分享到