照应和一致意外处置 太优雅了! SpringBoot 定义优雅全局一致 Restful API

  • 电脑网络维修
  • 2024-11-15

SpringBoot 定义优雅全局一致 Restful API 照应和一致意外处置,太优雅了!

一致接口照应能够缩小团队外部不用要的沟通;减轻接口生产者校验数据的累赘;降落其余共事接手代码的难度;提高接口的强健性和可裁减性。

大家好,我是码哥,《Redis 高手心法》作者。

假设你作为名目组长,为 Spring Boot 名目设计一个规范的一致的RESTfulAPI照应框架。

前端或许移动端开发人员经过调用后端提供的RESTful接口成功数据的替换。

经常出现的一致照应数据结构如下所示:

public class Result<T> implements Serializable {private Integer code;private String message;private T>

一致接口照应能够缩小团队外部不用要的沟通;减轻接口生产者校验数据的累赘;降落其余共事接手代码的难度;提高接口的强健性和可裁减性。

除此之外,还须要成功一个一致的意外处置框架。经过这个全局意外处置,可以防止将意外信息和系统敏感信息间接抛出给客户端。

针对特定意外捕捉后可以从新对意外输入信息做编排,提高交互友好度,同时可以记载意外信息。

咱们须要定义一个Result类,在类中定义须要前往的字段信息,比如形态码、结果形容、结果数据集等。

接口的形态码很多,咱们可以用一个枚举类启动封装。于是就有了上方的代码。顺便说一句,介绍大家经常使用lombok,缩小繁琐的 set、get、结构方法。

@Getter@AllArgsConstructorpublic enum ResultEnum {/*** return success result.*/SUCCESS(200, "接口调用成功"),/*** return business common failed.*/COMMON_FAILED(, "接口调用失败"),NOT_FOUND(404, "接口不存在"),FORBIDDEN(403, "资源拒绝访问"),UNAUTHORIZED(401, "未认证(签名失误)"),INTERNAL_SERVER_ERROR(500, "主机外部失误"),NULL_POINT(200002, "空指针意外"),PARAM_ERROR(200001, "参数失误");private Integer code;private String message;}

封装一个固定前往格局的结构对象:Result。

@Setter@Getterpublic class Result<T> implements Serializable {private Integer code;private String message;private T>

有了一致照应体,于是你就可以在 Controller 前往结果时这样写:

@RestControllerpublic class UserController {@Autowiredprivate UserService userService;@RequestMapping(value = "/queryUser")public Result<User> query(@RequestParam("userId") Long userId){try {// 业务代码...User user = userService.queryId(userId);return ResultMsg.success(user);} catch (Exception e){return ResultMsg.fail(e.getMessage());}}}

这个疑问问得好。

为了能够成功一致的照应答象,又能优雅的定义 Controller 类的方法,使其每个方法的前往值是其应有的类型。

重要是借助RestControllerAdvice注解和ResponseBodyAdvice接口来成功对接口照应给客户端之前封装成 Result。

Spring Boot 框架其实曾经协助开发者封装了很多适用的工具,比如 ResponseBodyAdvice 接口,咱们可以应用来成功数据格局的一致前往。

有些场景下咱们不宿愿 Controller 方法的前往值被包装为一致照应答象,可以先定义一个疏忽照应封装的注解,配合后续代码成功。

@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface IgnoreRestFulAPI {}

这是 Spring 框架提供的一个接口,咱们可以应用它成功对接口数据格局一致封装。

ResponseBodyAdvice可以对 controller 层中的领有@ResponseBody注解属性的方法启动照应阻拦,用户可以应用这一个性来封装数据的前往格局,也可以启动加密、签名等操作。

成功该接口的类还须要增加@RestControllerAdvice注解,这是一个组合注解,由@ControllerAdvice、@ResponseBody组成,而@ControllerAdvice承袭了@Component,因此@RestControllerAdvice实质上是个Component。

实质上就是经常使用 Spring AOP 定义的一个切面,作用于 Controller 方法口头成功后的增强操作。

ResponseBodyAdvice接口有两个方法须要重写。

@RestControllerAdvice@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> {private static final ObjectMapper mapper = new ObjectMapper();@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {// 方法没有IgnoreRestFulAPI注解,且前往类型不是 Result类型时调用 beforeBodyWrite 成功照应数据封装return !returnType.hasMethodAnnotation(IgnoreRestFulAPI.class)&& !returnType.getParameterType().isAssignableFrom(Result.class);}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// 假设前往值是void类型,间接前往200形态信息if (returnType.getParameterType().isAssignableFrom(Void.TYPE)) {return Result.success();}// 前往类型不是 Result,是 String 类型if (!(body instanceof Result)) {// warning: RestController方法上台往值类型为String时,自动照应的Content-Type是text/plain,// 须要手动指定为application/json 才干对结果启动包装成 jsonif (body instanceof String) {return toJson(Result.success(body));}return Result.success(body);}// 前往类型是 Result,间接前往return body;}private Object toJson(Object body) {try {return mapper.writeValueAsString(body);} catch (JsonProcessingException e) {throw new RuntimeException("不可转发json格局", e);}}}

由于启动类上的@SpringbootApplication自动扫描本包和子包。

比如GlobalResponseAdvice在 zero.magebyte.shop.common 包下,而启动类在 zero.magebyte.shop.order.server 包,那么 GlobalResponseAdvice 就不会失效。

为了防止全局接口一致照应处置器GlobalResponseAdvice类未被扫描到,倡导在启动类上加上包扫描。

定义一个 Controller 类来启动便捷的开发和测试。

@RestController@RequestMapping("/demo")public class DemoController {@GetMapping("/method1")public Result<Integer> method1() {return Result.success(100);}@GetMapping("/method2")public void method2() {}@GetMapping(value = "/method3")@IgnoreRestFulAPIpublic String method3() {return "不会被封装,间接前往 String";}/*** RestController中前往值类型是String的方法自动照应类型是text/plain,须要手动指定为application/json方可对其启动包装*/@GetMapping(value = "/method4", produces = MediaType.APPLICATION_JSON_VALUE)public String method4() {return "会被封装 Result 结构 JSON";}/*** 会被封装,然而照应类型是text/plain* @return*/@GetMapping(value = "/method5")public String method5() {return "会被封装为 Result 的 text/html";}}

method1 方法前往类型是 Result,所以不会再次封装,而是间接前往 Result 结构,并以Content-Type: application/json格局照应给客户端。

{"code": 200,"message": "接口调用成功","data": 100}

method2 方法前往类型是 void,会封装成 Result 结构,并以Content-Type: application/json格局照应给客户端。只不过>

{"code": 200,"message": "接口调用成功","data": null}

method3 被 @IgnoreRestFulAPI 注解,不会被封装 Result 结构,间接前往。

自动 String 类型的数据照应给客户端的格局为text/html,为了一致照应格局,须要手动设置照应类型为 json,如下所示。

@GetMapping(value = "/method4", produces = MediaType.APPLICATION_JSON_VALUE)

照应给客户端的格局就是一个 Result JSON 对象,Content-Type: application/json。

{"code": 200,"message": "接口调用成功","data": "会被封装 Result 结构 JSON"}

否则将会以Content-Type: text/html;charset=UTF-8照应呵客户端。

另外须要留意的是,假设你经常使用了 swagger,以上代码会造成 swagger 不可访问。

报错如下:

Unable to infer base url. This is common when using dynamic servlet registration or when the API is behind an API Gateway. The base url is the root of where all the swagger resources are served. For e.g. if the api is available atthen the base url isPlease enter the location manually:

要素:由于一致照应阻拦器对 swagger 的接口做了阻拦并对结果做了包装,造成前往结构出现后变动,swagger 不可解析。

处置打算:修正一致照应处置器阻拦的范畴,性能散列包门路。你可以指定@RestControllerAdvice(basePackages = {"xxx.xxx"})名目标 controller 目录即可。

@RestControllerpublic class UserController {@Autowiredprivate UserService userService;@RequestMapping(value = "/queryUser")public User query(@RequestParam("userId") Long userId){try {// 业务代码...User user = userService.queryId(userId);return user;} catch (Exception e){return Result.fail(e.getMessage());}}}

兵来将挡,水来土掩。这样写代码并不是不难看,而是十分渣滓!!!

如下是咱们自定义的业务意外。

@Setter@Getterpublic class BusinessException extends RuntimeException {private Integer code;private String message;public BusinessException(Throwable cause) {super(cause);}public BusinessException(String message) {super(message);this.message = message;}public BusinessException(Integer code, String message, Throwable cause) {super(cause);this.code = code;this.message = message;}}

在 Spring Boot 中,咱们不用这样写,可以继续应用 @RestControllerAdvice 注解和 @ExceptionHandler 注解成功全局意外处置器,阻拦 Controller 层抛出的意外。

新增GlobalExceptionHandler类,编写一致意外处置,类上方增加@RestControllerAdvice注解就开启了全局意外处置。

咱们可以在类面创立多个方法,并在方法上增加@ExceptionHandler注解,对不同的意外启动定制化处置,并一致前往 Result 结构照应给客户端。

@RestControllerAdvice@Slf4jpublic class GlobalExceptionHandler {/*** 处置自定义的业务意外** @param req* @param e* @return*/@ExceptionHandler(value = BusinessException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public <T> Result<T> baseExceptionHandler(HttpServletRequest req, BusinessException e) {log.error("出现业务意外!", e);int code = Objects.isNull(e.getCode()) ? ResultEnum.INTERNAL_SERVER_ERROR.getCode() : e.getCode();String message = StringUtils.isBlank(e.getMessage()) ? ResultEnum.INTERNAL_SERVER_ERROR.getMessage() : e.getMessage();return new Result<>(code, message);}@ExceptionHandler(value = RuntimeException.class)@ResponseBody@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public <T> Result<T> runtimeExceptionHandler(HttpServletRequest req, RuntimeException e) {log.error("出现运转时意外!", e);return Result.failed(ResultEnum.INTERNAL_SERVER_ERROR);}/*** 处置空指针的意外** @param req* @param e* @return*/@ExceptionHandler(value = NullPointerException.class)@ResponseBody@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public <T> Result<T> exceptionHandler(HttpServletRequest req, NullPointerException e) {log.error("出现空指针意外!", e);return Result.failed(ResultEnum.INTERNAL_SERVER_ERROR);}/*** 处置其余意外** @param req* @param e* @return*/@ExceptionHandler(value = Exception.class)@ResponseBody@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public <T> Result<T> exceptionHandler(HttpServletRequest req, Exception e) {log.error("未知意外!", e);return Result.failed(ResultEnum.INTERNAL_SERVER_ERROR);}@ExceptionHandler(value = BindException.class)@ResponseBody@ResponseStatus(HttpStatus.BAD_REQUEST)public Result<String> handlerBindException(HttpServletRequest request, BindException e) {StringBuilder sb = new StringBuilder();List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();for (FieldError fe : fieldErrors) {sb.append(fe.getField()).append(":").append(fe.getDefaultMessage()).append(";");}String errorStr = sb.length() == 0 ? "" : sb.substring(0, sb.length() - 1);return new Result(HttpStatus.BAD_REQUEST.value(), errorStr);}@ExceptionHandler(value = MethodArgumentNotValidException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public Result<String> handlerMethodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {StringBuilder sb = new StringBuilder();List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();for (FieldError fe : fieldErrors) {sb.append(fe.getField()).append(":").append(fe.getDefaultMessage()).append(";");}String errorStr = sb.isEmpty() ? "" : sb.substring(0, sb.length() - 1);return new Result<String>(HttpStatus.BAD_REQUEST.value(), errorStr);}@ExceptionHandler(value = SQLException.class)@ResponseBody@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Result<String> handlerSQLException(SQLException e) {log.error("数据库意外!", e);return Result.failed(ResultEnum.INTERNAL_SERVER_ERROR);}}

故意制作一个除 0 意外。

@GetMapping(value = "/method6")public Order method6() {int a = 1/0;Order order = new Order();order.setId(1);order.setMoney(999);return order;}

自定义抛出业务意外。

@GetMapping(value = "/method7")public Order method7() {Order order = new Order();order.setId(1);order.setMoney(999);if (order.getCreateTime() == null) {throw new BusinessException("创立期间不能为空");}return order;}

RestControllerAdvice注解和ResponseBodyAdvice接口来成功对接口照应给客户端之前封装成 Result。

一致接口照应客户端,缩小团队外部不用要的沟通;减轻接口生产者校验数据的累赘;降落其余共事接手代码的难度;提高接口的强健性和可裁减性。

经过@RestControllerAdvice注解和@ExceptionHandler` 注解成功一致意外处置,能够缩小代码的重复度和复杂度,无利于代码的保养,并且能够极速定位到 BUG,大大提高咱们的开发效率。

  • 关注微信

本网站的文章部分内容可能来源于网络和网友发布,仅供大家学习与参考,如有侵权,请联系站长进行删除处理,不代表本网站立场,转载联系作者并注明出处:https://duobeib.com/diannaowangluoweixiu/6416.html

猜你喜欢

热门标签

洗手盆如何疏浚梗塞 洗手盆为何梗塞 iPhone提价霸占4G市场等于原价8折 明码箱怎样设置明码锁 苏泊尔电饭锅保修多久 长城画龙G8253YN彩电输入指令画面变暗疑问检修 彩星彩电解除童锁方法大全 三星笔记本培修点上海 液晶显示器花屏培修视频 燃气热水器不热水要素 热水器不上班经常出现3种处置方法 无氟空调跟有氟空调有什么区别 norltz燃气热水器售后电话 大连站和大连北站哪个离周水子机场近 热水器显示屏亮显示温度不加热 铁猫牌保险箱高效开锁技巧 科技助力安保无忧 创维8R80 汽修 a1265和c3182是什么管 为什么电热水器不能即热 标致空调为什么不冷 神舟培修笔记本培修 dell1420内存更新 青岛自来水公司培修热线电话 包头美的洗衣机全国各市售后服务预定热线号码2024年修缮点降级 创维42k08rd更新 空调为什么运转异响 热水器为何会漏水 该如何处置 什么是可以自己处置的 重庆华帝售后电话 波轮洗衣机荡涤价格 鼎新热水器 留意了!不是水平疑问! 马桶产生了这5个现象 方便 极速 邢台空调移机电话上门服务 扬子空调缺点代码e4是什么疑问 宏基4736zG可以装置W11吗 奥克斯空调培修官方 为什么突然空调滴水很多 乐视s40air刷机包 未联络视的提高方向 官网培修 格力空调售后电话 皇明太阳能电话 看尚X55液晶电视进入工厂形式和软件更新方法 燃气热水器缺点代码

热门资讯

关注我们

微信公众号