ControllerAdvice注解

ControllerAdvice注解用于实现Controller层的全局异常捕获,可以自定义捕获特定异常进行处理。

官方文档

ControllerAdvice文档

Serves as a specialization of @Component, allowing for implementation classes to be autodetected through classpath scanning.

It is typically used to define @ExceptionHandler, @InitBinder, and @ModelAttribute methods that apply to all @RequestMapping methods.

Note that those checks are done at runtime, so adding many attributes and using multiple strategies may have negative impacts (complexity, performance).

官方文档说通常用于定义@ExceptionHandler、@InitBinder 和@ModelAttribute方法,应用于所有的@RequestMapping方法。这句话说明全局异常只应用于Controller层,对于Service层的异常只能抛出,返回给Controller处理。最后一句是提示这个检查是在程序运行时执行的,所以定于全局异常是如果做太多的处理,可能会对程序有不利的影响。

@InitBinder

首先,该注解被解析的时机,是该匹配Controller的请求执行映射的方法之前; 同时 @InitBinder标注的方法执行是多次的,一次请求来就执行一次。

当某个Controller上的第一次请求由SpringMvc前端控制器匹配到该Controller之后,根据Controller的 class 类型查找,所有方法上标注了@InitBinder的方法,并且存入RequestMappingHandlerAdapter的 initBinderCache,下次一请求执行对应业务方法之前时,可以走initBinderCache缓存,而不用再去解析@InitBinder; 所以 initBinder是controller级别的,一个Controller实例中的所有@initBinder 只对该Controller有效;

@ModelAttribute

被@ModelAttribute注释的方法会在此controller每个方法执行前被执行。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
@Controller
public class HelloWorldController {
@ModelAttribute
public void populateModel(@RequestParam String abc, Model model) {
model.addAttribute("attributeName", abc);
}

@RequestMapping(value = "/helloWorld")
public String helloWorld() {
return "helloWorld";
}
}

这个例子,在获得请求/helloWorld 后,populateModel方法在helloWorld方法之前先被调用,它把请求参数(/helloWorld?abc=text)加入到一个名为attributeName的model属性中,在它执行后 helloWorld被调用,返回视图名helloWorld和model已由@ModelAttribute方法生产好了。

这个例子中model属性名称和model属性对象由model.addAttribute()实现,不过前提是要在方法中加入一个Model类型的参数。

全局异常示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;


@ControllerAdvice
public class MyControllerAdvice {

/**
* 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器
* @param binder
*/
@InitBinder
public void initBinder(WebDataBinder binder) {}

/**
* 把值绑定到Model中,使全局@RequestMapping可以获取到该值
* @param model
*/
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("Date", new Date());
}

/**
* 全局异常捕捉处理
* @param ex
* @return
*/
@ResponseBody
@ExceptionHandler(value = Exception.class)
public Map errorHandler(Exception ex) {
Map map = new HashMap();
map.put("code", 100);
map.put("msg", ex.getMessage());
return map;
}

}

启动应用后,被 @ExceptionHandler、@InitBinder、@ModelAttribute 注解的方法,都会作用在 被 @RequestMapping 注解的方法上。

@ExceptionHandler 拦截了异常,我们可以通过该注解实现自定义异常处理。其中,@ExceptionHandler 配置的 value 指定需要拦截的异常类型,上面拦截了 Exception.class 这种异常。

自定义异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import com.markhub.common.lang.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.ShiroException;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;


@Slf4j
@RestControllerAdvice
public class GlobalException {

//权限异常
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(value = ShiroException.class)
public Result handler(ShiroException e) {
log.error("运行时异常:--------------{}", e);
return Result.fail(401,e.getMessage(),null);
}

//实体校验异常
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Result handler(MethodArgumentNotValidException e) {
log.error("实体校验异常:--------------{}", e);
BindingResult bindingResult = e.getBindingResult();
ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();
return Result.fail(objectError.getDefaultMessage());
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = IllegalArgumentException.class)
public Result handler(IllegalArgumentException e) {
log.error("Assert异常:--------------{}", e);
return Result.fail(e.getMessage());
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = RuntimeException.class)
public Result handler(RuntimeException e) {
log.error("运行时异常:--------------{}", e);
return Result.fail(e.getMessage());
}
}