提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
日常工作中,我们开发接口时,一般都会涉及到参数校验、异常处理、封装结果返回等处理。如果每个后端开发在参数校验、异常处理等都是各写各的,没有统一处理的话,代码就不优雅,也不容易维护。所以,作为一名合格的后端开发工程师,我们需要统一校验参数,统一异常处理、统一结果返回,让代码更加规范、可读性更强、更容易维护。
@RestController
@RequestMapping
public class UserController {
@RequestMapping("addUser")
public String addUser(UserParam userParam) {
if (StringUtils.isEmpty(userParam.getUserName())) {
return "用户名不能为空";
}
if (StringUtils.isEmpty(userParam.getPhone())) {
return "手机号不能为空";
}
if (userParam.getPhone().length() > 11) {
return "手机号不能超过11";
}
if (StringUtils.isEmpty(userParam.getEmail())) {
return "邮箱不能为空";
}
//省略其他参数校验
//todo 插入用户信息表
return "SUCCESS";
}
}
@RequestMapping("editUser")
public String editUser(UserParam userParam) {
if (StringUtils.isEmpty(userParam.getUserName())) {
return "用户名不能为空";
}
if (StringUtils.isEmpty(userParam.getPhone())) {
return "手机号不能为空";
}
if (userParam.getPhone().length() > 11) {
return "手机号不能超过11";
}
if (StringUtils.isEmpty(userParam.getEmail())) {
return "邮箱不能为空";
}
//省略其他参数校验
//todo 编辑用户信息表
return "SUCCESS";
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
引入包后,参数校验就非常简洁啦,如下:
public class UserParam {
@NotNull(message = "用户名不能为空")
private String userName;
@NotNull(message = "手机号不能为空")
@Max(value = 11)
private String phone;
@NotNull(message = "邮箱不能为空")
private String email;
然后在UserParam参数对象中,加入@Validated注解哈,把错误信息接收到BindingResult对象,代码如下:
@RequestMapping("addUser")
public String addUser(@Validated UserParam userParam, BindingResult result) {
List<FieldError> fieldErrors = result.getFieldErrors();
if (!fieldErrors.isEmpty()) {
return fieldErrors.get(0).getDefaultMessage();
}
//todo 插入用户信息表
return "SUCCESS";
}
@RequestMapping("/hello")
public String getStr(){
return "hello,捡田螺的小男孩";
}
//返回
hello,捡田螺的小男孩
@RequestMapping("queryUser")
public UserVo queryUser(String userId) {
return new UserVo("666", "捡田螺的小男孩");
}
//返回:
{"userId":"666","name":"捡田螺的小男孩"}
所以作为后端开发,我们项目的响应结果,需要统一标准的返回格式。一般一个标准的响应报文对象,都有哪些属性呢?
code :响应状态码
message :响应结果描述
data:返回的数据
响应状态码一般用枚举表示:
public enum CodeEnum {
/**操作成功**/
SUCCESS("0000","操作成功"),
/**操作失败**/
ERROR("9999","操作失败"),;
/**
* 自定义状态码
**/
private String code;
/**自定义描述**/
private String message;
CodeEnum(String code, String message){
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
因为返回的数据类型不是确定的,我们可以使用泛型,如下:
/**
* @author 捡田螺的小男孩
* @param
*/
public class BaseResponse<T> {
/**
* 响应状态码(0000表示成功,9999表示失败
*/
private String code;
/**
* 响应结果描述
*/
private String message;
/**
* 返回的数据
*/
private T data;
/**
* 成功返回
* @param data
* @param
* @return
*/
public static <T> BaseResponse<T> success(T data) {
BaseResponse<T> response= new BaseResponse<>();
response.setCode(CodeEnum.SUCCESS.getCode());
response.setMessage(CodeEnum.SUCCESS.getMessage());
response.setData(data);
return response;
}
/**
* 失败返回
* @param code
* @param message
* @param
* @return
*/
public static <T> BaseResponse<T> fail(String code, String message) {
BaseResponse<T> response = new BaseResponse<>();
response.setCode(code);
response.setMessage(message);
return response;
}
public void setCode(String code) {
this.code = code;
}
public void setMessage(String message) {
this.message = message;
}
public void setData(T data) {
this.data = data;
}
}
有了统一的响应体,我们就可以优化一下controller 层的代码啦:
@RequestMapping("/hello")
public BaseResponse<String> getStr(){
return BaseResponse.success("hello,捡田螺的小男孩");
}
//output
{"code":"0000","message":"操作成功","data":"hello,捡田螺的小男孩"}
@RequestMapping("queryUser")
public BaseResponse<UserVo> queryUser(String userId) {
return BaseResponse.success(new UserVo("666", "捡田螺的小男孩"));
}
//output
{"code":"0000","message":"操作成功","data":{"userId":"666","name":"捡田螺的小男孩"}}
日常开发中,我们一般都是自定义统一的异常类,如下:
public class BizException extends RuntimeException {
private String retCode;
private String retMessage;
public BizException() {
super();
}
public BizException(String retCode, String retMessage) {
this.retCode = retCode;
this.retMessage = retMessage;
}
public String getRetCode() {
return retCode;
}
public String getRetMessage() {
return retMessage;
}
}
在controller 层,很可能会有类似代码:
@RequestMapping("/query")
public BaseResponse<UserVo> queryUserInfo(UserParam userParam) {
try {
return BaseResponse.success(userService.queryUserInfo(userParam));
} catch (BizException e) {
//doSomething
} catch (Exception e) {
//doSomething
}
return BaseResponse.fail(CodeEnum.ERROR.getCode(),CodeEnum.ERROR.getMessage());
}
这块代码,没什么问题哈,但是如果try…catch太多,不是很优雅。
可以借助注解@RestControllerAdvice,让代码更优雅。@RestControllerAdvice是一个应用于Controller层的切面注解,它一般配合@ExceptionHandler注解一起使用,作为项目的全局异常处理。我们来看下demo代码哈。
还是原来的UserController,和一个会抛出异常的userService的方法,如下:
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/query")
public BaseResponse<UserVo> queryUserInfo1(UserParam userParam) {
return BaseResponse.success(userService.queryUserInfo(userParam));
}
}
@Service
public class UserServiceImpl implements UserService {
//抛出异常
@Override
public UserVo queryUserInfo(UserParam userParam) throws BizException {
throw new BizException("6666", "测试异常类");
}
}
我们再定义一个全局异常处理器,用@RestControllerAdvice注解,如下:
@RestControllerAdvice(annotations = RestController.class)
public class ControllerExceptionHandler {
}
我们有想要拦截的异常类型,比如想拦截BizException类型,就新增一个方法,使用@ExceptionHandler注解修饰,如下:
@RestControllerAdvice(annotations = RestController.class)
public class ControllerExceptionHandler {
@ExceptionHandler(BizException.class)
@ResponseBody
public BaseResponse<Void> handler(BizException e) {
System.out.println("进入业务异常"+e.getRetCode()+e.getRetMessage());
return BaseResponse.fail(CodeEnum.ERROR.getCode(), CodeEnum.ERROR.getMessage());
}
}
1.为了写出更优雅、更简洁、更容易维护的代码,我们需要统一参数校验、统一响应对象返回、统一异常处理
2.参数校验更简洁,可以使用注解实现。
3.如何统一响应对象返回,一般要包括状态码、描述信息、返回数据。
4.Controller层如何统一全局异常处理?@RestControllerAdvice+@ExceptionHandler
5.进阶篇?大家可以自己实现自定义注解哈,也建议去看看@RestControllerAdvice实现原理,它其实就是一个切面注解,看下它的源码即可。