当前位置: 澳门新濠3559 > 编程 > 正文

POST方法 而在resultful风格下以资源为导向,展现不

时间:2019-10-06 23:50来源:编程
在web应用中,请求处理时,出现异常是非常常见的。所以当应用出现各类异常时,进行异常的捕获或者二次处理(比如sql异常正常是不能外抛)是非常必要的,比如在开发对外api服务时,

在web应用中,请求处理时,出现异常是非常常见的。所以当应用出现各类异常时,进行异常的捕获或者二次处理(比如sql异常正常是不能外抛)是非常必要的,比如在开发对外api服务时,约定了响应的参数格式,如respCoderespMsg,调用方根据错误码进行自己的业务逻辑。本章节就重点讲解下统一异常和数据校验处理。

澳门新濠3559 1

Spring-Boot Restful Api

在spring 3.2中,新增了@ControllerAdvice 注解,可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute,并应用到所有@RequestMapping中。参考:@ControllerAdvice 文档

springboot中,默认在发送异常时,会跳转值/error请求进行错误的展现,根据不同的Content-Type展现不同的错误结果,如json请求时,直接返回json格式参数。浏览器访问异常时:

今天来一起学习一下Spring Boot中的异常处理,在日常web开发中发生了异常,往往是需要通过一个统一的异常处理来保证客户端能够收到友好的提示。

1、Restful API开发

一、介绍

澳门新濠3559 2浏览器

本篇要点如下

1.1 Restful简介

springMVC对编写Restful Api提供了很好的支持。

Restful Api有三个主要的特性:

  • 是基于Http协议的,是无状态的。
  • 是以资源为导向的
  • 人性化的,返回体内部包含相关必要的指导和链接

面向资源?
传统的Api接口以动作为导向,并且请求方法单一。例如/user/query?id=1 GET方法 ;/user/create
POST方法 而在resultful风格下以资源为导向,例如: /user/id(GET方法,获取) /user/(POST方法,创建)

restful api 用url描述资源,用Http方法描述行为,用Http状态码描述不同的结果,使用json作为交互数据(包括入参和响应)
澳门新濠3559,restful只是一种风格并不是一种强制的标准

创建 MyControllerAdvice,并添加 @ControllerAdvice注解。

package com.sam.demo.controller;

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;

/**
 * controller 增强器
 * @author sam
 * @since 2017/7/17
 */
