SpringBoot默认的错误处理机制:
一、测试需要的部分代码
(完整代码放在了github https://github.com/lhy1234/spring-security):
UserController:只对新增用户做测试,省略其他代码
@RestController @RequestMapping("/user") public class UserController { /** * 创建 * @Description: * //@RequestBody:json映射到java * @Valid 和User类上的@NotBlank注解一起做校验 * BindingResult存储的是校验错误信息 * @param @param user * @param @return * @return User * @throws * @author lihaoyang * @date 2018年2月24日 */ @PostMapping public User create(@Valid @RequestBody User user){ //,BindingResult errors 先屏蔽BindingResult让springboot做错误处理,不屏蔽就能正常响应200了 // if(errors.hasErrors()){ // errors.getAllErrors().stream() // .forEach(error -> System.err.println(error.getDefaultMessage())); // } user.setId("1"); System.err.println(user); return user; }
//省略其他代码。。。 }
User类:使用valid注解校验
public class User { public interface UserSimpleView {}; public interface UserDetailView extends UserSimpleView{}; //继承 private String id; @NotBlank(message="用户名不能为空") private String username; @NotBlank(message="密码不能为空") private String password; @Past(message="生日只能是过去的日期") private Date birthday; //省略get set }
一、对于进入controller之前就被打回的错误处理
1、对于url不存在的400错误处理
springboot对浏览器和app的请求,处理是不一样的:
1.1,使用浏览器,响应式的body一段html:
使用Springboot,启动服务,使用浏览器访问一个不存在的url后,出现html空白页:
浏览器发出的请求:请求头Content-Type:text/html;
1.2,使用chrome插件restlet client(https://restlet.com/modules/client/)模拟app访问:
使用restlet client模拟app访问不存在的url,响应体body是一个json:
图一
从上图可以看出,使用app发出的请求请求头是contentType是json类型
springboot错误处理类控制器 BasicErrorController代码片段:
这个控制器处理的url是 /error ,但是会根据请求头里类型的不同,做不同的处理,浏览器发的请求走上边的,返回时html;app发的请求,走下边的方法,返回是一个json。
这里提供了一种机制:同一个url,不同的情况做不同的处理。
上边是对url不存在的400错误的处理,从图一可以看出,springboot的响应信息很简单:
错误码 400,加上响应体body:
{
"timestamp": 1519608427470,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/xxx"
}
2,对于不符合校验规则的错误处理
User类的username和password是不能为空的,使用restlet client发出post请求,请求体为{},空的json,这样就不符合校验规则了(host是变量127.0.0.1):
响应头:
响应体:
会吧错误信息返回。
上边这两种情况,都是在进入controller之前就被spring打回的,一般我们需要在进入controller方法后,调用service过程中有异常来进行处理,下边模拟这种情况:
二、进入controller方法后发生了异常
在getInfo里直接抛出运行时异常模拟调用service发生错误:
/** * 详情 * @Description: TODO * @param @param id * @param @return * @return User * @throws * @author lihaoyang * @date 2018年2月24日 */ @GetMapping("{id:\d+}") //{}里可以是正则,匹配数字 // @GetMapping("detail/{id}") @JsonView(User.UserDetailView.class) public User getInfo(@PathVariable(value="id",required=true) String id){ throw new RuntimeException("query deltail interface has error!"); // System.err.println(id); // User user = new User(); // user.setUsername("tom"); // user.setPassword("123456"); // user.setId("1"); // return user; }
使用浏览器访问:
对于浏览器也可以自定义错误页面,在src/main/resources下新建resources/error目录,可以自定义错误页面,这个对于app访问是无效的,app还是返回json
模拟app访问:错误中只包含抛出的异常信息
大部分情况,这种springboot对rest服务做的错误处理已经够用了。
如果还不够用也可以自己定义错误处理机制:
三、自定义异常
自定义异常:
package com.imooc.exception; /** * 用户不存在异常 * ClassName: UserNotExistException * @Description: TODO * @author lihaoyang * @date 2018年2月26日 */ public class UserNotExistException extends RuntimeException{ /** * @Fields serialVersionUID : TODO */ private static final long serialVersionUID = 1L; private String id; public UserNotExistException(String id){ super("user not exist!"); this.id = id; } public String getId() { return id; } public void setId(String id) { this.id = id; } }
controller方法:
@GetMapping("{id:\d+}") //{}里可以是正则,匹配数字 // @GetMapping("detail/{id}") @JsonView(User.UserDetailView.class) public User getInfo(@PathVariable(value="id",required=true) String id){ // throw new RuntimeException("query deltail interface has error!"); throw new UserNotExistException(id); // System.err.println(id); // User user = new User(); // user.setUsername("tom"); // user.setPassword("123456"); // user.setId("1"); // return user; }
此时浏览器访问,返回自定义的错误页
区别就在于app访问,exception类已经是自定义的了,但是异常信息message还不够详细,下面介绍怎么处理
统一异常处理类:
package com.imooc.web.controller; import java.util.HashMap; import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import com.imooc.exception.UserNotExistException; /** * @ControllerAdvice:处理其他controller抛出的异常,都会到这里处理 */ @ControllerAdvice public class ControllerExceptionHandler { @ExceptionHandler(UserNotExistException.class)//处理哪个异常类 @ResponseBody //返回json @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//状态码 500 public Map<String,Object> handlerUserNotExistException(UserNotExistException ex){ Map<String,Object> result = new HashMap<>(); //通过ex对象拿到异常信息 result.put("id", ex.getId()); result.put("message", ex.getMessage()); return result; } }
app访问,可以打印自己想要的异常信息。