Spring MVC 使用原生 Servlet

1. 前言

本节课程和大家一起聊聊在 Spring MVC 中如何使用原生 Servlet API。通过本节课程,你将学习到:

  • 使用 Servlet API 作为控制器方法的入参;
  • 使用 Spring MVCServlet API 代理类作为控制器方法的入参;
  • 使用 IO 流对象作为控制器方法的入参。

本章节重点在于理解 Spring MVC 高级组件与低级组件之间的关系。

2. Servlet API

使用 Spring MVC 框架,可以给开发者带来诸多便利性,如自动绑定请求包中的数据、自动数据验证……

Spring MVC 构建的 WEB 项目中,其实也可以直接使用原生的 Servlet API 。但在真实项目中,还是少用为好,毕竟项目是有工期的。

快速迭代是现代项目开发所追求的目标,这也是开发过程中选择框架的目的。但是,了解本章节的知识,对于更好的理解 Spring MVC 有很大的帮助。

先一起回忆几个常用的 Srevlet API:

  • HttpServletRequest: 用来处理请求包;
  • HttpServletResponse: 用来处理响应包;
  • HttpSession: 会话对象;
  • ServletConfig: 配置对象;
  • ServletContext: Servlet 上下文对象。

这几个 API 应该很熟悉。因为 Spring MVC 对原生 Servlet API 进行了高级封装,使用 Spring MVC 开发时,开发者不需要直接使用原生 API ,但并不意味着我们不再需要它们,只是换了一种方式使用而已,由 Spring MVC 内部组件使用它们。

图片描述

Spring MVC 虽然封装了底层 Servlet API 的使用,但并没有切断开发者直接对原生 Servlet API 的调用之路。对于需要使用原生 Servlet API 的场景,Spring MVC 提供了各种方便。

只需要在控制器的方法中声明需要使用的原生 API 类型做参数即可。

@RequestMapping("/testApi01")
public String hello(HttpServletRequest request,HttpServletResponse response) {	
	return "success";
}

这一切都应该要感谢 Spring 强大的依赖注入功能。可以说,在 Spring 的世界里,你想要什么就有什么。调用上面方法时,Spring 就会注入你所需要的 API 对象。至于在方法中如何使用它,相信你一定已经游刃有余。

如果你需要使用更多的原生 API。如上面代码一样,在方法参数中告诉 Spring 你所需要的 API 类型就可以了。

@RequestMapping("/testApi02")
public String hello(HttpSession session) {	
	return "success";
}

3. Servlet API 代理类

什么是 Servlet API 代理类?

Spring MVC 在它的 org.springframework.web.context.request 包中提供的有 WebRequest 接口,此接口可以用来代理原生 HttpServletRequest 的功能。

其本质就是给原生 HttpServletRequest 加了一个外壳,扩充了点功能,实质还是在使用 HttpServletRequest 的功能。这个接口或者说是与此类似的接口,大家可以作为常识性知识了解,在企业开发过程并不常用。

和使用原生 Servlet API 一样,只需要在控制器的方法中声明成参数便可。

@RequestMapping(value = "/testApi03")
public String hello(WebRequest request) {
 String userName = request.getParameter("userName");
 return "success";
}

4. 使用 IO 流

WEB 程序整体上是 B / S 结构,本质上是基于底层网络的程序,是 C / S 结构体系的升级。回忆一下网络编程的几个基本要素:

  • 服务器端建立监听端口;
  • 客户端向监听端口发起网络连接;
  • 连接成功后建立起双向的网络流传输通道;
  • 彼此之间通过网络流通道进行数据交流。

程序的本质就是解决数据从哪里来、如何处理数据以及数据要去哪里的问题。基于网络的程序也是如此,其数据的来来往往是通过 IO 流实现的。

WEB 程序也是网络程序,数据的来去也是通过 IO 流实现。可能你会说,使用原生 Servlet 规范开发 WEB 项目时,你从没有接触或使用过这样的流、哪样的流。

那是因为无论你使用原生 Servlet API 还是使用 Spring MVC 开发 WEB ,两者已经帮你隐藏了其中的细节。

图片描述

HttpServletRequest 其本质就是封装网络输入流的操作。如果你不信,你可以查看 HttpServletRequest 的相关方法,会看到:

InputStream inStream=request.getInputStream();

触类旁通,此时你应该能理解到 HttpServletResponse 封装了网络输出流的相关操作。

OutputStream outputStream= response.getOutputStream();

那么,在 Spring MVC 中我们能不能在控制器的方法中直接引用底层的 IO 流对象?

当然可以!

TIps: Spring MVC 既可以把原生的 Servlet API 对象注入到控制器的方法中,也可以把更底层的 IO 流对象注入到控制器方法中。

4.1 注入 InputStream

在控制器的方法中声明 InputStream 作为入参,Spring MVC 就能注入你想要的 InputStream 对象。

@RequestMapping(value = "/testApi04",method = RequestMethod.POST)
public void hello(InputStream inputStream) throws IOException {
    byte[] buff=new byte[128];
	int read= inputStream.read(buff);
	System.out.println(new String(buff,0,read));
}

上面的实例,能读取到请求包中的数据,但过于低级,可读性并不是很好。

Tips: 控制器方法的映射机制有只接受 POST 方法的限制,如果是 GET 方法的请求包,直接使用 InputStream 对象无法获取到请求包中的数据。

GET 方法的请求数据是附加在 URL 上的,InputStream 只能读取实体部分的数据。

4.2 注入 OutputStream

在控制器的方法中注入 OutputStream 对象,只需要在方法中添加参数声明。

如下实例:可使用 OutputStream 对象读取指定文件中的内容后直接响应给浏览器。

@RequestMapping(value = "/testApi05")
public void hello(OutputStream outputStream) throws IOException {
	Resource res = new ClassPathResource("/test.txt");
	FileCopyUtils.copy(res.getInputStream(), outputStream);
}

test.txt 文件的内容是”this is a test’。文件直接放在项目的 src/main/java 目录下。

图片描述

在浏览器中输入请求路径 http://localhost:8888/sm-demo/testApi05 。你将在浏览器中看到:

图片描述

有句话叫做 “条条道路通罗马”,用在 Spring MVC 中真的是合适,依靠 Spring 强大的注入功能,只要原生开发中能有的对象基本上都能注入进去。

5. 小结

本节课程和大家讲解了在控制器方法中如何直接使用原生 Servlet API 和 更底层的 IO 流对象。

使用起来不难。但通过本节课程,希望大家能更透彻地理解 Spring MVC 和原生 Servlet API、网络编程之间的关系。

  • Servlet API 是 J2EE 拟定的 企业级 WEB 应用程序开发规范,可以认为是官方提出来的开发框架。对 WEB 开发过程中的更低级的网络流操作进行了封装;

  • Spring MVC 则是对原生 Servlet API 进一步进行了封装。一切只是为了让开发过程更高效。