@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("author", "Magical Sam");
    }

    /**
     * 全局异常捕捉处理
     * @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;
    }

}

使用postman访问时:

  • 介绍Spring Boot默认的异常处理机制
  • 如何自定义错误页面
  • 通过@ControllerAdvice注解来处理异常

1.2 编写restful api 测试用例

因为restful api 与传统api存在一些风格上的差异,例如以method代表行为。所以在开发的过程中需要一边开发一边测试,测试我们的接口是否达到了预期的目的。springBoot提供了开发restful api测试用例的方法。首先导入依赖

    <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
    </dependency>

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

澳门新濠3559 3postman

默认情况下,Spring Boot为两种情况提供了不同的响应方式。

1.3 编写restful接口

@ModelAttribute:在Model上设置的值,对于所有被 @RequestMapping 注解的方法中,都可以通过 ModelMap 获取,如下:

@RequestMapping("/home")
public String home(ModelMap modelMap) {
    System.out.println(modelMap.get("author"));
}

//或者 通过@ModelAttribute获取

@RequestMapping("/home")
public String home(@ModelAttribute("author") String author) {
    System.out.println(author);
}

显然,默认的异常页是对用户或者调用者而言都是不友好的,所以一般上我们都会进行实现自己业务的异常提示信息。

一种是浏览器客户端请求一个不存在的页面或服务端处理发生异常时,一般情况下浏览器默认发送的请求头中Accept: text/html,所以Spring Boot默认会响应一个html文档内容,称作“Whitelabel Error Page”。

1.3.1 基本注解

  • @RestController 声明一个controller负责提供restful接口
  • @RequestMapping 将请求的url映射到方法
  • @RequetParam 映射请求参数到方法请求参数 可以指定required指定此参数是否必填,name参数指定别名,defaultValue指定默认值。在传参时,SpringMVC会自动封装参数,所以可以在方法中用一个对象参数接收

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

创建全局的统一异常处理类

利用@ControllerAdvice@ExceptionHandler定义一个统一异常处理类

  • @ControllerAdvice:控制器增强,使@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法应用到所有的 @RequestMapping注解的方法。

  • @ExceptionHandler:异常处理器,此注解的作用是当出现其定义的异常时进行处理的方法

创建异常类:CommonExceptionHandler

@ControllerAdvicepublic class CommonExceptionHandler { /** * 拦截Exception类的异常 * @param e * @return */ @ExceptionHandler(Exception.class) @ResponseBody public Map<String,Object> exceptionHandler(Exception e){ Map<String,Object> result = new HashMap<String,Object>(); result.put("respCode", "9999"); result.put("respMsg", e.getMessage; //正常开发中,可创建一个统一响应实体,如CommonResp return result; }}

多余不同异常,需要进行不同的异常处理时,可编写多个exceptionHandler方法,注解ExceptionHandler指定处理的异常类,如

 /** * 拦截 CommonException 的异常 * @param ex * @return */ @ExceptionHandler(CommonException.class) @ResponseBody public Map<String,Object> exceptionHandler(CommonException ex){ log.info("CommonException:{}",ex.getMsg(), ex.getCode; Map<String,Object> result = new HashMap<String,Object>(); result.put("respCode", ex.getCode; result.put("respMsg", ex.getMsg; return result; }

由于加入了@ResponseBody,所以返回的是json格式

澳门新濠3559 4image澳门新濠3559 5image

说明异常已经被拦截了。可拦截不同的异常,进行不同的异常提示,比如NoHandlerFoundExceptionHttpMediaTypeNotSupportedExceptionAsyncRequestTimeoutException等等,这里就不列举了,读者可自己加入后实际操作下。

对于返回页面时,返回ModelAndView即可,如

@ExceptionHandler(value = Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { ModelAndView mav = new ModelAndView(); mav.addObject("exception", e); mav.addObject("url", req.getRequestURL; mav.setViewName(DEFAULT_ERROR_VIEW); return mav; }

由于工作中都是才有前后端分离开发模式,所以一般上都没有直接返回资源页的需求了,一般上都是返回固定的响应格式,如respCoderespMsgdata,前端通过判断respCode的值进行业务判断,是弹窗还是跳转页面。

在web开发时,对于请求参数,一般上都需要进行参数合法性校验的,原先的写法时一个个字段一个个去判断,这种方式太不通用了,所以java的JSR 303: Bean Validation规范就是解决这个问题的。

JSR 303只是个规范,并没有具体的实现,目前通常都是才有hibernate-validator进行统一参数校验。

JSR303定义的校验类型

Constraint 详细信息
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern 被注释的元素必须符合指定的正则表达式

Hibernate Validator 附加的 constraint

Constraint 详细信息
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range 被注释的元素必须在合适的范围内

创建实体类

@Data@NoArgsConstructor@AllArgsConstructorpublic class DemoReq { @NotBlank(message="code不能为空") String code; @Length(max=10,message="name长度不能超过10") String name;}

然后在控制层方法里,加入@Valid即可,这样在访问前,会对请求参数进行检验。

 @GetMapping("/demo/valid") public String demoValid(@Valid DemoReq req) { return req.getCode()   ","   req.getName(); }

启动,后访问http://127.0.0.1:8080/demo/valid

澳门新濠3559 6image

加上正确参数后,http://127.0.0.1:8080/demo/valid?code=3&name=s

澳门新濠3559 7image

这样数据的统一校验就完成了,对于其他注解的使用,大家可自行谷歌下,基本上都很简单的,对于已有的注解无法满足校验需要时,也可进行自定义注解的开发,一下简单讲解下,自定义注解的编写

不使用@valid的情况下,也可利用编程的方式编写一个工具类,进行实体参数校验

public class ValidatorUtil { private static Validator validator = ((HibernateValidatorConfiguration) Validation .byProvider(HibernateValidator.class).configure.failFast.buildValidatorFactory().getValidator(); /** * 实体校验 * * @param obj * @throws CommonException */ public static <T> void validate throws CommonException { Set<ConstraintViolation<T>> constraintViolations = validator.validate(obj, new Class[0]); if (constraintViolations.size { ConstraintViolation<T> validateInfo = (ConstraintViolation<T>) constraintViolations.iterator; // validateInfo.getMessage() 校验不通过时的信息,即message对应的值 throw new CommonException("0001", validateInfo.getMessage; } }}

使用

 @GetMapping("/demo/valid") public String demoValid(@Valid DemoReq req) { //手动校验 ValidatorUtil.validate; return req.getCode()   ","   req.getName(); }

澳门新濠3559 8image.png

1.3.2 @PathVariable

映射url片段到java方法参数

    @GetMapping("/user/{id}")
    public User getUserInfo(@PathVariable("id") String id){
        return new User("sico","12345");
    }

二、自定义异常处理(全局异常处理)

spring boot 默认情况下会映射到 /error 进行异常处理,但是提示并不十分友好,下面自定义异常处理,提供友好展示。

自定义校验注解

自定义注解,主要时实现ConstraintValidator的处理类即可,这里已编写一个校验常量的注解为例:参数值只能为特定的值。

自定义注解

@Documented//指定注解的处理类@Constraint(validatedBy = {ConstantValidatorHandler.class })@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })@Retentionpublic @interface Constant { String message() default "{constraint.default.const.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; String value();}

注解处理类

public class ConstantValidatorHandler implements ConstraintValidator<Constant, String> { private String constant; @Override public void initialize(Constant constraintAnnotation) { //获取设置的字段值 this.constant = constraintAnnotation.value(); } @Override public boolean isValid(String value, ConstraintValidatorContext context) { //判断参数是否等于设置的字段值,返回结果 return constant.equals; }}

使用

 @Constant(message = "verson只能为1.0",value="1.0") String version;

运行:

澳门新濠3559 9image

此时,自定义注解已生效。大家可根据实际需求进行开发。

大家看到在校验不通过时,返回的异常信息是不友好的,此时可利用统一异常处理,对校验异常进行特殊处理,特别说明下,对于异常处理类共有以下几种情况(被@RequestBody和@RequestParam注解的请求实体,校验异常类是不同的)

@ExceptionHandler(MethodArgumentNotValidException.class) public Map<String,Object> handleBindException(MethodArgumentNotValidException ex) { FieldError fieldError = ex.getBindingResult().getFieldError(); log.info("参数校验异常:{}", fieldError.getDefaultMessage(),fieldError.getField; Map<String,Object> result = new HashMap<String,Object>(); result.put("respCode", "01002"); result.put("respMsg", fieldError.getDefaultMessage; return result; } @ExceptionHandler(BindException.class) public Map<String,Object> handleBindException(BindException ex) { //校验 除了 requestbody 注解方式的参数校验 对应的 bindingresult 为 BeanPropertyBindingResult FieldError fieldError = ex.getBindingResult().getFieldError(); log.info("必填校验异常:{}", fieldError.getDefaultMessage(),fieldError.getField; Map<String,Object> result = new HashMap<String,Object>(); result.put("respCode", "01002"); result.put("respMsg", fieldError.getDefaultMessage; return result; }

启动后,提示就友好了

澳门新濠3559 10image

所以统一异常还是很有必要的。

本章节主要是阐述了统一异常处理和数据的合法性校验,同时简单实现了一个自定义的注解类,大家在碰见已有注解无法解决时,可通过自定义的形式进行,当然对于通用而已,利用@Pattern基本上都是可以实现的。

目前互联网上很多大佬都有springboot系列教程,如有雷同,请多多包涵了。本文是作者在电脑前一字一句敲的,每一步都是实践的。若文中有所错误之处,还望提出,谢谢。

  • 个人QQ:499452441
  • 微信公众号:lqdevOps

澳门新濠3559 11公众号

个人博客:

完整示例:chapter-8

原文地址:

另一种是使用Postman等调试工具发送请求一个不存在的url或服务端处理发生异常时,Spring Boot会返回类似如下的Json格式字符串信息

1.3.3 在url声明中使用正则表达式

在@pathVariable中url片段默认可以接收任何格式,任何类型,可以用正则表达式加以限定,例如:

    /**
     * 获取用户详情,利用正则表达式限定为只接收数字
     * @param id
     * @return
     */
    @GetMapping("/user/{id:\d }")
    public User getUserInfo(@PathVariable("id") String id){
        return new User("sico","12345");
    }

1、编写自定义异常类:

package com.sam.demo.custom;

/**
 * @author sam
 * @since 2017/7/17
 */
public class MyException extends RuntimeException {

    public MyException(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    private String code;
    private String msg;

    // getter & setter
}
{ "timestamp": "2018-05-12T06:11:45.209 0000", "status": 404, "error": "Not Found", "message": "No message available", "path": "/index.html"} 

1.3.4 使用@jsonView控制json输出内容

SpringMVC会将实体对象转换成json返回。有时候我们希望在不同的请求中隐藏一些字段。可以用@JsonView控制输出内容。
使用@jsonView注解有以下步骤:

  • 使用接口来声明多个视图
  • 在值对象的getter方法上指定视图
  • 在controller方法上指定视图

使用接口声明视图
此接口只作声明使用,可以直接放置到目标实体内部,示例:

public class User implements Serializable{

    public interface SimpleView{};
    public interface DetailView extends SimpleView{};
    //....
}

注意继承关系,DetailView继承了SimpleView。即视图DetailView会显示被SimpleView标注的视图

在值对象上的getter方法上指定视图

    @JsonView(SimpleView.class)
    public String getUsername() {
        return username;
    }
    //...
    @JsonView(DetailView.class)
    public String getPassword() {
        return password;
    }

在方法上指定视图

    /**
     * 获取用户详情,利用正则表达式限定为只接收数字
     * @param id
     * @return
     */
    @GetMapping("/user/{id:\d }")
    @JsonView(User.DetailView.class)
    public User getUserInfo(@PathVariable("id") String id){
        return new User("sico","12345");
    }

由于视图的继承关系,DetailView任然会显示被SimpleView标注的字段

注:spring 对于 RuntimeException 异常才会进行事务回滚。

原理也很简单,Spring Boot 默认提供了程序出错的结果映射路径/error。这个/error请求会在BasicErrorController中处理,其内部是通过判断请求头中的Accept的内容是否为text/html来区分请求是来自客户端浏览器(浏览器通常默认自动发送请求头内容Accept:text/html)还是客户端接口的调用,以此来决定返回页面视图还是 JSON 消息内容。相关BasicErrorController中代码如下:

1.3.5 RequestMapping的变体

RequestMapping有以下变体,他们分别对应了不同的请求方法

  • @GetMapping 对应GET方法
  • @PostMapping 对应POST方法
  • @PutMapping 对应PUT方法
  • @DeleteMapping 对应DELETE方法

2、编写全局异常处理类

澳门新濠3559 12image.png

1.3.5 @RequestBody将请求体映射到java方法参数

@(spring)RequestBody将请求中的请求体中的实体数据转换成实体对象,常用语PUT和POST

    /**
     * 创建用户
     * 仅有加入@RequestBody注解才能解析出请求体重传入的实体数据
     */
    @PutMapping("/user")
    public void create(@RequestBody User user){
        User user1=new User("cocoa","123",1);
    }

创建 MyControllerAdvice.java,如下:

package com.sam.demo.controller;

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;

/**
 * controller 增强器
 *
 * @author sam
 * @since 2017/7/17
 */
@ControllerAdvice
public class MyControllerAdvice {

    /**
     * 全局异常捕捉处理
     * @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;
    }

    /**
     * 拦截捕捉自定义异常 MyException.class
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = MyException.class)
    public Map myErrorHandler(MyException ex) {
        Map map = new HashMap();
        map.put("code", ex.getCode());
        map.put("msg", ex.getMsg());
        return map;
    }

}

好了,了解完Spring Boot默认的错误机制后,我们来点有意思的,浏览器端访问的话,任何错误Spring Boot返回的都是一个Whitelabel Error Page的错误页面,这个很不友好,所以我们可以自定义下错误页面。

1.3.6 @Valid注解和BindingResult验证请求参数的合法性并处理校验结果

一般需要在请求接口中校验请求参数,例如参数是否为空,是否唯一等。

  • @NotBlack 非空注解,将此注解加到实体类属性上。
    @NotBlank
    private String username;

在请求方法的字段上加上@valid注解时,以上的注解将生效。如果请求接口的参数无法通过校验,将返回400

    @PutMapping("/user")
    public void create(@Valid @RequestBody User user){
        User user1=new User("cocoa","123",1);
    }

BindingResult
如果使用@valid注解,当参数不符合标准时。会直接返回400。而不会进入接口方法的方法体。如果需要对没通过校验的请求作一些处理。在使用BindingResult的情况下,如果用户传入的参数不符合约束。则相应的错误信息将会被放置在BindingRsult对象中。从BindingResult对象中取出错误信息:

    @PutMapping("/user")
    public void create(@Valid @RequestBody User user, BindingResult errors){
        if (errors.hasErrors()){
            errors.getAllErrors().stream().forEach(error->logger.error(error.getDefaultMessage()));
        }
        User user1=new User("cocoa","123",1);
    }

如果没有通过非空校验,将包含错误。默认的非空错误信息是:"may not be null",这个错误信息可以自定义。

3、controller中抛出异常进行测试。

@RequestMapping("/home")
public String home() throws Exception {

//        throw new Exception("Sam 错误");
    throw new MyException("101", "Sam 错误");

}

1、先从最简单的开始,直接在/resources/templates下面创建error.html就可以覆盖默认的Whitelabel Error Page的错误页面,我项目用的是thymeleaf模板,对应的error.html代码如下:

1.3.6.1 hibernate validate常用校验注解

澳门新濠3559 13

image

澳门新濠3559 14

image

启动应用,访问: ,正常显示以下json内容,证明自定义异常已经成功被拦截。

{"msg":"Sam 错误","code":"101"}

澳门新濠3559 15image.png

1.3.6.2 获取校验错误信息(包括字段信息)

使用fieldError可以获取错误的字段信息和错误信息

    @PutMapping("/user")
    public void update(@Valid @RequestBody User user,BindingResult errors){
        if (errors.hasErrors()){
            errors.getAllErrors().stream().forEach(error->{
                FieldError fieldError=(FieldError) error;
                String errorMessage=fieldError.getField() " " fieldError.getDefaultMessage();
                logger.error(errorMessage);
            });
        }
        User user1=new User("cocoa","123",1);
    }

* 如果不需要返回json数据,而要渲染某个页面模板返回给浏览器,那么MyControllerAdvice中可以这么实现:

@ExceptionHandler(value = MyException.class)
public ModelAndView myErrorHandler(MyException ex) {
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("error");
    modelAndView.addObject("code", ex.getCode());
    modelAndView.addObject("msg", ex.getMsg());
    return modelAndView;
}
<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <title>Title</title></head><body>动态error错误页面<p th:text="${error}"></p><p th:text="${status}"></p><p th:text="${message}"></p></body></html>
1.3.6.3 自定义校验失败信息

用以上方式虽然能够获得错误字段和错误信息,但过于麻烦。可以在校验注解中指定message值自定义错误信息。如下:

    @NotBlank(message = "用户名不能为空")
    private String username;

在 templates 目录下,添加 error.ftl(这里使用freemarker) 进行渲染:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>错误页面</title>
</head>
<body>
    <h1>${code}</h1>
    <h1>${msg}</h1>
</body>
</html>

这样运行的时候,请求一个不存在的页面或服务端处理发生异常时,展示的自定义错误界面如下:

1.3.6.3 自定义校验逻辑

默认的校验注解能够满足大部分的校验要求,但是依然不能完全满足要求。例如需要校验一个字段是否唯一,就无法通过默认的注解完成。此时需要自定义校验逻辑。可以通过自定义的注解来实现和自定义校验器实现

自定义注解

@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
//java标准校验注解,validateBy指定校验的类
@Constraint(validatedBy = NameUniqueValidator.class )
public @interface NameUnique {

    //校验注解中必须实现以下三个属性
    String message() default "";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default { };
}

自定义校验器

/**
 * 实现ConstraintValidator接口,第一个泛型指定用于标注验证的注解,第二个泛型指定倍标注值得类型
 * 不需要用@component等注解将验证类加入容器。spring会自动将此类加入容器
 */
public class NameUniqueValidator implements ConstraintValidator<NameUnique,Object>{

    //在这个类中可以使用Spring @Autowire注解注入任何需要的对象

    /**
     * 校验器初始化
     * @param nameUnique
     */
    @Override
    public void initialize(NameUnique nameUnique) {

    }

    /**
     * 校验方法
     * @param o 待校验的值
     * @param constraintValidatorContext
     * @return 返回true代表校验成功,false代表校验失败
     */
    @Override
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        //TODO 执行校验逻辑
        return false;
    }
}

重启应用, 显示自定的错误页面内容。

澳门新濠3559 16image.png

1.4 服务异常处理

补充:如果全部异常处理返回json,那么可以使用 @RestControllerAdvice 代替 @ControllerAdvice ,这样在方法上就可以不需要添加 @ResponseBody。

版权声明:本文为博主原创文章,转载请注明出处。

2、此外,如果你想更精细一点,根据不同的状态码返回不同的视图页面,也就是对应的404,500等页面,这里分两种,错误页面可以是静态HTML(即,添加到任何静态资源文件夹下),也可以使用模板构建,文件的名称应该是确切的状态码。

1.4.1 SpringBoot默认的错误处理机制

SpringBoot会自动的处理一些异常。例如访问了一个不存在的页面,当使用浏览器访问时,SpringBoot会返回一个默认的错误页面,如下所示:

澳门新濠3559 17

image

但使用postman访问时,返回如下错误信息:

{
    "timestamp": 1509626392183,
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/12ss"
}

原理:SpringBoot中包含一个BasicErrorController类用于处理错误,处理/error请求。当它检测到请求头中包含text/html的时候,返回一个错误的页面。当没有这个请求头时,返回json格式的错误。如何判断请求是否来自网页?使用注解:@RequestMapping(produces="text/html")
如下:

    @RequestMapping(
        produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView == null ? new ModelAndView("error", model) : modelAndView;
    }

    @RequestMapping
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = this.getStatus(request);
        return new ResponseEntity(body, status);
    }

可以模仿这种处理机制,在同一个url下做出不同的响应。

  • 如果只是静态HTML页面,不带错误信息的,在resources/public/下面创建error目录,在error目录下面创建对应的状态码html即可 ,例如,要将404映射到静态HTML文件,您的文件夹结构如下所示:

    澳门新濠3559 18image.png

1.4.2 自定义异常处理

静态404.html简单页面如下:

1.4.2 自定义返回的浏览器错误页面

自定义返回的浏览器错误页面只需要把相应的html文件放置在resources/resources/error文件夹下即可,404即404.html 500即500.html

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body> 静态404错误页面</body></html>
1.4.3 自定义返回的json格式的错误信息

如果抛出自定义的异常,SpringBoot默认处理如下所示:

{
    "timestamp": 1509629240633,
    "status": 500,
    "error": "Internal Server Error",
    "exception": "com.sicosola.security.demo.exception.ServiceException",
    "message": "用户不存在",
    "path": "/user/1"
}

自定义异常返回格式
可以创建一个全局的控制器的错误处理器,从控制器抛出的异常都会在此处被拦截。可以在此处对它进行处理,首先自定义一个异常:

public class ServiceException extends RuntimeException{

    private Integer code;

    private String desc;

    public ServiceException(Integer code, String desc) {
        super(desc);
        this.code = code;
        this.desc = desc;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

定义一个controller全局异常处理器,处理异常

/**
 * 控制器错误处理器,从控制器抛出的异常被它拦截。
 * 可以在此处封装错误信息,以友好的方式返回给前端
 */

@ControllerAdvice
public class ControllerExceptionHandler {

    /**
     * 处理ServiceException
     * @return
     */
    @ExceptionHandler(ServiceException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Map<String,Object> HandlerServiceException(ServiceException e){
        Map<String,Object> errorMessage=new HashMap<>();
        errorMessage.put("code",e.getCode());
        errorMessage.put("desc",e.getDesc());
        return errorMessage;
    }

}

这样访问一个错误路径的时候,就会显示静态404错误页面错误页面

1.5 Restful api拦截

一般来说,可以使用以下机制来拦截

  • 过滤器 (Filter)
  • 拦截器 (Interceptor)
  • 切片 (Aspect)

澳门新濠3559 19image.png

1.5.1 使用Filter

使用Filter仅需实现一个filter,将其加入容器即可。
在SpringBoot中,如何将不可更改源码的第三方Filter加入Spring容器中?
可以利用在配置类中利用FilterRegistrationBean将第三方过滤器注册到Spring

    /*
    用以下方式将第三方容器注册到Spring
     */
    @Bean
    public FilterRegistrationBean timeFilter(){
        FilterRegistrationBean registrationBean=new FilterRegistrationBean();
        //假设这是第三方容器
        TimeFilter filter=new TimeFilter();
        registrationBean.setFilter(filter);
        //可以声明这个filter在哪些路径起作用
        List<String> urls=new ArrayList<>();
        urls.add("/*");
        registrationBean.setUrlPatterns(urls);
        return registrationBean;
    }

使用Filter的缺陷
filter是由JavaEE提供的功能,它只能获取Http请求和Http响应的信息。无法知晓具体的业务是由某个控制器和某个方法完成的。

注:这时候如果存在上面第一种介绍的error.html页面,则状态码错误页面将覆盖error.html,具体状态码错误页面优先级比较高。

1.5.2 拦截器

拦截器是由Spring框架提供的功能,可以弥补Filter的不足

自定义Interceptor实现HandlerInterceptor.实现其处理方法后,在configuration中配置。
需要使配置类继承WebMvcConfigurerAdapter并覆盖其addInterceptors方法。

  • 如果是动态模板页面,可以带上错误信息,在resources/templates/下面创建error目录,在error目录下面命名即可:澳门新濠3559 20image.png
1.5.2.1 实现一个拦截器
    /**
 * 记录服务调用时间的拦截器
 */
@Component
public class TimeInterceptor implements HandlerInterceptor{

    private Logger logger= LoggerFactory.getLogger(getClass());

    /**
     * 处理前
     * @param httpServletRequest
     * @param httpServletResponse
     * @param handler 此参数记录了处理对象,包括类名和方法名等信息
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
        //设置开始时间
        httpServletRequest.setAttribute("startTime",new Date().getTime());
        //获取当前拦截接口处理类(Controller)
        logger.error(((HandlerMethod)handler).getBean().getClass().getName());
        //获取当前拦截接口的处理方法
        logger.error(((HandlerMethod)handler).getMethod().getName());
        //只有返回true才会执行后面的方法
        return true;
    }

    /**
     * 接口成功返回后,如果调用控制器方法时控制器方法抛出异常。则post方法不会被调用
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        long startTime= (long) httpServletRequest.getAttribute("startTime");
        logger.error("TimeInterceptor耗时:" (new Date().getTime()-startTime));
    }

    /**
     * 处理完成,无论控制器方法成功与否。都会进入这个方法
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o
     * @param e
     * @throws Exception,当控制器方法抛出异常时,此exception有值,如果有全局异常处理器(参考ControllerExceptionHandler)它将拿不到异常对象
     */
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        long startTime= (long) httpServletRequest.getAttribute("startTime");
        logger.error("TimeInterceptor耗时:" (new Date().getTime()-startTime));
    }
}

这里我们模拟下500错误,控制层代码,模拟一个除0的错误:

1.5.2.2 将拦截器注册到Spring
@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter{


    @Autowired
    TimeInterceptor timeInterceptor;

    /**
     * 此类继承自 WebMvcConfigureAdapter
      * @param registry 拦截器注册器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //将timeInterceptor注册
        registry.addInterceptor(timeInterceptor);
    }
}    
@Controller public class BaseErrorController extends AbstractController{ private Logger logger = LoggerFactory.getLogger(this.getClass; @RequestMapping(value="/ex") @ResponseBody public String error(){ int i=5/0; return "ex"; } } 

1.5.3 切面

拦截器能拦截请求并且能够获取到处理请求的控制器与方法。但是它依然无法拿到参数中的值。如果想获取参数的值,就需要使用切面。切片是Spring框架的核心功能之一。

要使用AOP,首先需要定义一个切面(切面中定义了处理的逻辑),此处声明一个切片名为TimeAspect。在声明一个切入点(切入点约定切片在哪些方法上起作用,在什么时候上起作用)
切入点常用的注解(约定在什么时候起作用)

  • @before 此注解约定切入点在目标方法执行前执行
  • @after 此注解约定切入点在目标方法执行后执行
  • @afterthrow 在方法抛出异常时调用
  • @around 覆盖了前3种,常用

在什么方法上起作用
在什么方法上起作用是用一个表达式指定的。

    //此注解声明类为切面
@Aspect
@Component
public class TimeAspect {

    private Logger logger= LoggerFactory.getLogger(getClass());

    /**
     * 定义切入点,
     * 第一个*表示任何返回值,第二个表示任何方法最后表示任何参数
     * @param joinPoint 此对象中包含了被切入方法的信息
     * @return
     */
    @Around("execution(* com.sicosola.security.demo.web.controller.UserController.*(..))")
    public Object handlerControllerMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.error("Time aspect start !");
        //获取被切入方法的参数
        Object[] args = joinPoint.getArgs();
        for (Object arg:args){
            logger.error("args is " arg);
        }
        long startTime=new Date().getTime();
        //执行目标方法,返回目标方法的返回值
        Object o = joinPoint.proceed();

        logger.error("耗时:" (new Date().getTime()-startTime));
        return o;
    }
}

500.html代码:

1.6 异步处理Rest服务

使用异步处理服务可以提高服务器的吞吐量,并且这种异步的处理对客户端是透明的。
在传统的同步模式下,所有的请求都在主线程中完成。Tomcat管理的线程是有最大数量的,当达到最大数量时。其它的请求就需要等待。而异步线程使用副线程,当请求发送到主线程时。主线程将任务交给副线程,主线程又可以继续接收请求。

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> 动态500错误页面 <p th:text="${error}"></p> <p th:text="${status}"></p> <p th:text="${message}"></p> </body> </html> 

1.6.1 使用Runable异步处理Rest服务

使用Callable单开一个线程执行任务,Callable是由java并发包提供的机制。

    @RequestMapping("/order")
    public Callable<String> Order() throws InterruptedException {
        logger.info("主线程开始");
        //使用Callable单开一个线程处理
        Callable<String> result=new Callable<String>() {
            @Override
            public String call() throws Exception {
                logger.info("处理线程开始");
                Thread.sleep(1000);
                logger.info("处理线程结束");
                return "success";

            }
        };

        Thread.sleep(1000);
        logger.info("主线程返回");
        return result;
    }

可以看到如下的日志:

2017-11-03 09:56:39.592  INFO 5148 --- [nio-8080-exec-1] c.s.s.demo.web.async.AsyncController     : 主线程开始
2017-11-03 09:56:40.592  INFO 5148 --- [nio-8080-exec-1] c.s.s.demo.web.async.AsyncController     : 主线程返回
2017-11-03 09:56:40.603  INFO 5148 --- [      MvcAsync1] c.s.s.demo.web.async.AsyncController     : 处理线程开始
2017-11-03 09:56:41.603  INFO 5148 --- [      MvcAsync1] c.s.s.demo.web.async.AsyncController     : 处理线程结束

可以看到处理业务实在副线程MvcAsync中打印出来的。根据日志可以看出,主线程几乎没有任何停顿就立即返回。

这时访问 即可看到如下错误,说明确实映射到了500.html

1.6.2 使用DeffrredResult异步处理Rest服务。

Runable并不能满足所有的场景,有时候可能使用消息队列在不同的服务器之间完成异步。使用Runable机制就不会有明显的效果。如下

澳门新濠3559 21

image

此时需要使用DeffrredResult处理。它可以在两个不同的线程之间来传递。其大致处理流程如下:

  • 创建一个DeferredResultHolder
    @Component
public class DeferredResultHolder {

    //key代表订单号,value代表处理结果
    private Map<String,DeferredResult<String>> map=new HashMap<>();

    public Map<String, DeferredResult<String>> getMap() {
        return map;
    }

    public void setMap(Map<String, DeferredResult<String>> map) {
        this.map = map;
    }
}
  • 控制器方法接收到请求发送到消息队列,并创建一个DiferedResult,以订单号为key,result为value放到holder的map中。
  • 处理成功的消息监听器在收到处理结果后从holder中取出对应的DiferedResult并设置值。一旦该result被设置值就会异步返回。

最需要理解的是Holder,Holder只是作为一个容器保存了待接受值的所有diferredResult对象。Holder就作为两个不同线程之间的通信桥梁

澳门新濠3559 22image.png

1.6.3 异步处理配置

SpringWebMvcConfig中有个configureAsyncSupport方法,可以用此方法进行异步配置。可以在此配置类中注册异步拦截器,设置异步请求默认超时时间。设置自定义线程池。

    /**
     * 配置异步处理
     * @param configurer
     */
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        //注册异步拦截器,此拦截器
        //configurer.registerCallableInterceptors();
        //configurer.registerDeferredResultInterceptors();
        //设置异步请求的默认超时时间
        configurer.setDefaultTimeout(10);
        //自定义线程池替代Spring默认的线程池
       // configurer.setTaskExecutor();
    }

注:如果同时存在静态页面500.html和动态模板的500.html,则后者覆盖前者。即templates/error/这个的优先级比resources/public/error高。

2 SpringBoot中的配置信息封装

Spring Boot中一般会在resources目录下使用.properties文件或者.yml文件进行一些系统的配置。我们可以自定义自己的配置逻辑。自定义配置并在系统中读取配置。

首先利用@ConfigurationProperties(prefix="---")声明配置类,其中prefix是配置前缀。
然后利用@EnableConfigurationProperties使配置类起作用。参考:

public class BrowserProperties {

    private String loginPage;

    public String getLoginPage() {
        return loginPage;
    }

    public void setLoginPage(String loginPage) {
        this.loginPage = loginPage;
    }
}

/**
 * sico-security框架配置积累
 */

@ConfigurationProperties(prefix = "sico.security")
public class SecurityProperties {

    private BrowserProperties browser=new BrowserProperties();

    public BrowserProperties getBrowser() {
        return browser;
    }

    public void setBrowser(BrowserProperties browser) {
        this.browser = browser;
    }
}

@Configuration
//SecurityProperties配置读取器生效
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityCoreConfig {

}

需要特别注意的是配置类中的属性名必须和配置项的名称完全相同,否则将无法正常读取

整体概括上面几种情况,如下:

  • error.html会覆盖默认的 whitelabel Error Page 错误提示
  • 静态错误页面优先级别比error.html高
  • 动态模板错误页面优先级比静态错误页面高

3、上面介绍的只是最简单的覆盖错误页面的方式来自定义,如果对于某些错误你可能想特殊对待,则可以这样

@Configuration public class ContainerConfig { @Bean public EmbeddedServletContainerCustomizer containerCustomizer(){ return new EmbeddedServletContainerCustomizer(){ @Override public void customize(ConfigurableEmbeddedServletContainer container) { container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500")); } }; } } 

上面这段代码中HttpStatus.INTERNAL_SERVER_ERROR就是对应500错误码,也就是说程序如果发生500错误,就会将请求转发到/error/500这个映射来,那我们只要实现一个方法是对应这个/error/500映射即可捕获这个异常做出处理

@RequestMapping("/error/500") @ResponseBody public String showServerError() { return "server error"; } 

这样,我们再请求前面提到的异常请求 的时候,就会被我们这个方法捕获了。

澳门新濠3559 23image.png

这里我们就只对500做了特殊处理,并且返还的是字符串,如果想要返回视图,去掉 @ResponseBody注解,并返回对应的视图页面。如果想要对其他状态码自定义映射,在customize方法中添加即可。

上面这种方法虽然我们重写了/500映射,但是有一个问题就是无法获取错误信息,想获取错误信息的话,我们可以继承BasicErrorController或者干脆自己实现ErrorController接口,除了用来响应/error这个错误页面请求,可以提供更多类型的错误格式等(BasicErrorController在上面介绍SpringBoot默认异常机制的时候有提到)

这里博主选择直接继承BasicErrorController,然后把上面 /error/500映射方法添加进来即可

@Controllerpublic class MyBasicErrorController extends BasicErrorController { public MyBasicErrorController() { super(new DefaultErrorAttributes(), new ErrorProperties; } /** * 定义500的ModelAndView * @param request * @param response * @return */ @RequestMapping(produces = "text/html",value = "/500") public ModelAndView errorHtml500(HttpServletRequest request,HttpServletResponse response) { response.setStatus(getStatus.value; Map<String, Object> model = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML)); model.put("msg","自定义错误信息"); return new ModelAndView("error/500", model); } /** * 定义500的错误JSON信息 * @param request * @return */ @RequestMapping(value = "/500") @ResponseBody public ResponseEntity<Map<String, Object>> error500(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML)); HttpStatus status = getStatus; return new ResponseEntity<Map<String, Object>>(body, status); }}

代码也很简单,只是实现了自定义的500错误的映射解析,分别对浏览器请求以及json请求做了回应。

BasicErrorController默认对应的@RequestMapping是/error,固我们方法里面对应的@RequestMapping(produces = "text/html",value = "/500")实际上完整的映射请求是/error/500,这就跟上面 customize 方法自定义的映射路径对上了。

errorHtml500 方法中,我返回的是模板页面,对应/templates/error/500.html,这里顺便自定义了一个msg信息,在500.html也输出这个信息<p th:text="${msg}"></p>,如果输出结果有这个信息,则表示我们配置正确了。

再次访问请求 ,结果如下

澳门新濠3559 24image.png

Spring Boot提供的ErrorController是一种全局性的容错机制。此外,你还可以用@ControllerAdvice注解和@ExceptionHandler注解实现对指定异常的特殊处理。

这里介绍两种情况:

  • 局部异常处理 @Controller @ExceptionHandler
  • 全局异常处理 @ControllerAdvice @ExceptionHandler

局部异常处理 @Controller @ExceptionHandler

局部异常主要用到的是@ExceptionHandler注解,此注解注解到类的方法上,当此注解里定义的异常抛出时,此方法会被执行。如果@ExceptionHandler所在的类是@Controller,则此方法只作用在此类。如果@ExceptionHandler所在的类带有@ControllerAdvice注解,则此方法会作用在全局。

该注解用于标注处理方法处理那些特定的异常。被该注解标注的方法可以有以下任意顺序的参数类型:

  • Throwable、Exception 等异常对象;

  • ServletRequest、HttpServletRequest、ServletResponse、HttpServletResponse;

  • HttpSession 等会话对象;

  • org.springframework.web.context.request.WebRequest;

  • java.util.Locale;

  • java.io.InputStream、java.io.Reader;

  • java.io.OutputStream、java.io.Writer;

  • org.springframework.ui.Model;

并且被该注解标注的方法可以有以下的返回值类型可选:

  • ModelAndView;

  • org.springframework.ui.Model;

  • java.util.Map;

  • org.springframework.web.servlet.View;

  • @ResponseBody 注解标注的任意对象;

  • HttpEntity<?> or ResponseEntity<?>;

  • void;

以上罗列的不完全,更加详细的信息可参考:Spring ExceptionHandler。

举个简单例子,这里我们对除0异常用@ExceptionHandler来捕捉。

@Controllerpublic class BaseErrorController extends AbstractController{ private Logger logger = LoggerFactory.getLogger(this.getClass; @RequestMapping(value="/ex") @ResponseBody public String error(){ int i=5/0; return "ex"; } //局部异常处理 @ExceptionHandler(Exception.class) @ResponseBody public String exHandler(Exception e){ // 判断发生异常的类型是除0异常则做出响应 if(e instanceof ArithmeticException){ return "发生了除0异常"; } // 未知的异常做出响应 return "发生了未知异常"; }} 

澳门新濠3559 25image.png

全局异常处理 @ControllerAdvice @ExceptionHandler

在spring 3.2中,新增了@ControllerAdvice 注解,可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute,并应用到所有@RequestMapping中。

简单的说,进入Controller层的错误才会由@ControllerAdvice处理,拦截器抛出的错误以及访问错误地址的情况@ControllerAdvice处理不了,由SpringBoot默认的异常处理机制处理。

我们实际开发中,如果是要实现RESTful API,那么默认的JSON错误信息就不是我们想要的,这时候就需要统一一下JSON格式,所以需要封装一下。

/*** 返回数据*/public class AjaxObject extends HashMap<String, Object> { private static final long serialVersionUID = 1L; public AjaxObject() { put("code", 0); } public static AjaxObject error() { return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员"); } public static AjaxObject error(String msg) { return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg); } public static AjaxObject error(int code, String msg) { AjaxObject r = new AjaxObject(); r.put("code", code); r.put("msg", msg); return r; } public static AjaxObject ok(String msg) { AjaxObject r = new AjaxObject(); r.put("msg", msg); return r; } public static AjaxObject ok(Map<String, Object> map) { AjaxObject r = new AjaxObject(); r.putAll; return r; } public static AjaxObject ok() { return new AjaxObject(); } public AjaxObject put(String key, Object value) { super.put(key, value); return this; } public AjaxObject data(Object value) { super.put("data", value); return this; } public static AjaxObject apiError(String msg) { return error; }}

上面这个AjaxObject就是我平时用的,如果是正确情况返回的就是:

{ code:0, msg:“获取列表成功”, data:{ queryList :[] }}

正确默认code返回0,data里面可以是集合,也可以是对象,如果是异常情况,返回的json则是:

{ code:500, msg:“未知异常,请联系管理员”}

然后创建一个自定义的异常类:

public class BusinessException extends RuntimeException implements Serializable { private static final long serialVersionUID = 1L; private String msg; private int code = 500; public BusinessException(String msg) { super; this.msg = msg; } public BusinessException(String msg, Throwable e) { super; this.msg = msg; } public BusinessException(int code,String msg) { super; this.msg = msg; this.code = code; } public BusinessException(String msg, int code, Throwable e) { super; this.msg = msg; this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public int getCode() { return code; } public void setCode { this.code = code; }}

注:spring 对于 RuntimeException 异常才会进行事务回滚

Controler中添加一个json映射,用来处理这个异常

@Controllerpublic class BaseErrorController{ @RequestMapping public void json(ModelMap modelMap) { System.out.println(modelMap.get); int i=5/0; }}

最后创建这个全局异常处理类:

/** * 异常处理器 */@RestControllerAdvicepublic class BusinessExceptionHandler { private Logger logger = LoggerFactory.getLogger(getClass; /** * 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器 * @param binder */ @InitBinder public void initBinder(WebDataBinder binder) { System.out.println("请求有参数才进来"); } /** * 把值绑定到Model中,使全局@RequestMapping可以获取到该值 * @param model */ @ModelAttribute public void addAttributes(Model model) { model.addAttribute("author", "嘟嘟MD"); } @ExceptionHandler(Exception.class) public Object handleException(Exception e,HttpServletRequest req){ AjaxObject r = new AjaxObject(); //业务异常 if(e instanceof BusinessException){ r.put("code", ((BusinessException) e).getCode; r.put("msg", ((BusinessException) e).getMsg; }else{//系统异常 r.put("code","500"); r.put("msg","未知异常,请联系管理员"); } //使用HttpServletRequest中的header检测请求是否为ajax, 如果是ajax则返回json, 如果为非ajax则返回view(即ModelAndView) String contentTypeHeader = req.getHeader("Content-Type"); String acceptHeader = req.getHeader; String xRequestedWith = req.getHeader("X-Requested-With"); if ((contentTypeHeader != null && contentTypeHeader.contains("application/json")) || (acceptHeader != null && acceptHeader.contains("application/json")) || "XMLHttpRequest".equalsIgnoreCase(xRequestedWith)) { return r; } else { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("msg", e.getMessage; modelAndView.addObject("url", req.getRequestURL; modelAndView.addObject("stackTrace", e.getStackTrace; modelAndView.setViewName; return modelAndView; } }}

@ExceptionHandler 拦截了异常,我们可以通过该注解实现自定义异常处理。其中,@ExceptionHandler 配置的 value 指定需要拦截的异常类型,上面我配置了拦截Exception,再根据不同异常类型返回不同的相应,最后添加判断,如果是Ajax请求,则返回json,如果是非ajax则返回view,这里是返回到error.html页面。

为了展示错误的时候更友好,我封装了下error.html,不仅展示了错误,还添加了跳转百度谷歌以及StackOverFlow的按钮,如下:

<!DOCTYPE HTML><html xmlns:th="http://www.thymeleaf.org" layout:decorator="layout"><head> <title>Spring Boot管理后台</title> <script type="text/javascript"> </script></head><body><div layout:fragment="content" th:remove="tag"> <div > <h1>系统异常统一处理</h1> <h3 th:text="'错误信息:' ${msg}"></h3> <h3 th:text="'请求地址:' ${url}"></h3> <h2>Debug</h2> <a th:href="@{'https://www.google.com/webhp?hl=zh-CN#safe=strict&hl=zh-CN&q=' ${msg}}" target="_blank" >Google</a> <a th:href="@{'https://www.baidu.com/s?wd=' ${msg}}" target="_blank" >Baidu</a> <a th:href="@{'http://stackoverflow.com/search?q=' ${msg}}" target="_blank" >StackOverFlow</a> <h2>异常堆栈跟踪日志StackTrace</h2> <div th:each="line:${stackTrace}"> <div th:text="${line}"></div> </div> </div></div><div layout:fragment="js" th:remove="tag"></div></body></html>

访问

澳门新濠3559 26image.png

如果是ajax请求,返回的就是错误:

{ "msg":"未知异常,请联系管理员", "code":500 }

这里我给带@ModelAttribute注解的方法通过Model设置了author值,在json映射方法中通过 ModelMwap 获取到改值。

认真的你可能发现,全局异常类我用的是@RestControllerAdvice,而不是@ControllerAdvice,因为这里返回的主要是json格式,这样可以少写一个@ResponseBody。

到此,SpringBoot中对异常的使用也差不多全了,本项目中处理异常的顺序会是这样,当发送一个请求:

  • 拦截器那边先判断是否登录,没有则返回登录页。
  • 在进入Controller之前,譬如请求一个不存在的地址,返回404错误界面。
  • 在执行@RequestMapping时,发现的各种错误(譬如数据库报错、请求参数格式错误/缺失/值非法等)统一由@ControllerAdvice处理,根据是否Ajax返回json或者view。

想要查看更多Spring Boot干货教程,可前往:Spring Boot干货系列总纲

↗[相关示例完整代码]

  • chapter13==》Spring Boot干货系列:Spring Boot全局异常处理整理

一直觉得自己写的不是技术,而是情怀,一篇篇文章是自己这一路走来的痕迹。靠专业技能的成功是最具可复制性的,希望我的这条路能让你少走弯路,希望我能帮你抹去知识的蒙尘,希望我能帮你理清知识的脉络,希望未来技术之巅上有你也有我。

编辑:编程 本文来源:POST方法 而在resultful风格下以资源为导向,展现不

关键词: 澳门新濠3559