Java SpringBoot
在项目开发中,接口与接口之间、前后端之间的数据传输都使用 JSON 格式。

1、fastjson使用

阿里巴巴的 fastjson是目前应用最广泛的JSON解析框架。这里也使用fastjson。

1.1 引入依赖

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>fastjson</artifactId>
  4. <version>1.2.35</version>
  5. </dependency>

2、统一封装返回数据

在web项目中,接口返回数据一般要包含状态码、信息、数据等,例如下面的接口示例:

  1. import com.alibaba.fastjson.JSONObject;
  2. import org.springframework.web.bind.annotation.RequestMapping;
  3. import org.springframework.web.bind.annotation.RequestMethod;
  4. import org.springframework.web.bind.annotation.RestController;
  5. @RestController
  6. @RequestMapping(value = "/test", method = RequestMethod.GET)
  7. public class TestController {
  8. @RequestMapping("/json")
  9. public JSONObject test() {
  10. JSONObject result = new JSONObject();
  11. try {
  12. // 业务逻辑代码
  13. result.put("code", 0);
  14. result.put("msg", "操作成功!");
  15. result.put("data", "测试数据");
  16. } catch (Exception e) {
  17. result.put("code", 500);
  18. result.put("msg", "系统异常,请联系管理员!");
  19. }
  20. return result;
  21. }
  22. }

这样的话,每个接口都这样处理,非常麻烦,需要一种更优雅的实现方式。

2.1 定义统一的JSON结构

统一的 JSON 结构中属性包括数据、状态码、提示信息,其他项可以自己根据需要添加。一般来说,应该有默认的返回结构,也应该有用户指定的返回结构。由于返回数据类型无法确定,需要使用泛型,代码如下:

  1. public class ResponseInfo<T> {
  2. /**
  3. * 状态码
  4. */
  5. protected String code;
  6. /**
  7. * 响应信息
  8. */
  9. protected String msg;
  10. /**
  11. * 返回数据
  12. */
  13. private T data;
  14. /**
  15. * 若没有数据返回,默认状态码为 0,提示信息为“操作成功!”
  16. */
  17. public ResponseInfo() {
  18. this.code = 0;
  19. this.msg = "操作成功!";
  20. }
  21. /**
  22. * 若没有数据返回,可以人为指定状态码和提示信息
  23. * @param code
  24. * @param msg
  25. */
  26. public ResponseInfo(String code, String msg) {
  27. this.code = code;
  28. this.msg = msg;
  29. }
  30. /**
  31. * 有数据返回时,状态码为 0,默认提示信息为“操作成功!”
  32. * @param data
  33. */
  34. public ResponseInfo(T data) {
  35. this.data = data;
  36. this.code = 0;
  37. this.msg = "操作成功!";
  38. }
  39. /**
  40. * 有数据返回,状态码为 0,人为指定提示信息
  41. * @param data
  42. * @param msg
  43. */
  44. public ResponseInfo(T data, String msg) {
  45. this.data = data;
  46. this.code = 0;
  47. this.msg = msg;
  48. }
  49. // 省略 get 和 set 方法
  50. }

2.2 使用统一的JSON结构

封装了统一的返回数据结构后,在接口中就可以直接使用了。如下:

  1. import com.example.demo.model.ResponseInfo;
  2. import org.springframework.web.bind.annotation.RequestMapping;
  3. import org.springframework.web.bind.annotation.RequestMethod;
  4. import org.springframework.web.bind.annotation.RestController;
  5. @RestController
  6. @RequestMapping(value = "/test", method = RequestMethod.GET)
  7. public class TestController {
  8. @RequestMapping("/json")
  9. public ResponseInfo test() {
  10. try {
  11. // 模拟异常业务代码
  12. int num = 1 / 0;
  13. return new ResponseInfo("测试数据");
  14. } catch (Exception e) {
  15. return new ResponseInfo(500, "系统异常,请联系管理员!");
  16. }
  17. }
  18. }

如上,接口的返回数据处理便优雅了许多。针对上面接口做个测试,启动项目,通过浏览器访问:localhost:8096/test/json,得到响应结果:

  1. {"code":500,"msg":"系统异常,请联系管理员!","data":null}

3、全局异常处理

