R类 和 枚举
01、为什么学习统一返回?
在企业其实大部分的程序开发,或者接口开发,其实都返回一种所谓的JSON格式。而json格式是通过java面向对象的方式进行封装而得。
- 在统一异常处理的时候,我们使用的是@RestControllerAdvice是controller后置增强处理。使用@RestControllerAdvice后置处理可以达到
- 在前后端分离的项目架构中,全部都是异步返回@RestController 也全部都是@ResponseBody的方法。以为你没有页面也没有所谓的freemarker。所以在开发我们肯定是希望把具体错误信息—进行拦截–进行改造(统一返回) – 改造的信息返回给用户(接口的调用者)
- 进行改造的标准就显得非常的重要,一般做法就使用java面向对象的方式进行开发。面向类返回,而不要使用Map和Object.
- 因为使用Map的key用户随便定义,你达不到高度统一和规范。
常用的命名:
- R
- Result
- ResultResponse
- ServerResponse
- ApiResponse
- ApiResult
02、R类封装的过程
统一返回的格式:
成功只有一个情况,失败就N种情况。
# 成功的返回
{
status:200,
data:{id:1,name:"zhangsan"},
msg:""
}
# 失败返回
{
status:601,
msg:"用户不存在!!",
data:""
}
{
status:602,
msg:"支付用户已禁止!!",
data:""
}
认识json格式
{}----Map或者Bean
[{Map},{Map},{Map}] List<Map>
[{Bean},{Bean},{Bean}] List<Bean>
R的雏形:
@Data
public class R {
// 状态码
private Integer status;
// 返回信息
private String msg;
// 返回数据,因为返回数据是不确定类型,所以只能考虑Object或者泛型
private Object data;
}
03、R的使用原因是什么?
package com.kuangstudy.web;
import com.kuangstudy.bean.User;
import com.kuangstudy.config.R;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* Description:
* Author: yykk Administrator
* Version: 1.0
* Create Date Time: 2021/12/16 20:33.
* Update Date Time:
*
* @see
*/
@RestController
public class UserController {
@GetMapping("/saveuser")
public R saveUser(){
// 1:保存用户对象
User user = new User();
user.setUserid(1L);
user.setUsername("yykk");
user.setPassword("24534534");
user.setCreateTime(new Date());
// 2: 返回R
R r = new R();
r.setStatus(200);
r.setMsg("");
r.setData(user);
return r;
}
@GetMapping("/saveuser2")
public Map<String,Object> saveUser2(){
// 1:保存用户对象
User user = new User();
user.setUserid(1L);
user.setUsername("yykk");
user.setPassword("24534534");
user.setCreateTime(new Date());
// 2: 返回R
Map<String,Object> map = new HashMap<>();
map.put("status",200);
map.put("msg","");
map.put("data",user);
return map;
}
}
http://localhost:8085/saveuser 返回得到如下效果:
var res = {
"status": 200,
"msg": "",
"data": {
"userid": 1,
"username": "yykk",
"createTime": "2021-12-16T12:39:10.037+00:00",
"password": "24534534"
}
}
// 如果取值
console.log(res.status)
console.log(res.data.userid)
console.log(res.data.username)
console.log(res.data.createTime)
console.log(res.data.password)
// 结构取值
var {userid,username,...yykk} = res.data;
console.log(userid)
console.log(username)
console.log(yykk.createTime)
console.log(yykk.password)
-
上面的代码R和Map,其实可以达到需要的效果。但是Map有问题,不灵活因为一个团队可能是很多开发人员在开发。如果每个人都一套标准和返回的话,就显得没有约束和规范。这个时候接口调用者就开始迷茫了、
-
使用R达到一个统一返回的目的。
- 标准只有一种,推荐使用此方案
-
问题:返回书写不方便而且很繁琐,可以考虑使用方法进行封装,达到复用的目的和调用方便
04、R类的改造
package com.kuangstudy.config;
import lombok.Data;
@Data
public class R {
// 状态码
private Integer status;
// 返回信息
private String msg;
// 返回数据,因为返回数据是不确定类型,所以只能考虑Object或者泛型
private Object data;
// 全部约束使用方法区执行和返回,不允许到到外部去new
private R() {
}
/**
* 方法封装 :
* - R 大量的大量的入侵
* - 解决调用方便的问题
*
* @param obj
* @return
*/
public static R success(Object obj) {
// 200写死,原因很简单:在开发成功只允许只有一种声音,不允许多种
return restResult(obj, 200, "success");
}
public static R success(Object obj, String msg) {
return restResult(obj, 200, msg);
}
// 错误为什么传递status。成功只有一种,但是错误有N状态
public static R error(Integer status, String msg) {
return restResult(null, status, msg);
}
public static R error(Integer status, String msg, Object obj) {
return restResult(obj, status, msg);
}
private static R restResult(Object data, Integer status, String msg) {
R r = new R();
r.setStatus(status);
r.setMsg(msg);
r.setData(data);
return r;
}
}
05、R的构造函数为什么要私有化?
其实不用,只不过从规范约束的角度思考话,建议是私有化:让调用只有一种方式。
- 只通过方法区处理返回
- 不允许使用创建对象的对象的方式进行返回
- 从而得到高度的统一和标准
06、R类和枚举恋爱关系
统一返回和枚举:黄金搭档
在统一返回的时候,其实我们很多错误信息,都是让我们程序开发人员去定义,状态也好还是返回信息也好。比如:
R.error(601,"用户属于有误");
R.error(602,"支付失败");
R.error(10001,"密码有误");
R.error(500,"服务器忙中");
上面会存在一个问题,随着项目越来越庞大,开发人员也越来越多。这个时候就必须高度对着提示和状态控制做集中式管理。方便修改和统一调整。
集中管理的方式
用常量类 & 用接口
package com.kuangstudy.consts;
public interface ResultConstants {
// return R.error(601,"用户密码有误");
Integer USER_PWD_STATUS = 601;
String USER_PWD_MESSAGE = "用户密码有误!";
//R.error(500,"服务器忙中");
Integer SERVER_ERROR_STATUS = 500;
String SERVER_ERROR_MESSAGE = "服务器忙中!";
}
上面的静态常量区做集中管理信息是没有任何问题,但是存在几个毛病:
-
状态管理会非常的多 一个接口定义状态会很多,如果一旦开发不标准不规范就造成谁和谁。
-
命名都是一种灾难
枚举
枚举在学习中:它是一个类,类继承接口,继承枚举。
枚举在程序中:它是一个常量类==多值匹配(成双入对,三三两两)==的时候一种面向对象的替代。
枚举的定义
- 可以定义属性和方法
- 构造函数 - 私有
- 说明什么:它的对象只能通过方法去暴露。枚举并选择通过方法区暴露对象
- 而是通过一种自定义构造函数名字的方式,进行暴露。
枚举的定义在什么情况下是单列的:
在枚举中只有一个对象的时候就单列的。
package com.kuangstudy.enums;
public enum ResultStatusEnum {
INSTANCE();
ResultStatusEnum() {
}
public Integer age;
}
ResultStatusEnum.INSTANCE.age = 10;
ResultStatusEnum.INSTANCE2.age = 20;
07、R类和枚举恋爱
R.java
package com.kuangstudy.config;
import com.kuangstudy.enums.ResultStatusEnum;
import lombok.Data;
@Data
public class R {
// 状态码
private Integer status;
// 返回信息
private String msg;
// 返回数据,因为返回数据是不确定类型,所以只能考虑Object或者泛型
private Object data;
// 全部约束使用方法区执行和返回,不允许到到外部去new
private R() {
}
/**
* 方法封装 :
* - R 大量的大量的入侵
* - 解决调用方便的问题
*
* @param obj
* @return
*/
public static R success(Object obj) {
// 200写死,原因很简单:在开发成功只允许只有一种声音,不允许多种
return restResult(obj, ResultStatusEnum.SUCCESS_STATUS.getStatus(), ResultStatusEnum.SUCCESS_STATUS.getMessage());
}
public static R success(Object obj, String msg) {
return restResult(obj, ResultStatusEnum.SUCCESS_STATUS.getStatus(), ResultStatusEnum.SUCCESS_STATUS.getMessage());
}
// 错误为什么传递status。成功只有一种,但是错误有N状态
@Deprecated
public static R error(Integer status, String msg) {
return restResult(null, status, msg);
}
@Deprecated
public static R error(Integer status, String msg, Object obj) {
return restResult(obj, status, msg);
}
public static R error(ResultStatusEnum resultStatusEnum) {
return restResult(null, resultStatusEnum.getStatus(),resultStatusEnum.getMessage());
}
private static R restResult(Object data, Integer status, String msg) {
R r = new R();
r.setStatus(status);
r.setMsg(msg);
r.setData(data);
return r;
}
}
ResultStatusEnum.java
package com.kuangstudy.enums;
public enum ResultStatusEnum {
SUCCESS_STATUS(200,"SUCCESS"),
USER_PWR_STATUS(601,"用户密码有误"),
ORDER_ERROR_STATUS(602,"订单有误");
ResultStatusEnum(Integer status,String message) {
this.status = status;
this.message = message;
}
private Integer status;
private String message;
public Integer getStatus() {
return status;
}
public String getMessage() {
return message;
}
}
08、枚举还有必要set方法吗?
本身统一返回用枚举,本身就去做集中管理错误信息,你现在提供一个set暴露出去让程序员进行去修改,那不是自相矛盾吗?所以这种场景下,枚举的set就应去除。
09、SpringMvc框架提供ResponseEntity为什么不用?
在一些开源项目中,未来学习一个项目课程的时候,可能会看到有一些项目返回的并不是R。而是ResponseEntity
ResponseEntity 是springmvc官方提供。它sprignmvc的方法做统一管理的一个机制。但是这个存在很大的局限性。不方便扩展和以及内部提供HttpStatus枚举类。满足不了我们业务需求。因为它会脱离业务,不方便控制。所以一般不使用。
其实R类+自定义枚举的参考:ResponseEntity + HttpStatus
@GetMapping("/saveuser3")
public ResponseEntity<User> saveuser3(){
// 1:保存用户对象
User user = new User();
user.setUserid(1L);
user.setUsername("yykk");
user.setPassword("24534534");
user.setCreateTime(new Date());
// 2: 返回R
return ResponseEntity.status(HttpStatus.OK).body(user);
}
10、R存在和枚举的存在问题?
在企业开发一定多人协同的开发模式,但是如果团队突然一下激增几十人或者上百人的时候,就存在一个问题。这个时候团队就出现一个声音,很多觉得这种统一返回有点点:
- 太过于约束。
- 来了一个新人,又和沟通一遍。沟通成本会越来越高。
解决方案
springmvc利用aop思想,其实在controller调用方法时候做很多的增强处理。aop:对象执行方法
- 如果出错:sprignmvc统一异常处理—Aop. ---- @AfterThrowing----切面
- springmvc还提供统一返回返回的处理----Aop----@AfterReturing—切面
- springmvc还提供统一参数处理----Aop----@Before—切面
实现步骤
定义一个统一返回的类:ResultResponseHandler实现 ResponseBodyAdvice接口如下:
package com.kuangstudy.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xq.common.result.handler.ErrorHandler;
import com.xq.controller.login.WeixinLoginController;
import com.xq.controller.login.WeixinOpenReplyController;
import com.xq.controller.pay.alipay.AliPayController;
import com.xq.controller.pay.weixin.WeixinNavtiveController;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
*
* bug: (basePackages = "com.kuangstudy")建议扫包
* 为什么?
* 如果你项目中没有使用Swagger,你可以扫包也可以不扫。都是正常的。
* 但是如果你项目使用了Swagger,因为Swagger本身也是一个springmvc的项目,他里面也是一个个http请求
* 这个请求的时候如果你项目中配置了拦截器,或者一些通知类xxxAdvice,那么就会把Swagger都会进行拦截。
* 就会造成Swagger失效。
*/
@RestControllerAdvice
public class ResultResponseHandler implements ResponseBodyAdvice<Object> {
/**
* 是否支持advice功能,true是支持 false是不支持
*
* @param methodParameter
* @return
*/
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
// 参数o 代表其实就是springmvc的请求的方法的结果
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
// 对请求的结果在这里统一返回和处理
if (o instanceof ErrorHandler) {
// 1、如果返回的结果是一个异常的结果,就把异常返回的结构数据倒腾到R.fail里面即可
ErrorHandler errorHandler = (ErrorHandler) o;
return R.error(errorHandler.getStatus(), errorHandler.getMsg());
} else if (o instanceof String) {
try {
// 2、因为springmvc数据转换器对String是有特殊处理 StringHttpMessageConverter
ObjectMapper objectMapper = new ObjectMapper();
R r = R.success(o);
return objectMapper.writeValueAsString(r);
} catch (Exception ex) {
ex.printStackTrace();
}
}
return R.success(o);
}
}
正常路线:— 请求-----controller对象----saveuser-----return user -----supports(true)-- beforeBodyWrite—R.success(user)
异常路线:— 请求-----controller对象----saveuser-----异常ex—GlobalExceptionControllerHandler—ErrorHandler-----supports(true)-- beforeBodyWrite—R.error(ex)
springclould
A------->提供用户查询接口-------Fegin <-----调用--------B----得到的是什么结果:R
10、为什么要学习自定义异常
package com.kuangstudy.config.exresult.ex;//package com.kuangstudy.config;
import com.kuangstudy.config.exresult.ResultStatusEnum;
/**
* Description: 自定义异常
*/
public class BussinessException extends RuntimeException {
private Integer status;
private String msg;
public BussinessException(Integer status, String msg) {
super(msg);
this.msg = msg;
this.status = status;
}
public BussinessException(ResultStatusEnum resultStatusEnum) {
super(resultStatusEnum.getMessage());
this.status = resultStatusEnum.getStatus();
this.msg = resultStatusEnum.getMessage();
}
public BussinessException(String msg) {
super(msg);
this.status = 500;
this.msg = msg;
}
public Integer getStatus() {
return status;
}
public String getMsg() {
return msg;
}
}
package com.kuangstudy.config.exresult.ex;//package com.kuangstudy.config;
import com.kuangstudy.config.exresult.ResultStatusEnum;
/**
* Description: 自定义异常
*/
public class OrderException extends RuntimeException {
private Integer status;
private String msg;
public OrderException(Integer status, String msg) {
super(msg);
this.msg = msg;
this.status = status;
}
public OrderException(String msg) {
super(msg);
this.status = 500;
this.msg = msg;
}
public OrderException(ResultStatusEnum resultStatusEnum) {
super(resultStatusEnum.getMessage());
this.status = resultStatusEnum.getStatus();
this.msg = resultStatusEnum.getMessage();
}
public Integer getStatus() {
return status;
}
public String getMsg() {
return msg;
}
}
- 自定义异常可以方便后续分析业务 便于后续分析和定位
- 自定义不建议继承:Exception 和 Throwable 如果你集成是顶级异常。你throw必须要throws.