响应406报错Resolved [HttpMediaTypeNotAcceptableException Could not find acceptable representation]

Http请求报错406: “Not Acceptable”,

​ 接口在返回结果集的时候出现了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报错Resolved [HttpMediaTypeNotAcceptableException Could not find acceptable representation]_第1张图片

  • Postman调用接口报错信息

响应406报错Resolved [HttpMediaTypeNotAcceptableException Could not find acceptable representation]_第2张图片

二、406状态码基本概念

406 Not Acceptable是一个HTTP响应状态码,指示服务器无法实现客户端的一个 Accept-标头的请求响应。这通常是用户代理(即浏览器)指定一个可接受的字符集(通过Accept-Charset)、语言(通过Accept-Language)等应响应的结果,并且服务器无法提供此类响应。

​ 406:HTTP协议状态码的一种(4xx表示客户端的问题),表示客户端无法解析服务端返回的内容。就是后台的返回结果前台无法解析就会报406错误。

​ 406 Not Acceptable 表示用户代理(在大多数情况下是 Web 浏览器)请求了有效的资源,但请求包含一个特殊的 Accept- 标头,该标头向服务器指示有效响应只能包含特定类型的信息。下面是此类场景的几个栗子:

  • 用户代理可能本地化为服务器无法提供的特定区域设置或语言。例如,用户代理可以使用 Accept-Language 请求标头来指定有效的法语语言(Accept-Language:fr),但如果服务器无法使用法语提供响应,则 406 代码可能是唯一正确的响应。
  • 用户代理可能请求服务器返回特定类型的内容。这些内容类型通常称为 MIME 类型,用于定义如纯文本(text/plain)、PNG 图像(image/png)、mp4 视频(video/mp4)等内容。因此,客户端可以在请求中包含 Accept 标头,并定义应由服务器提供的显式 MIME 类型(例如,Accept:application/xml)。如果服务器无法响应请求的匹配内容类型,则可能需要 406 Not Acceptable 响应。

在 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) 这个注解,后续问题就出现在这个注解上

  • controller层代码:
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);
    }
}

四、解决过程与方法

​ 基于这个问题,我在网上查询了许多解决方案,但是最终都未能解决我的这个问题…

这里先记录一下收集到的解决方案和尝试过程。

4.1 lombok注解

将返回的数据类型类中为所有属性添加get/set方法,或者添加@Data注解,或者添加@Setter和@Getter

响应406报错Resolved [HttpMediaTypeNotAcceptableException Could not find acceptable representation]_第3张图片

​ 这里我的统一返回值类中,其实原本就打上了 @Data 注解(内部包含@Getter 和 @Setter 注解),这里在给统一返回类打上了@Getter 和 @Setter 注解尝试着能不能解决。

​ 结果一样,还是会出现同样的 406 错误! 下面就尝试着 第二种解决方案:

4.2 设置响应格式Json

​ 方式二的尝试是通过响应格式的设置,这里猜测是响应格式导致的问题,所以又尝试通过下面两种方式来解决这个问题:

  • @RequestMapping 注解上进行调整:

响应406报错Resolved [HttpMediaTypeNotAcceptableException Could not find acceptable representation]_第4张图片

​ 这里将 Controller 层访问接口方法上的 @GetMapping注解进行了设置修改为:

@RequestMapping(value = “/queryList”,method = RequestMethod.GET,produces = “applications/json;charset=utf-8”)

​ 这里设置了响应格式 application/json;charset=utf-8 ,但是最后测试结果还是一样 406 没改变。

​ 于是在尝试着通过 responseBean 来设置 响应数据格式

  • 通过Response.setContentType() 设置响应格式:

    响应406报错Resolved [HttpMediaTypeNotAcceptableException Could not find acceptable representation]_第5张图片

    ​ 哈哈哈,通过我注释掉就能知道,效果一样,还是 406 。

    上面两种方式都是为了改变响应头,响应格式的修改,但是都是不起效,说明问题也不是出在这里。

4.3 406引发原因

​ 通过一系列的排查测试,最终找到 报错的原因了:

响应406报错Resolved [HttpMediaTypeNotAcceptableException Could not find acceptable representation]_第6张图片

​ 导致页面报错 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 +
                '}';
    }
}
  • 修改之后,调用接口,响应正常:

    响应406报错Resolved [HttpMediaTypeNotAcceptableException Could not find acceptable representation]_第7张图片

    问题解决了,下面就来了解一下关于这个 Lombok 提供的 @Accessors 注解的作用。

五、@Accessors注解

​ Accessors注解是 lombok 插件包中的一个注解,中文含义是存取器。

打开Accessors 的源码观察:

响应406报错Resolved [HttpMediaTypeNotAcceptableException Could not find acceptable representation]_第8张图片

5.1 fluent 属性

​ 如果属性 fluent = true :则访问器将以字段命名,即我们的 Get / Set 方法将不在包含get/set前缀,在调用时,是直接使用字段的名称即可。

响应406报错Resolved [HttpMediaTypeNotAcceptableException Could not find acceptable representation]_第9张图片

如上图所示效果,我们在调用get / set方法时 ,就直接只用属性名称即可,不在添加 set /get 前缀!

5.2 chain属性

​ **默认为false(注:但是当fluent为true时,其默认为true),**生成的setter方法是void类型;如果设置为true生成的setter方法返回this(当前对象)。

​ 这就意味着,我们可以通过 set方式对操作对象进行链式编程了,即在调用set方法之后,还是返回其对象,然后就能直接进行下一步对象的操作,从而实现链式编程。

响应406报错Resolved [HttpMediaTypeNotAcceptableException Could not find acceptable representation]_第10张图片

5.3 prefix属性

​ 可以指定前缀,生成getter和setter方法时会去掉指定的前缀(遵守驼峰命名)。

​ 相当于字符串截取功能,在生成getter和setter方法的时候,会自动截取去除指定前缀,然后加上get与set。

​ 请注意,仅当前一个字符不是小写字符或前缀的最后一个字母不是字母(例如下划线)时,前缀才算数。如果在去除前缀时多个字段都变成同一个名称,则会生成错误。

5.4 makeFinal属性

​ 如果为 true,则生成的访问器将被标记为 final。 默认值:false

​ 返回:是否应将访问器标记为 final。

你可能感兴趣的:(开发实际问题,java,http)