接口在返回结果集的时候出现了406的报错。避坑。 - -!
2023-11-23 09:44:04.062 WARN 15612 — [nio-8888-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation]
406 Not Acceptable是一个HTTP响应状态码,指示服务器无法实现客户端的一个 Accept-标头的请求响应。这通常是用户代理(即浏览器)指定一个可接受的字符集(通过Accept-Charset)、语言(通过Accept-Language)等应响应的结果,并且服务器无法提供此类响应。
406:HTTP协议状态码的一种(4xx表示客户端的问题),表示客户端无法解析服务端返回的内容。就是后台的返回结果前台无法解析就会报406错误。
406 Not Acceptable 表示用户代理(在大多数情况下是 Web 浏览器)请求了有效的资源,但请求包含一个特殊的 Accept- 标头,该标头向服务器指示有效响应只能包含特定类型的信息。下面是此类场景的几个栗子:
在 HTTP 请求中可以提供少量其他 Accept- 标头,但绝大多数场景与上述类似:用户代理需要显式类型的响应,服务器要么提供响应,要么返回 406 代码以指示它无法实现请求。
package cn.example.springbootproject.tempTestVue;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.HashMap;
import java.util.Map;
/**
* @Description: 实现统一返回数据
* @ClassName: R
*/
/** chain:支持链式编程 fluent:忽略get/set前缀 */
@Data
@Accessors(chain = true,fluent = true)
public class R {
private Boolean success;
private Boolean error;
/**响应提示信息*/
private String message;
/**相应状态码*/
private Integer code;
/**保存返回数据的对象*/
private Map data = new HashMap<String,Object>();
/**请求成功*/
public static R ok(){
R r = new R();
r.success(true);
r.message(ResultCodeEnum.SUCCESS.getMessage());
r.code(ResultCodeEnum.SUCCESS.getCode());
return r;
}
/**请求失败*/
public static R error(){
R r = new R();
r.error(false);
r.code(ResultCodeEnum.FAIL.getCode());
r.message(ResultCodeEnum.FAIL.getMessage());
return r;
}
/**返回数据对象赋值接口*/
public R data(String key,Object value){
this.data.put(key,value);
return this;
}
public R data(Map<String,Object> map){
this.data(map);
return this;
}
@Override
public String toString() {
return "R{" +
"success=" + success +
", error=" + error +
", message='" + message + '\'' +
", code=" + code +
", data=" + data +
'}';
}
}
注: 这里我使用到了 @Accessors(chain = true,fluent = true) 这个注解,后续问题就出现在这个注解上
package cn.example.springbootproject.tempTestVue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* @Description: 雇员查询控制层
* @ClassName: EmplyeeController
*/
@RequestMapping("/employee")
@RestController
//@CrossOrigin
public class EmplyeeController {
@Autowired
HttpServletResponse response;
@GetMapping("queryList")
@ResponseBody
public R queryList(){
//这里就简单的封装一个模拟数据了,不在调用Service层在进行数据的查询
ArrayList<Map> employeeList = new ArrayList<>();
for (int i = 0; i < 5 ;i++) {
HashMap<String, Object> employee = new HashMap<>();
employee.put("id",i);
employee.put("name","张三"+i);
employee.put("age",20+i);
employee.put("sex","男");
employee.put("address","北京市朝阳区"+i);
employee.put("salary",10000+i);
employeeList.add(employee);
}
System.out.println("访问接口成功:employeeList = " + employeeList);
return R.ok().data("list",employeeList);
}
}
基于这个问题,我在网上查询了许多解决方案,但是最终都未能解决我的这个问题…
这里先记录一下收集到的解决方案和尝试过程。
将返回的数据类型类中为所有属性添加get/set方法,或者添加@Data注解,或者添加@Setter和@Getter
这里我的统一返回值类中,其实原本就打上了 @Data 注解(内部包含@Getter 和 @Setter 注解),这里在给统一返回类打上了@Getter 和 @Setter 注解尝试着能不能解决。
结果一样,还是会出现同样的 406 错误! 下面就尝试着 第二种解决方案:
方式二的尝试是通过响应格式的设置,这里猜测是响应格式导致的问题,所以又尝试通过下面两种方式来解决这个问题:
这里将 Controller 层访问接口方法上的 @GetMapping注解进行了设置修改为:
@RequestMapping(value = “/queryList”,method = RequestMethod.GET,produces = “applications/json;charset=utf-8”)
这里设置了响应格式 application/json;charset=utf-8 ,但是最后测试结果还是一样 406 没改变。
于是在尝试着通过 responseBean 来设置 响应数据格式
通过Response.setContentType() 设置响应格式:
哈哈哈,通过我注释掉就能知道,效果一样,还是 406 。
上面两种方式都是为了改变响应头,响应格式的修改,但是都是不起效,说明问题也不是出在这里。
通过一系列的排查测试,最终找到 报错的原因了:
导致页面报错 406: “Not Acceptable” 的原因就是 统一响应数据类 上的 @Accessors 注解中的 fluent=true属性。
将 @Accessors 注解中的 fluent 属性删除掉之后在测试,响应结果就正常了,目前我所了解到的该注解的作用就是在我们调用实体类的 Get/Set 方法时,直接可以忽略掉get/set开通,就类似没有该属性时 setName(xxx),存在该属性值后只需要 object.name(xxx)。
package cn.example.springbootproject.tempTestVue;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.HashMap;
import java.util.Map;
/**
* @Description: 实现统一返回数据
* @ClassName: R
*/
/** chain:支持链式编程 fluent:忽略get/set前缀 */
@Data
@Accessors(chain = true/*,fluent = true*/)
public class R {
private Boolean success;
private Boolean error;
/**响应提示信息*/
private String message;
/**相应状态码*/
private Integer code;
/**保存返回数据的对象*/
private Map data = new HashMap<String,Object>();
/**请求成功*/
public static R ok(){
R r = new R();
r.setSuccess(true);
r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
r.setCode(ResultCodeEnum.SUCCESS.getCode());
return r;
}
/**请求失败*/
public static R error(){
R r = new R();
r.setError(false);
r.setCode(ResultCodeEnum.FAIL.getCode());
r.setMessage(ResultCodeEnum.FAIL.getMessage());
return r;
}
/**返回数据对象赋值接口*/
public R data(String key,Object value){
this.data.put(key,value);
return this;
}
public R data(Map<String,Object> map){
this.data(map);
return this;
}
@Override
public String toString() {
return "R{" +
"success=" + success +
", error=" + error +
", message='" + message + '\'' +
", code=" + code +
", data=" + data +
'}';
}
}
Accessors注解是 lombok 插件包中的一个注解,中文含义是存取器。
打开Accessors 的源码观察:
如果属性 fluent = true :则访问器将以字段命名,即我们的 Get / Set 方法将不在包含get/set前缀,在调用时,是直接使用字段的名称即可。
如上图所示效果,我们在调用get / set方法时 ,就直接只用属性名称即可,不在添加 set /get 前缀!
**默认为false(注:但是当fluent为true时,其默认为true),**生成的setter方法是void类型;如果设置为true生成的setter方法返回this(当前对象)。
这就意味着,我们可以通过 set方式对操作对象进行链式编程了,即在调用set方法之后,还是返回其对象,然后就能直接进行下一步对象的操作,从而实现链式编程。
可以指定前缀,生成getter和setter方法时会去掉指定的前缀(遵守驼峰命名)。
相当于字符串截取功能,在生成getter和setter方法的时候,会自动截取去除指定前缀,然后加上get与set。
请注意,仅当前一个字符不是小写字符或前缀的最后一个字母不是字母(例如下划线)时,前缀才算数。如果在去除前缀时多个字段都变成同一个名称,则会生成错误。
如果为 true,则生成的访问器将被标记为 final。 默认值:false
返回:是否应将访问器标记为 final。