[Spring-注解] 之 @ControllerAdvice

@ControllerAdvice注解

  • 概述

@ControllerAdvice,是spring3.2提供的新注解。被@ControllerAdvice注解的类则被显式地声明为一个Spring的beans,或者通过类路径扫描来自动检测。

默认情况下,@ControllerAdvice注解类中的方法全局应用于所有控制器。使用选择器(annotations, basePackageClasses, basePackages和value)则可以缩放指定需要应用的具体控制器。

  • 作用

@ControllerAdvice是在类上声明的注解,其用法主要有三点:

1.全局异常处理

2.全局数据绑定

3.全局数据预处理

结合方法型注解@ExceptionHandler,用于捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的;

结合方法型注解@InitBinder,用于request中自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的;

结合方法型注解@ModelAttribute,表示其标注的方法将会在目标Controller方法执行之前执行。 

  • 使用

@ControllerAdvice的用法基本是将其声明在某个bean上,然后在该bean的方法上使用其他的注解来指定不同的织入逻辑。不过这里@ControllerAdvice并不是使用AOP的方式来织入业务逻辑的,而是Spring内置对其各个逻辑的织入方式进行了内置支持。

全局异常处理

@ExceptionHandler的作用主要在于声明一个或多个类型的异常,当符合条件的Controller抛出这些异常之后将会对这些异常进行捕获,然后按照其标注的方法的逻辑进行处理,从而改变返回的视图信息。如下是@ExceptionHandler的属性结构:

@ExceptionHandler注解的声明:

/**
 * @author Arjen Poutsma
 * @author Juergen Hoeller
 * @since 3.0
 * @see org.springframework.web.context.request.WebRequest
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {

	/**
	 * Exceptions handled by the annotated method. If empty, will default to any
	 * exceptions listed in the method argument list.
	 */
	Class<? extends Throwable>[] value() default {};

}

当使用该注解的时候,value属性为空的话,则默认捕获所有异常。

下面展示一个简单的全局异常管理的例子:

@ControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 入参参数校验-Exception
     * @param exception
     * @return
     * 不带任何参数访问接口,会抛出 BindException
     */
    @ExceptionHandler(value = BindException.class)
    public ResponseData argumentBindException(BindException exception) {
        String message = exception.getAllErrors().get(0).getDefaultMessage();
        return ResponseData.failed(message);
    }

    /**
     * 入参必填项校验-Exception
     * @param exception
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseData argumentValidatedException(MethodArgumentNotValidException exception) {
        String message = exception.getBindingResult().getAllErrors().get(0).getDefaultMessage();
        return ResponseData.failed(ExceptionEnum.ARGUMENT_VALIDATED_EXCEPTION.code(),ExceptionEnum.ARGUMENT_VALIDATED_EXCEPTION.desc() + message);

    }


    /**
     * 空指针异常
     * @param exception
     * @return
     */
    @ResponseBody
    @ExceptionHandler(NullPointerException.class)
    public ResponseData nullPointerException(NullPointerException exception) {
        exception.printStackTrace();
        return ResponseData.failed(ExceptionEnum.NULL_POINTER_EXCEPTION);
    }

}

全局异常处理的存在可以去除我们业务代码中丑陋的try(){...}catch{...}块,使开发更加专业于业务的开发,也可以去掉繁琐的参数校验,使之变得优雅可读。

全局数据绑定

全局数据绑定功能可以用来做一些初始化的数据操作,我们可以将一些公共的数据定义在添加了 @ControllerAdvice 注解的类中,这样,在每一个 Controller 的接口中,就都能够访问导致这些数据。

@ModelAttribute,处理用于接口参数可以用于转换对象类型的属性之外,其还可以用来进行方法的声明。如果声明在方法上,并且结合@ControllerAdvice,该方法将会在@ControllerAdvice所指定的范围内的所有接口方法执行之前执行,并且@ModelAttribute标注的方法的返回值还可以供给后续会调用的接口方法使用。

@ModelAttribute注解的声明:

/**
 * @author Juergen Hoeller
 * @author Rossen Stoyanchev
 * @since 2.5
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {

	/**
	 * Alias for {@link #name}.
      * 该属性与name属性的作用一致,用于指定目标参数的名称
	 */
	@AliasFor("name")
	String value() default "";

	/**
	 * The name of the model attribute to bind to.
	 * <p>The default model attribute name is inferred from the declared
	 * attribute type (i.e. the method parameter type or method return type),
	 * based on the non-qualified class name:
	 * e.g. "orderAddress" for class "mypackage.OrderAddress",
	 * or "orderAddressList" for "List&lt;mypackage.OrderAddress&gt;".
	 * @since 4.3
	 */
	@AliasFor("value")
	String name() default "";

	/**
	 * Allows declaring data binding disabled directly on an {@code @ModelAttribute}
	 * method parameter or on the attribute returned from an {@code @ModelAttribute}
	 * method, both of which would prevent data binding for that attribute.
	 * <p>By default this is set to {@code true} in which case data binding applies.
	 * Set this to {@code false} to disable data binding.
	 * @since 4.3
      * 与name属性一起使用,如果指定了binding为false,那么name属性指定名称的属性将不会被处理
	 */
	boolean binding() default true;

}

