Spring MVC 中的异常处理机制

倖福魔咒の 提交于 2019-12-23 04:52:26

Spring MVC 的异常解析

核心接口
• HandlerExceptionResolver
实现类
• SimpleMappingExceptionResolver
• DefaultHandlerExceptionResolver
• ResponseStatusExceptionResolver
• ExceptionHandlerExceptionResolver

Spring MVC 的异常解析HandlerExceptionResolver这个核心接口和其他几个实现类完成的,ResponseStatusExceptionResolver 处理 的是带ResponseStatus注解的一些方法和类,我们可以在异常类上面添加ResponseStatus这个注解,以表明在抛出这个异常类的时候,我的http响应码是什么。

DispatcherServlet如何处理异常
首先进入DispatcherServlet,找到doService方法。在doService方法中,在处理doDispatch时,我们进入doDispatch,在doDispatch中,我们可以看到,如果我们抛出异常,这些异常会被捕获住,然后,放在dispatchException当中,使用processDispatchResult处理结果,进入processDispatchResult,在处理结果的时候,就会去看,有没有传入一个异常,如果有传入异常,就会对这个异常进行处理,使用processHandlerException方法进行处理,进入processHandlerException,可以看到,通过handlerExceptionResolvers这一个链式解析,检验这个resolver能不能处理这个异常,如果可以处理,处理完毕之后,返回一个ModelAndView,使用ModelAndView做一个视图的呈现。

异常处理方法

处理方法
• @ExceptionHandler
添加位置
• @Controller / @RestController
• @ControllerAdvice / @RestControllerAdvice

在一个方法上添加ExceptionHandler注解,这样标明我们这个方法是来处理异常的。可以加在我们的Controller 当中,在Controller 类当中添加一个带有ExceptionHandler注解的方法。我们也可以做一个类似于aop的动作,去编写一个ControllerAdvice ,这里就相当于aop的一个拦截器,它会对我们所有的Controller做一个拦截,在ControllerAdvice 里面添加一个带有ExceptionHandler注解的方法。
注意:我们在ControllerAdvice 中添加的ExceptionHandler方法的优先级低于Controller 中添加的ExceptionHandler方法。

实例

目录结构如下:
在这里插入图片描述

需要修改的代码:

@Controller
@RequestMapping("/coffee")
@Slf4j
public class CoffeeController {
    @Autowired
    private CoffeeService coffeeService;

    @PostMapping(path = "/", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    @ResponseBody
    @ResponseStatus(HttpStatus.CREATED)
    public Coffee addCoffee(@Valid NewCoffeeRequest newCoffee,
                            BindingResult result) {
        if (result.hasErrors()) {
            log.warn("Binding Errors: {}", result); //当绑定出错的时候 返回一个FormValidationException的异常
            throw new FormValidationException(result);
        }
        return coffeeService.saveCoffee(newCoffee.getName(), newCoffee.getPrice());
    }

    @PostMapping(path = "/", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    @ResponseStatus(HttpStatus.CREATED)
    public Coffee addJsonCoffee(@Valid @RequestBody NewCoffeeRequest newCoffee,
                                BindingResult result) {
        if (result.hasErrors()) {
            log.warn("Binding Errors: {}", result); //当绑定出错的时候 ValidationException 自带的
            throw new ValidationException(result.toString());
        }
        return coffeeService.saveCoffee(newCoffee.getName(), newCoffee.getPrice());
    }

//    @PostMapping(path = "/", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
//    @ResponseBody
//    @ResponseStatus(HttpStatus.CREATED)
//    public Coffee addCoffeeWithoutBindingResult(@Valid NewCoffeeRequest newCoffee) {
//        return coffeeService.saveCoffee(newCoffee.getName(), newCoffee.getPrice());
//    }

//    @PostMapping(path = "/", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
//    @ResponseBody
//    @ResponseStatus(HttpStatus.CREATED)
//    public Coffee addJsonCoffeeWithoutBindingResult(@Valid @RequestBody NewCoffeeRequest newCoffee) {
//        return coffeeService.saveCoffee(newCoffee.getName(), newCoffee.getPrice());
//    }

    @PostMapping(path = "/", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @ResponseBody
    @ResponseStatus(HttpStatus.CREATED)
    public List<Coffee> batchAddCoffee(@RequestParam("file") MultipartFile file) {
        List<Coffee> coffees = new ArrayList<>();
        if (!file.isEmpty()) {
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(
                        new InputStreamReader(file.getInputStream()));
                String str;
                while ((str = reader.readLine()) != null) {
                    String[] arr = StringUtils.split(str, " ");
                    if (arr != null && arr.length == 2) {
                        coffees.add(coffeeService.saveCoffee(arr[0],
                                Money.of(CurrencyUnit.of("CNY"),
                                        NumberUtils.createBigDecimal(arr[1]))));
                    }
                }
            } catch (IOException e) {
                log.error("exception", e);
            } finally {
                IOUtils.closeQuietly(reader);
            }
        }
        return coffees;
    }

    @GetMapping(path = "/", params = "!name")
    @ResponseBody
    public List<Coffee> getAll() {
        return coffeeService.getAllCoffee();
    }

    @RequestMapping(path = "/{id}", method = RequestMethod.GET)
    @ResponseBody
    public Coffee getById(@PathVariable Long id) {
        Coffee coffee = coffeeService.getCoffee(id);
        log.info("Coffee {}:", coffee);
        return coffee;
    }

    @GetMapping(path = "/", params = "name")
    @ResponseBody
    public Coffee getByName(@RequestParam String name) {
        return coffeeService.getCoffee(name);
    }
}



@ResponseStatus(HttpStatus.BAD_REQUEST) //当碰到这个异常的时候 http状态码为400的BAD_REQUEST
@Getter
@AllArgsConstructor
public class FormValidationException extends RuntimeException {
    private BindingResult result;
}





@RestControllerAdvice //是@ControllerAdvice和@ResponseBody的合并。此注解通过对异常的拦截实现的统一异常返回处理
public class GlobalControllerAdvice {
    @ExceptionHandler(ValidationException.class) //专门处理ValidationException这个异常 可以传入很多类型
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, String> validationExceptionHandler(ValidationException exception) {
        Map<String, String> map = new HashMap<>();
        map.put("message", exception.getMessage());
        return map;
    }
}

实验效果

打开postman
我们以表单的方式创建一个咖啡(错误情况):

在这里插入图片描述
提供表单做的错误处理,是springmvc来做的处理,即,使用的是FormValidationException

我们以json的形式创建一个咖啡(错误情况):
在这里插入图片描述

我们可以看到,返回的是message错误信息,使用了GlobalControllerAdvice进行了处理。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!