3.1 系统定义异常处理

新建一个 ExceptionHandlerAdvice 全局异常处理类,然后加上 @RestControllerAdvice 注解即可拦截项目中抛出的异常,如下代码中包含了几个异常处理,如参数格式异常、参数缺失、系统异常等,见下例:

  1. @RestControllerAdvice
  2. @Slf4j
  3. public class ExceptionHandlerAdvice {
  4. // 参数格式异常处理
  5. @ExceptionHandler({IllegalArgumentException.class})
  6. @ResponseStatus(HttpStatus.BAD_REQUEST)
  7. public ResponseInfo badRequestException(IllegalArgumentException exception) {
  8. log.error("参数格式不合法:" + e.getMessage());
  9. return new ResponseInfo(HttpStatus.BAD_REQUEST.value() + "", "参数格式不符!");
  10. }
  11. // 权限不足异常处理
  12. @ExceptionHandler({AccessDeniedException.class})
  13. @ResponseStatus(HttpStatus.FORBIDDEN)
  14. public ResponseInfo badRequestException(AccessDeniedException exception) {
  15. return new ResponseInfo(HttpStatus.FORBIDDEN.value() + "", exception.getMessage());
  16. }
  17. // 参数缺失异常处理
  18. @ExceptionHandler({MissingServletRequestParameterException.class})
  19. @ResponseStatus(HttpStatus.BAD_REQUEST)
  20. public ResponseInfo badRequestException(Exception exception) {
  21. return new ResponseInfo(HttpStatus.BAD_REQUEST.value() + "", "缺少必填参数!");
  22. }
  23. // 空指针异常
  24. @ExceptionHandler(NullPointerException.class)
  25. @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
  26. public ResponseInfo handleTypeMismatchException(NullPointerException ex) {
  27. log.error("空指针异常,{}", ex.getMessage());
  28. return new JsonResult("500", "空指针异常");
  29. }
  30. @ExceptionHandler(Exception.class)
  31. @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
  32. public JsonResult handleUnexpectedServer(Exception ex) {
  33. log.error("系统异常:", ex);
  34. return new JsonResult("500", "系统发生异常,请联系管理员");
  35. }
  36. // 系统异常处理
  37. @ExceptionHandler(Throwable.class)
  38. @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  39. public ResponseInfo exception(Throwable throwable) {
  40. log.error("系统异常", throwable);
  41. return new ResponseInfo(HttpStatus.INTERNAL_SERVER_ERROR.value() + "系统异常,请联系管理员!");
  42. }
  43. }
  1. @RestControllerAdvice 注解包含了 @Component 注解,说明在 Spring Boot 启动时,也会把该类作为组件交给 Spring 来管理。
  2. @RestControllerAdvice 注解包含了 @ResponseBody 注解,为了异常处理完之后给调用方输出一个 JSON 格式的封装数据。
  3. @RestControllerAdvice 注解还有个 basePackages 属性,该属性用来拦截哪个包中的异常信息,一般不指定这个属性,拦截项目工程中的所有异常。
  4. 在方法上通过 @ExceptionHandler 注解来指定具体的异常,然后在方法中处理该异常信息,最后将结果通过统一的 JSON 结构体返回给调用者。
  5. 但在项目中,一般都会比较详细地去拦截一些常见异常,拦截 Exception 虽然可以一劳永逸,但是不利于去排查或者定位问题。实际项目中,可以把拦截 Exception 异常写在 GlobalExceptionHandler 最下面,如果都没有找到,最后再拦截一下 Exception 异常,保证输出信息友好。

下面通过一个接口来进行测试:

  1. @RestController
  2. @RequestMapping(value = "/test", method = RequestMethod.POST)
  3. public class TestController {
  4. @RequestMapping("/json")
  5. public ResponseInfo test(@RequestParam String userName, @RequestParam String password) {
  6. try {
  7. String data = "登录用户:" + userName + ",密码:" + password;
  8. return new ResponseInfo("0", "操作成功!", data);
  9. } catch (Exception e) {
  10. return new ResponseInfo("500", "系统异常,请联系管理员!");
  11. }
  12. }
  13. }

接口调用,password这项故意空缺:
2021-07-24-19-24-33-865989.png