这里@ModelAttribute的各个属性值主要是用于其在接口参数上进行标注时使用的,如果是作为方法注解,其name或value属性则指定的是返回值的名称。

@ModelAttribute注解标记的方法返回的是一个全局数据,默认的key是返回类型的驼峰形式,我们也可以通过指定@ModelAttribute(name="keyName")的name属性来修改全局数据的key值,在Controller中取值首先要给方法加上Model model参数,用来接收全局数据,然后有两种方式取得全局数据,第一种是用model的getAttribute方法,另一种就是asMap方法转成一个Map,然后从map中根据key获取。

下面展示一个简单的全局数据绑定的例子:

package com.roker.fate.common.handler;

import com.roker.fate.model.vo.User;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RestControllerAdvice;



@ControllerAdvice
public class GlobalDataBindHandler {

    @ModelAttribute(value = "string")
    public String globalStringData(){
        return "这是全局数据绑定-String";
    }

    @ModelAttribute(value = "user")
    public User globalUserData(){
        User user = new User();
        user.setUsername("Java");
        user.setUserCode(4l);
        return user;
    }
}
package com.roker.fate.controller;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.Map;


@RestController
public class UserController {


    @GetMapping(value = "/dataInfo1")
    public ResponseData globalDataDemo1(Model model){
        //获取方式一
        String string1 = (String) model.getAttribute("string");
        System.out.println(string1);
        //获取方式二
        Map<String, Object> map = model.asMap();
        String string2 = (String) map.get("string");
        System.out.println(string2);
        return ResponseData.ok(string1);
    }

    @GetMapping(value = "/dataInfo2")
    public User globalDataDemo2(Model model){
        User user = (User) model.getAttribute("user");
        System.out.println(user);
        return user;
    }



}

使用@ModelAttribute注解标注的方法确实在目标接口执行之前执行了。需要说明的是,@ModelAttribute标注的方法的执行是在所有拦截器的preHandle()方法执行之后才会执行。

全局数据预处理

@InitBinder,该注解的主要作用是绑定一些自定义的参数。一般情况下我们使用的参数通过@RequestParam,@RequestBody或者@ModelAttribute等注解就可以进行绑定了,但对于一些特殊类型参数,比如Date,它们的绑定Spring是没有提供直接的支持的,我们只能为其声明一个转换器,将request中字符串类型的参数通过转换器转换为Date类型的参数,从而供给@RequestMapping标注的方法使用。

如下是@InitBinder的声明:

/**
 * @author Juergen Hoeller
 * @since 2.5
 * @see org.springframework.web.bind.WebDataBinder
 * @see org.springframework.web.context.request.WebRequest
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InitBinder {

	/**
     * 这里value参数用于指定需要绑定的参数名称,如果不指定,则会对所有的参数进行适配,
     * 只有是其指定的类型的参数才会被转换
	 */
	String[] value() default {};

}

如下是使用@InitBinder注册Date类型参数转换器的实现:

package com.roker.fate.common.handler;

import org.springframework.format.datetime.DateFormatter;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RestControllerAdvice;


@RestControllerAdvice
public class GlobalDataPreprocessHandler {

    @InitBinder
    public void dataFormatter(WebDataBinder binder){
        binder.addCustomFormatter(new DateFormatter("yyyy-mm-dd hh24:mi:ss"));
    }
}
    @RequestMapping(value = "/date", method = RequestMethod.GET)
    public ResponseData detail(@RequestParam("id") long id, Date date) {
        System.out.println(date);
        return ResponseData.ok();
    }

补充

@ControllerAdvice是组件注解,他使得其实现类能够被classpath扫描自动发现,如果应用是通过MVC命令空间或MVC Java编程方式配置,那么该特性默认是自动开启的。

注解@ControllerAdvice的类可以拥有@ExceptionHandler, @InitBinder或 @ModelAttribute注解的方法,并且这些方法会被应用到控制器类层次的所有@RequestMapping方法上。

@RestControllerAdvice 类似于 @RestController 与 @Controller的区别

猜你喜欢

转载自blog.csdn.net/Roker_966/article/details/107978574