草庐IT

day11-JSON处理和HttpMessageConverter<T>

liyuelian 2023-04-16 原文

JOSN处理和HttpMessageConverter< T>

1.JSON处理-@ResponseBody

说明:在实际开发中,我们往往需要服务器返回的数据都是 JSON 格式。

SpringMVC 提供了 @ResponseBody 注解,用来标注 Controller 方法的返回的格式为 JSON,将 Java 对象或集合转为 JSON 格式的数据。

方法返回的对象通过适当的转换器转换为指定的格式之后,写入到 response 对象的 body 区,通常用来返回 JSON 数据或者是 XML 数据。

注意:在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,它的效果等同于通过 response对象输出指定格式的数据。

使用案例

下面是要完成的效果:请求目标方法,目标方法返回一个json格式的数据

(1)引入处理JSON需要的jar包,注意spring5.x 需要使用jackson-2.9.x.jar 的包。

(2)创建 json.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>json提交</title>
    <!--引入jquery-->
    <script type="text/javascript" src="script/jquery-3.6.0.min.js"></script>
    <!--使用jquery的ajax-->
    <script type="text/javascript">
        $(function () {
            //给id=getJson的超链接绑定一个事件
            $("#getJson").click(function () {
                var href = this.href;
                var args = {"time": new Date()};
                $.get(
                    href,//请求url
                    args,//发送时间,防止浏览器缓存
                    function (data) {
                        console.log("data=", data)
                    },
                    "json"//指定返回的格式
                )
                return false;//超链接不跳转
            })
        })
    </script>
</head>
<body>
<h1>请求一个json数据</h1>
<a href="<%=request.getContextPath()%>/json/dog" id="getJson">点击获取json数据</a>
</body>
</html>

(3)创建 Javabean 作为返回的数据

package com.li.web.json.entity;

/**
 * @author 李
 * @version 1.0
 */
public class Dog {
    private String name;
    private String address;

    public Dog() {
    }

    public Dog(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

(4)创建 JsonHandler 处理请求

package com.li.web.json;

import com.li.web.json.entity.Dog;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author 李
 * @version 1.0
 */
@Controller
public class JsonHandler {
    /**
     * 1.目标方法的 @ResponseBody注解表示返回的数据是json格式
     * 2.SpringMVC 底层根据目标方法@ResponseBody,返回指定格式
     *  返回的格式根据http请求来处理
     * 3.底层原理之前在实现SpringMVC底层机制时讲过,
     *  这里原生的SpringMVC使用了转换器 HttpMessageConverter
     * @return
     */
    @RequestMapping(value = "/json/dog")
    @ResponseBody
    public Dog getJson(){
        //返回对象
        //SpringMVC 会根据你的设置,转成json格式数据返回
        Dog dog = new Dog();
        dog.setName("大黄狗");
        dog.setAddress("小新的家");
        return dog;
    }
}

(5)启动tomcat,访问json.jsp页面。点击超链接,返回如下信息:

2.JSON处理-@RequestBody

和 @ResponseBody 注解相反,@RequestBody 注解是将客户端提交的 json数据,封装成 Javabean 对象。

注意:@RequestBody 用于修饰参数。

应用案例

在前端页面发出一个json数据,后端接收数据,并使用 @RequestBody 注解将 json 数据转成 Javabean 对象,然后使用 @ResponseBody 注解将该 Javabean 对象转回 json 数据,返回给前端。

(1)修改json.jsp,增加发送 json 数据代码

这里使用 ajax 请求的 contentType 指定发送格式为 json,发送请求时会被封装到请求头中。这样后端在接收时,根据 contentType 能知道数据是 json 格式的。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>json提交</title>
    <!--引入jquery-->
    <script type="text/javascript" src="script/jquery-3.6.0.min.js"></script>
    <!--使用jquery的ajax-->
    <script type="text/javascript">
        $(function () {
            //绑定按键点击事件,提交json数据
            $("button[name='but1']").click(function () {
                var url = "/springmvc/save2";
                var username = $("#username").val();
                var age = $("#age").val();
                //将 username和 age封装成 json字符串
                var args = {"username": username, "age": age};//json对象
                //将json对象封装成json字符串
                var jsonString = JSON.stringify(args);//json字符串
                $.ajax({
                    url:url,
                    data:jsonString,
                    type:"POST",
                    success:function (data) {
                        console.log("返回的data=",data)
                    },
                    //指定发送数据的编码和格式
                    contentType:"application/json;charset=utf-8"
                })
            })
        })
    </script>
</head>
<body>
<h1>发出一个json数据</h1>
u:<input id="username" type="text"/><br/>
a:<input id="age" type="text"/><br/>
<button name="but1">添加用户</button>
</body>
</html>

(2)User.java

package com.li.web.json.entity;

/**
 * @author 李
 * @version 1.0
 */
public class User {
    private String username;
    private Integer age;

    public User() {
    }

    public User(String username, Integer age) {
        this.username = username;
        this.age = age;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", age=" + age +
                '}';
    }
}

(3)JsonHandler.java

package com.li.web.json;

import com.li.web.json.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author 李
 * @version 1.0
 */
@Controller
public class JsonHandler {
    /**
     * 1.在形参指定了 @RequestBody 注解
     * 2.SpringMVC 就会将提交的json字符串数据填充给指定的Javabean
     * @param user 指定的Javabean对象
     * @return 这里使用了 @ResponseBody 注解,因此返回的也是json
     */
    @RequestMapping(value = "/save2")
    @ResponseBody
    public User save2(@RequestBody User user) {
        //将前端传过来的数据以json的格式返回浏览器
        System.out.println("user=" + user);
        return user;
    }
}

(4)启动tomcat,访问json.jsp,提交和返回的数据如下:

后台输出:

说明后端成功拿到了前端发送的 json 数据,并将其填充到了指定的Javabean对象中。然后又将 Javabean 对象转成了 json 格式的数据返回前端。

3.JSON处理-注意事项和细节

  1. 目标方法正常返回 Json 需要的数据,可以是一个对象,也可以是一个集合

  2. @ResponseBody 注解也可以用于修饰于 Controller 上,这样会对 Controller 中所有方法生效

  3. @ResponseBody 和 @Controller 可以直接写成一个注解 @RestController

4.HttpMessageConverter< T>

  • 基本说明

SpringMVC 处理 JSON 底层实现是依靠 HttpMessageConverter<T> 来进行转换的

  • 工作机制简图

整个过程为:

请求报文发送到后端,HttpInputMessage 的实现子类会将请求报文封装起来,然后找到对应的转换器进行转换,然后封装成对应的 Java 对象给到目标方法。

目标方法返回,找到指定格式对应的消息转换器,消息转换器将返回的数据再进行转换,将转换后的数据封装到 HttpOutputMessage ,然后返回给客户端。

这里的 HttpMessageConverter(消息转换器),HttpInputMessage 和 HttpOutputMessage 都是接口,下面有很多实现子类,会根据请求/响应报文头来找到匹配子类。

  • 处理JSON-底层实现(HttpMessageConverter< T>)
  1. 使用 HttpMessageConverter<T> 将请求信息转换并绑定到处理方法的入参中,或将响应结果转为对应类型的响应信息,Spring 提供了两种途径:
    • 使用 @RequestBody / @ResponseBody 对目标方法或方法参数进行标注
    • 使用 HttpEntity<T> / ResponseEntity<T> 作为目标方法的入参或返回值
  2. 当控制器处理方法使用到 @RequestBody / @ResponseBodyHttpEntity<T> / ResponseEntity<T> 时,Spring 首先根据请求头或响应头的 Accept 属性选择匹配的 HttpMessageConverter,进而根据参数类型泛型类型的过滤得到匹配的消息转换器 HttpMessageConverter,若找不到可用的消息转换器,将会报错。

debug源码

以处理JSON-@RequestBody的例子为案例:

(1)快捷键 ctrl+n 在 IDEA 中搜索 AbstractJackson2HttpMessageConverter 类,在该类的 readJavaType() 方法中打上断点,点击 debug

(2)浏览器访问 json.jsp 页面,填写数据并点击按钮

(3)后台光标跳转到断点处,此时方法参数 inputMessage 的数据如下,说明此时 http 请求的内容已经被封装到了给对象中。

方法参数 javaType 用于指定 inputMessage 的相应数据填充到什么样的 Java 类型中

(4)点击 step over,方法首先获取http请求数据指定的类型 contentType

(5)在获取了指定的数据类型后,将数据进行转换

(6)点击 step over:

(7)在目标方法中添加断点,点击 resume,可以看到光标跳转到断点处,此时目标方法就拿到了由 json 数据填充后的 Javabean 对象

(8)在 AbstractJackson2HttpMessageConverter 类的 writeInternal() 方法中打上断点,点击 resume,光标跳转到如下位置:

由于我们在目标方法使用了 @ResponseBody 注解,因此返回的时候不再会进入视图处理器,而是通过消息转换器,将返回的 user 对象转换为指定的 json 格式数据。下图的 HttpOutputMessage 对象就是用于存放转换后的数据。

获取要转换的格式:

使用 selectObjectMapper 方法将对象转换为指定格式:

后面就是进行一些处理,然后将数据返回给客户端。

(9)然后浏览器获得指定格式的数据:

5.文件下载-ResponseEntity< T>

  • 说明

在SpringMVC 中,通过返回 ResponseEntity<T> 的类型,可以实现文件的下载功能

使用 ResponseEntity< T> 下载文件,底层仍然是上面 HttpMessageConverter< T> 的机制。

使用案例

实现效果如下:在前端页面点击超链接,可以实现文件下载。

在项目的web目录下创建一个img目录,放入一张图片作为要下载的文件

(1)修改 json.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>文件下载</title>
</head>
<body>
<h1>下载文件的测试</h1>
<a href="<%=request.getContextPath()%>/downFile">点击下载文件</a>
</body>
</html>

(2)修改 JsonHandler.java,增加方法,用于响应下载文件的请求

文件下载响应头的设置:

  • content-type:设置响应内容的格式

  • content-disposition:设置如何处理响应的内容,一般有两种方式:

    • inline:直接在页面显示
    • attchment:以附件形式下载
package com.li.web.json;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author 李
 * @version 1.0
 */
@Controller
public class JsonHandler {
    /**
     * 这里的构建形参session主要是为了获取输出流
     *
     * @param session
     * @return
     */
    @RequestMapping(value = "/downFile")
    public ResponseEntity<byte[]> downFile(HttpSession session) throws IOException {
        //先获取下载文件的 InputStream
        InputStream resourceAsStream =
                session.getServletContext().getResourceAsStream("/img/view.jpg");
        //开辟 byte 数组
        //resourceAsStream.available() 返回可读取的字节数的估计值
        byte[] bytes = new byte[resourceAsStream.available()];
        //将要下载的文件数据保存到 bytes 数组
        resourceAsStream.read(bytes);
        /**
         * ResponseEntity 的构造器:
         * public ResponseEntity(@Nullable T body,
         *                      @Nullable MultiValueMap<String, String> headers,
         *                      HttpStatus status)
         *     {
         *        this(body, headers, (Object) status);
         *     }
         *  body 就是要回送的数据内容
         *  headers 就是回送数据的编码以及应该以何种方式进行处理
         *  status 代表状态
         */
        //1.创建返回的 HttpStatus status
        HttpStatus httpStatus = HttpStatus.OK;
        //2.创建 headers,指定浏览器应当以附件形式操作返回的数据
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Content-Disposition", "attachment;filename=view.jpg");
        //3.构建 ResponseEntity 对象
        ResponseEntity<byte[]> responseEntity =
                new ResponseEntity<>(bytes, httpHeaders, httpStatus);
        return responseEntity;
    }
}

(3)测试,点击下载链接,显示成功下载图片

tomcat 真正的工作目录是 out,要保证 out 目录下存在该文件,才能下载成功

6.练习

  1. 将之前的数据格式化、验证以及国际化、json 处理、文件下载相关代码自己过一遍
  2. 将 debug 过的 HttpMessageConverter 源码再走一遍,加深理解
  3. 画出数据类型转换校验核心类 DataBinder 的工作机制示意图
  4. debug 一下 validate 得到验证 errors 信息,加深理解

有关day11-JSON处理和HttpMessageConverter<T>的更多相关文章

  1. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  2. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  3. ruby-on-rails - Rails HTML 请求渲染 JSON - 2

    在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这

  4. ruby-on-rails - rspec should have_select ('cars' , :options => ['volvo' , 'saab' ] 不工作 - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion在首页我有:汽车:VolvoSaabMercedesAudistatic_pages_spec.rb中的测试代码:it"shouldhavetherightselect"dovisithome_pathit{shouldhave_select('cars',:options=>['volvo','saab','mercedes','audi'])}end响应是rspec./spec/request

  5. ruby-on-rails - Nokogiri:使用 XPath 搜索 <div> - 2

    我使用Nokogiri(Rubygem)css搜索寻找某些在我的html里面。看起来Nokogiri的css搜索不喜欢正则表达式。我想切换到Nokogiri的xpath搜索,因为这似乎支持搜索字符串中的正则表达式。如何在xpath搜索中实现下面提到的(伪)css搜索?require'rubygems'require'nokogiri'value=Nokogiri::HTML.parse(ABBlaCD3"HTML_END#my_blockisgivenmy_bl="1"#my_eqcorrespondstothisregexmy_eq="\/[0-9]+\/"#FIXMEThefoll

  6. ruby-on-rails - 如何使用 Rack 接收 JSON 对象 - 2

    我有一个非常简单的RubyRack服务器,例如:app=Proc.newdo|env|req=Rack::Request.new(env).paramspreq.inspect[200,{'Content-Type'=>'text/plain'},['Somebody']]endRack::Handler::Thin.run(app,:Port=>4001,:threaded=>true)每当我使用JSON对象向服务器发送POSTHTTP请求时:{"session":{"accountId":String,"callId":String,"from":Object,"headers":

  7. ruby - 安装libv8(3.11.8.13)出错,Bundler无法继续 - 2

    运行bundleinstall后出现此错误:Gem::Package::FormatError:nometadatafoundin/Users/jeanosorio/.rvm/gems/ruby-1.9.3-p286/cache/libv8-3.11.8.13-x86_64-darwin-12.gemAnerroroccurredwhileinstallinglibv8(3.11.8.13),andBundlercannotcontinue.Makesurethat`geminstalllibv8-v'3.11.8.13'`succeedsbeforebundling.我试试gemin

  8. ruby - 用 YAML.load 解析 json 安全吗? - 2

    我正在使用ruby2.1.0我有一个json文件。例如:test.json{"item":[{"apple":1},{"banana":2}]}用YAML.load加载这个文件安全吗?YAML.load(File.read('test.json'))我正在尝试加载一个json或yaml格式的文件。 最佳答案 YAML可以加载JSONYAML.load('{"something":"test","other":4}')=>{"something"=>"test","other"=>4}JSON将无法加载YAML。JSON.load("

  9. ruby-on-rails - 没有参数的 `<<`(小于两倍)是什么意思? - 2

    我在一个我想在formtasticGem中覆盖的方法中找到了这个。该方法如下所示:defto_htmlinput_wrappingdohidden_field_html是什么意思?在第三行做什么?我知道它对数组有什么作用,但在这里我不知道。 最佳答案 你可以这样读:hidden_field_htmllabel_with_nested_checkbox是连接到hidden_​​field_html末尾的参数-为了“清晰”,他们将其分成两行 关于ruby-on-rails-没有参数的`

  10. ruby-on-rails - 找不到 gem railties (>= 0.a) (Gem::GemNotFoundException) - 2

    我已经看到了一些其他的问题,尝试了他们的建议,但没有一个对我有用。我已经使用Rails大约一年了,刚刚开始一个新的Rails项目,突然遇到了问题。我卸载并尝试重新安装所有Ruby和Rails。Ruby很好,但Rails不行。当我输入railss时,我得到了can'tfindgemrailties。我当前的Ruby版本是ruby2.2.2p95(2015-04-13修订版50295)[x86_64-darwin15],尽管我一直在尝试通过rbenv设置ruby​​2.3.0。如果我尝试rails-v查看我正在运行的版本,我会得到同样的错误。我使用的是MacOSXElCapitan版本10

随机推荐