SpringMVC
RESTful
概述
REST:Representational State Transfer,表现层资源状态转移。
表现层:前端的视图页面和后端的控制层就是表现层
资源
资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URI来标识。URI既是资源的名称,也是资源在Web上的地址。对某个资源感兴趣的客户端应用,可以通过资源的URI与其进行交互。
URI :统一资源标识符(Uniform Resource Identifier,URI)是一个用于标识某一互联网资源名称的字符串。
统一资源定位符(Uniform Resource Locator,URL),统一资源名称(Uniform Resource Name,URN)是URI的子集。
资源的表述
资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如 HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式。
状态转移
状态转移说的是:在客户端和服务器端之间转移(transfer)代表资源状态的表述。通过转移和操作资源的表述,来间接实现操作资源的目的。
RESTful 的实现
- 具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。
- 它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。
- REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。
操作 | 传统方式 | REST风格 |
---|---|---|
查询操作 | getUserById?id=1 | user/1–>get请求方式 |
保存操作 | saveUser | user–>post请求方式 |
删除操作 | deleteUser?id=1 | user/1–>delete请求方式 |
更新操作 | updateUser | user–>put请求方式 |
案例
-
该案例我写在 demo3 中(也可创建一个新的项目),大致需要的文件如下:
-
创建 user.html 并编写用来测试的表单和超链接
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>使用RESTFul模拟操作用户资源(增删改查)</title> </head> <body> <h1>使用RESTFul模拟操作用户资源(增删改查)</h1> <a th:href="@{/user}">查询所有用户信息</a><br/> <a th:href="@{/user/1}">根据用户ID查询用户信息</a><br/> <form method="post" th:action="@{/user}"> 用户名:<input type="text" name="username"><br/> 密码:<input type="text" name="password"><br/> <input type="submit" value="添加用户信息"> </form> <form method="put" th:action="@{/user}"> 用户名:<input type="text" name="username"><br/> 密码:<input type="text" name="password"><br/> <input type="submit" value="根据用户ID修改用户信息"> </form> <form method="delete" th:action="@{/user}"> 用户ID:<input type="text" name="id"><br/> <input type="submit" value="根据用户ID删除对应用户信息"> </form> </body> </html>
-
编写对应的控制器方法
package com.laoyang.mvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; /** * @ClassName UserController * @Description: 使用RESTFul模拟操作用户资源(增删改查) * @Author Laoyang * @Date 2022/1/13 15:53 */ @Controller public class UserController { /** * 查询所有用户信息 * /user ---> GET */ @RequestMapping(value = "/user", method = RequestMethod.GET) public String findAllUser() { System.out.println("查询所有用户信息"); return "success"; } /** * 根据用户ID查询用户信息 * /user/1 ---> GET */ @RequestMapping(value = "/user/{id}", method = RequestMethod.GET) public String findByUserId(@PathVariable("id") Integer id) { System.out.println("id=" + id); System.out.println("根据用户ID查询用户信息"); return "success"; } /** * 添加用户信息 * /user ---> POST */ @RequestMapping(value = "/user", method = RequestMethod.POST) public String saveUser(String username, String password) { System.out.println("username=" + username + ";password=" + password); System.out.println("添加用户信息"); return "success"; } /** * 根据用户ID修改用户信息 * /user ---> PUT */ @RequestMapping(value = "/user", method = RequestMethod.PUT) public String updateUser(String username, String password) { System.out.println("username=" + username + ";password=" + password); System.out.println("根据用户ID修改用户信息"); return "success"; } /** * 根据用户ID删除对应用户信息 * /user/1 ---> DELETE */ @RequestMapping(value = "/user", method = RequestMethod.DELETE) public String deleteUser(Integer id) { System.out.println("id=" + id); System.out.println("根据用户ID删除对应用户信息"); return "success"; } }
-
在 spring-mvc.xml 文件中配置页面跳转
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 扫描组件 --> <context:component-scan base-package="com.laoyang.mvc" /> <!-- 配置 Thymeleaf 视图解析器 --> <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <!-- 优先级 --> <property name="order" value="1"/> <!-- 字符编码 --> <property name="characterEncoding" value="UTF-8"/> <!-- 模板 --> <property name="templateEngine"> <bean class="org.thymeleaf.spring5.SpringTemplateEngine"> <property name="templateResolver"> <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <!-- 视图前缀 --> <property name="prefix" value="/WEB-INF/templates/"/> <!-- 视图后缀 --> <property name="suffix" value=".html"/> <!-- 模板模型 --> <property name="templateMode" value="HTML5"/> <!-- 页面编码格式 --> <property name="characterEncoding" value="UTF-8"/> </bean> </property> </bean> </property> </bean> <!-- path:设置处理的请求地址,该路径是和 @RequestMapping 注解中的地址是一样的 简单理解:就是把 controller 中用来跳转页面的控制器方法写在了配置文件中 view-controller:设置请求地址所对应的视图名称 --> <!-- 访问方式:http://localhost:8080/springmvc/ --> <mvc:view-controller path="/" view-name="index"/> <!-- 访问方式:http://localhost:8080/springmvc/doView --> <mvc:view-controller path="/doView" view-name="view" /> <!-- 访问方式:localhost:8080/springmvc/doUser,这一步是新增的代码 --> <mvc:view-controller path="/doUser" view-name="user"/> <!-- 开启 mvc 的注解驱动 --> <mvc:annotation-driven /> </beans>
-
启动Tomcat查看效果(访问localhost:8080/springmvc/doUser)
-
查询所有用户
-
根据用户ID查询对应的用户信息
-
添加用户信息
以上三个都是可以正常访问的,但是浏览器效果都是跳转到 success 页面,所以大家看控制台打印就可以看那个方法被执行了。
-
根据用户ID修改用户信息
-
根据用户ID删除对应用户信息
这里截的图是修改的效果,目前是不满足我们的需求的,删除效果和这个差不多,就不另做演示了
-
-
问题说明: 因为修改和删除的表单提交的请求方式是 put 和 delete,而浏览器基本上都只支持 get 和 post 请求,所以会导致无法通过 put 或 delete 方式进行处理,所以遇到这种情况的时候,浏览器默认会以 get 请求方式进行处理,所以修改和删除所映射到的控制器方法是 GET 类型的(查询所有用户,可以观察控制台打印的语句)
-
解决方案: 可注册 HiddenHttpMethodFilter 过滤器处理除 get 和 post 之外的几种请求方式,通过这个过滤器就可以解决刚才的问题
这里我先简单说明一下,如果想要在了解一些东西可以看后面的 HiddenHttpMethodFilter 解析
-
在 web.xml 中配置 HiddenhttpMethodFilter
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- 配置HiddenHttpMethodFilter过滤器 --> <filter> <filter-name>hidden</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <!-- 注册前端控制器 DispatcherServlet --> <servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 初始化参数 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <!-- 初始化前端控制器,将前端控制器 DispatcherServlet 的初始化时间提前到服务器启动时 --> <load-on-startup>1</load-on-startup> </servlet> <!-- 设置字符编码格式 注意:设置编码格式之前不可以获取任何请求参数,如果获取了,就会导致设置的字符编码失效!从而导致拿到的数据是乱码。 > 所以字符编码的 filter-mapping 标签一定要放在其它 filter-mapping 标签前面!!! --> <filter> <filter-name>encoding-filter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <!-- 指定要使用的编码格式 --> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <!-- 强制使用我们设置的编码格式,默认是false(不强制) --> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <!-- 字符编码的处理规则,必须在其他处理规则之前 --> <filter-mapping> <filter-name>encoding-filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 前端控制器请求处理规则 --> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 请求方式的处理规则 --> <filter-mapping> <filter-name>hidden</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
注意: 编写 web.xml 文件的时候一定要注意,字符编码的处理规则一定要放在最上面!因为设置字符编码之前不可以获取到任何一个参数!如果你先让其它的处理规则先执行,那么这些请求规则就可能会带有参数,一旦带有参数,就会导致我们所设置的字符编码失效(简单来说,就是即使我们设置了字符编码,拿到的数据也还是乱码!),所以千万要注意!!!
Ps:web.xml 文件基本上的配置就是这些,两个过滤器,一个 Servlet,只要是使用 SpringMVC,这几个配置就不能少!
-
配置完之后还需要将提交方式修改为 POST,并且传一个参数
_method
,这个参数的值就是我们最终的提交方式,HiddenHttpMethodFilter 会根据这个参数进行处理<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>使用RESTFul模拟操作用户资源(增删改查)</title> </head> <body> <h1>使用RESTFul模拟操作用户资源(增删改查)</h1> <form method="post" th:action="@{/user}"> <!-- 因为这个参数只是给过滤器进行处理的,所以我们使用隐藏域来传,这样就不会影响到用户使用了 --> <input type="hidden" name="_method" value="PUT"> 用户名:<input type="text" name="username"><br/> 密码:<input type="text" name="password"><br/> <input type="submit" value="根据用户ID修改用户信息"> </form> <form method="post" th:action="@{/user}"> <input type="hidden" name="_method" value="DELETE"> 用户ID:<input type="text" name="id"><br/> <input type="submit" value="根据用户ID删除对应用户信息"> </form> </body> </html>
-
再次测试
这个时候在看控制台,就是打印我们修改方法和删除方法中的数据了
HiddenHttpMethodFilter 解析
为什么要使用 HiddenHttpMethodFilter
-
由于浏览器只支持发送 get 和 post 方式的请求,所以我们需要配置这个过滤器来让浏览器能够使用我们指定的方式进行处理,那么该如何发送 put 和 delete 请求呢?
-
SpringMVC 提供了 HiddenHttpMethodFilter 帮助我们将 POST 请求转换为 DELETE 或 PUT 请求 HiddenHttpMethodFilter 处理put和delete请求的条件:
-
当前请求的请求方式必须为post
-
当前请求必须传输请求参数
_method
,并且该参数的值必须是可兼容的(PUT、DELETE、PATCH,如有改动可自行查看源码)
满足以上条件,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为请求参数
_method
的值,因此请求参数_method
的值才是最终的请求方式 -
部分源码
- 大家可根据我在源码上标注的注释进行理解
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
/**
* request 和 response 是我们拦截的请求和响应
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletRequest requestToUse = request;
/*
request.getMethod():获取当前所拦截的请求的请求方式
理解:如果当前的请求方式为 POST,并且没有任何错误信息,则进行相关的操作
*/
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
// 获取当前请求的请求参数(默认为:_method)
String paramValue = request.getParameter(this.methodParam);
// 如果该值的长度不为0,则进行相关的操作
if (StringUtils.hasLength(paramValue)) {
// 将请求参数的值转换为大写
String method = paramValue.toUpperCase(Locale.ENGLISH);
/*
如果转换为大写之后的请求参数是ALLOWED_METHODS中的某一个,那么就可以进行使用(PUT、DELETE、PATCH)
如果不是ALLOWED_METHODS中的值,则无法实现我们想要的效果
> 比如我们提交方式为QWE,因为ALLOWED_METHODS中并没有QWE,所以就无法以QWE方式发送请求
*/
if (ALLOWED_METHODS.contains(method)) {
// 将当前请求的请求方式替换成我们需要的那种(比如把POST替换成PUT,然后在以PUT方式发送请求)
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
// 给当前请求放行
filterChain.doFilter(requestToUse, response);
}
}
SpringMVC 过滤器说明
-
目前为止,SpringMVC中提供了两个过滤器:
CharacterEncodingFilter
和HiddenHttpMethodFilter
-
在 web.xml 中注册时,必须先注册 CharacterEncodingFilter,再注册HiddenHttpMethodFilter
原因:
-
在 CharacterEncodingFilter 中是通过
request.setCharacterEncoding(encoding)
方法设置字符集的; request.setCharacterEncoding(encoding) 方法要求前面不能有任何获取请求参数的操作 -
而 HiddenHttpMethodFilter 恰恰有一个获取请求参数的操作:
String paramValue = request.getParameter(this.methodParam);
-
HttpMessageConverter
- 学习前可先创建一个新的项目工程,用来测试对应的案例代码,我这里的工程名为:
springmvc-demo4
web.xml 和 pom.xml 文件里面的内容复制过来就行
说明
- HttpMessageConverter:
报文信息转换器
,将请求报文转换为 Java 对象,或将 Java 对象转换为响应报文。 - HttpMessageConverter 提供了两个注解和两个类型:
@RequestBody
,@ResponseBody
,RequestEntity
,ResponseEntity
@RequestBoyd
@RequestBody 可以获取请求体,需要在控制器方法设置一个形参,使用 @RequestBody 进行标识,当前请求的请求体就会为当前注解所标识的形参赋值。
案例
-
编写web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- 配置编码过滤器 --> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 配置 PUT 和 DELETE 请求方式的过滤器 --> <filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--注册SpringMVC前端控制器--> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
-
编写spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 开启组件扫描 --> <context:component-scan base-package="com.laoyang.mvc" /> <!-- 配置 Thymeleaf 视图解析器 --> <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <!-- 优先级 --> <property name="order" value="1"/> <!-- 字符编码 --> <property name="characterEncoding" value="UTF-8"/> <!-- 模板 --> <property name="templateEngine"> <bean class="org.thymeleaf.spring5.SpringTemplateEngine"> <property name="templateResolver"> <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <!-- 视图前缀 --> <property name="prefix" value="/WEB-INF/templates/" /> <!-- 视图后缀 --> <property name="suffix" value=".html"/> <!-- 模板模型 --> <property name="templateMode" value="HTML5"/> <!-- 页面编码格式 --> <property name="characterEncoding" value="UTF-8"/> </bean> </property> </bean> </property> </bean> <!-- 配置视图控制器 --> <mvc:view-controller path="/" view-name="index" /> <!-- 开放对静态资源的访问 --> <mvc:default-servlet-handler /> <!-- 开启mvc注解驱动 --> <mvc:annotation-driven /> </beans>
-
编写 index.html 页面
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <h1>首页</h1> <form method="post" th:action="@{/testRequestBody}"> 用户名:<input type="text" name="username"><br/> 密码:<input type="text" name="password"><br/> <input type="submit" value="测试@RequestBody注解"> </form> </body> </html>
-
编写 success.html 页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>主页面</title> </head> <body> <h1>主页面</h1> </body> </html>
-
编写对应的控制器方法
package com.laoyang.mvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; /** * @ClassName HttpController * @Description: 测试 @RequestBody 注解和 RequestEntity 类型 * @Author Laoyang * @Date 2022/1/15 15:52 */ @Controller public class RequestController { /** * 测试 @RequestBody 注解 */ @RequestMapping(value = "/testRequestBody") public String testRequestBody(@RequestBody String requestBody) { // 因为使用了 @RequestBody 注解,所以这里的 requestBody 就可以获取到页面两个文本框的值 System.out.println("------>" + requestBody); return "success"; } }
-
启动Tomcat进行测试
- 页面效果:成功跳转到了 success.html 页面
- 控制台效果:成功获取到了我们在浏览器文本框中输入的值
RequestEntity
RequestEntity 封装请求报文的一种类型,需要在控制器方法的形参中设置该类型的形参,当前请求的请求报文就会赋值给该形参,可以通过 getHeaders()
获取请求头信息,通过 getBody()
获取请求体信息。
案例
-
在 index.html 中编写表单
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <h1>首页</h1> <form method="post" th:action="@{/testRequestEntity}"> 用户名:<input type="text" name="username"><br/> 密码:<input type="text" name="password"><br/> <input type="submit" value="测试RequestEntity类型"> </form> </body> </html>
-
编写对应的控制器方法
package com.laoyang.mvc.controller; import org.springframework.http.RequestEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; /** * @ClassName HttpController * @Description: 测试 @RequestBody 注解和 RequestEntity 类型 * @Author Laoyang * @Date 2022/1/15 15:52 */ @Controller public class RequestController { /** * 测试 RequestEntity 类型 */ @RequestMapping(value = "/testRequestEntity") public String testRequestEntity(RequestEntity<String> requestEntity) { // 请求头获取的就是浏览器(F12)中看到的 System.out.println("请求头 --->" + requestEntity.getHeaders()); // 请求体获取的就是浏览器发送请求时带的参数 System.out.println("请求体 --->" + requestEntity.getBody()); return "success"; } }
-
启动Tomcat进行测试
-
请求头获取的就是浏览器(F12)中看到的,具体大家可看浏览器,或控制台打印的数据
-
@ResponseBody
@ResponseBody 用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器。
使用ServletAPI的response对象响应浏览器数据
-
在 index.html 中编写超链接
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <h1>首页</h1> <a th:href="@{/testResponse}">通过ServletAPI的response对象响应浏览器数据</a><br/> </body> </html>
-
编写对应控制器方法
package com.laoyang.mvc.controller; import com.laoyang.mvc.pojo.User; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @ClassName ResponseController * @Description: 测试 @ResponseBody 注解和 ResponseEntity 类型 * @Author Laoyang * @Date 2022/1/15 17:10 */ @Controller public class ResponseController { /** * 通过ServletAPI的response对象响应浏览器数据 */ @RequestMapping("/testResponse") public void testResponse(HttpServletResponse response) { try { // 设置编码格式 response.setContentType("text/html;charset=utf-8"); // 响应给浏览器的数据 response.getWriter().print("Hello 原生ServletAPI的response"); } catch (IOException e) { e.printStackTrace(); } } }
-
启动Tomcat查看效果
通过@ResponseBody注解响应浏览器数据(响应String类型的数据)
-
在 index.html 中编写超链接
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <h1>首页</h1> <a th:href="@{/testResponseBody}">通过@ResponseBody注解响应浏览器数据(String)</a><br/> </body> </html>
-
编写对应控制器方法
package com.laoyang.mvc.controller; import com.laoyang.mvc.pojo.User; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @ClassName ResponseController * @Description: 测试 @ResponseBody 注解和 ResponseEntity 类型 * @Author Laoyang * @Date 2022/1/15 17:10 */ @Controller public class ResponseController { /** * 通过@ResponseBody注解响应浏览器数据 - String数据 */ @RequestMapping("/testResponseBody") @ResponseBody public String testResponseBody() { /* 不加 @ResponseBody 注解就表示跳转到 success 页面 加上 @ResponseBody 注解则表示将该返回值响应给浏览器 */ return "success"; } }
-
启动Tomcat查看效果
使用 @ResponseBody 注解处理 json 数据
-
在 index.html 中编写超链接
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <h1>首页</h1 <a th:href="@{/testResponseUser}">通过@ResponseBody注解响应浏览器数据(User)</a><br/> </body> </html>
-
编写一个实体类用来配合测试
package com.laoyang.mvc.pojo; public class User { private Integer id; private String username; private String password; private Integer age; private String sex; // 创建对应的get/set 方法、有参构造器、全参构造器 }
-
编写对应控制器方法
package com.laoyang.mvc.controller; import com.laoyang.mvc.pojo.User; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @ClassName ResponseController * @Description: 测试 @ResponseBody 注解和 ResponseEntity 类型 * @Author Laoyang * @Date 2022/1/15 17:10 */ @Controller public class ResponseController { /** * 通过@ResponseBody注解响应浏览器数据 - User对象数据 */ @RequestMapping("/testResponseUser") @ResponseBody public User testResponseUser() { return new User(1001, "admin", "12345", 18, "男"); } }
-
启动Tomcat查看效果
报错原因:浏览器只能处理字符串类型的数据
解决方案:引入 json 相关依赖,并进行相关配置
解决方案的实现步骤
-
导入
jackson
的依赖<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.5</version> </dependency>
-
在 SpringMVC 的核心配置文件(spring-mvc.xml)中开启 mvc 的注解驱动,此时在HandlerAdaptor 中会自动装配一个消息转换器:
MappingJackson2HttpMessageConverter
,可以将响应到浏览器的 Java 对象转换为 Json 格式的字符串<mvc:annotation-driven />
在最开始的时候我们已经配好了,这里就可以不配了;如果没有配置,那么一定要加上
-
在处理器方法上使用 @ResponseBody 注解进行标识
package com.laoyang.mvc.controller; import com.laoyang.mvc.pojo.User; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @ClassName ResponseController * @Description: 测试 @ResponseBody 注解和 ResponseEntity 类型 * @Author Laoyang * @Date 2022/1/15 17:10 */ @Controller public class ResponseController { /** * 通过@ResponseBody注解响应浏览器数据 - User对象数据 */ @RequestMapping("/testResponseUser") @ResponseBody public User testResponseUser() { return null; } }
-
将 Java 对象直接作为控制器方法的返回值进行返回,就会自动转换为 Json 格式的字符串
package com.laoyang.mvc.controller; import com.laoyang.mvc.pojo.User; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @ClassName ResponseController * @Description: 测试 @ResponseBody 注解和 ResponseEntity 类型 * @Author Laoyang * @Date 2022/1/15 17:10 */ @Controller public class ResponseController { /** * 通过@ResponseBody注解响应浏览器数据 - User对象数据 */ @RequestMapping("/testResponseUser") @ResponseBody public User testResponseUser() { return new User(1001, "admin", "12345", 18, "男"); } }
-
启动Tomcat查看效果
使用 @ResponseBody 注解处理 ajax 请求
-
在 index.html 中编写超链接
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <h1>首页</h1> <div id="app"> <a th:href="@{testAxios}" @click="testAxios">SpringMVC处理ajax</a> </div> </body> </html>
-
通过vue和axios处理点击事件
-
需要先导入
vue.js
和axios.min.js
-
然后使用代码进行引用
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script> <script type="text/javascript" th:src="@{/static/js/axios.min.js}"></script> <script type="text/javascript"> var vue = new Vue({ el:"#app", methods:{ testAxios:function (event) { axios({ method:"post", url:event.target.href, params:{ username:"admin", password:"123456" } }).then(function (response) { alert(response.data); }); event.preventDefault(); } } }); </script>
-
-
编写对应控制器方法
package com.laoyang.mvc.controller; import com.laoyang.mvc.pojo.User; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @ClassName ResponseController * @Description: 测试 @ResponseBody 注解和 ResponseEntity 类型 * @Author Laoyang * @Date 2022/1/15 17:10 */ @Controller public class ResponseController { /** * 使用 @ResponseBody 注解处理 ajax 请求 */ @RequestMapping("/testAxios") @ResponseBody public String testAxios(String username, String password) { System.out.println(username + "--->" + password); return "Hello Axios"; } }
-
启动Tomcat查看效果
点击链接后就可以获取到控制器方法响应到浏览器的数据了,控制台中也可以看到 username 和 password 的数据了。
@RestController 注解
@RestController
注解是 SpringMVC 提供的一个复合注解,标识在控制器的类上,就相当于为类添加了 @Controller 注解,并且为其中的每个方法添加了 @ResponseBody 注解。
@RestController
public class ResponseController {
}
效果就相当于:
@Controller
public class ResponseController {
@ResponseBody
public String testAxios(String username, String password) {
System.out.println(username + "--->" + password);
return "Hello";
}
}
简单理解:@RestController 就相当于是 @Controller 和 @ResponseBody 的效果的结合
> 使用这一个注解,就可以实现这两个注解的效果
Ps:因为作用是一样的,所以这里就不演示了
ResponseEntity
- ResponseEntity 用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文。
- 通过 ResponseEntity 可以实现文件的上传和下载
文件下载
-
在 static 目录下创建 img 文件夹,然后导入一些文件
-
创建 file.html,编写超链接
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>文件上传与下载</title> </head> <body> <h1>文件上传与下载</h1> <a th:href="@{/download}">下载文件</a><br/> </body> </html>
-
编写对应控制器方法
package com.laoyang.mvc.controller; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import java.io.FileInputStream; import java.io.InputStream; /** * @ClassName FileAndDownController * @Description: 文件上传和下载 * @Author Laoyang * @Date 2022/1/16 16:36 */ @Controller public class FileAndDownController { /** * 文件下载 */ @RequestMapping("/download") public ResponseEntity<byte[]> download(HttpSession session) throws Exception { //获取ServletContext对象 ServletContext servletContext = session.getServletContext(); //获取服务器中文件的真实路径 String realPath = servletContext.getRealPath("/static/img/21.jpg"); //创建输入流 InputStream is = new FileInputStream(realPath); //创建字节数组(is.available():获取当前文件的字节数) byte[] bytes = new byte[is.available()]; //将流读到字节数组中 is.read(bytes); //创建HttpHeaders对象设置响应头信息 MultiValueMap<String, String> headers = new HttpHeaders(); //设置要下载方式以及下载文件的名字 headers.add("Content-Disposition", "attachment;filename=Digimon.jpg"); //设置响应状态码 HttpStatus statusCode = HttpStatus.OK; //创建ResponseEntity对象(参数分别是:请求体、请求头、响应状态码) ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode); //关闭输入流 is.close(); return responseEntity; } }
-
启动Tomcat查看效果
点击浏览器页面中的超链接,即可下载我们配置好的文件
文件上传
-
文件上传要求 form 表单的请求方式必须为 post,并且添加属性
enctype="multipart/form-data"
-
SpringMVC 中将上传的文件封装到 MultipartFile 对象中,通过此对象可以获取文件相关信息。
-
导入相关依赖
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency>
-
在 file.html 中编写表单进行文件上传
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>文件上传与下载</title> </head> <body> <h1>文件上传与下载</h1> <form th:action="@{/upload}" method="post" enctype="multipart/form-data"> <input type="file" name="photo"> <input type="submit" value="上传文件"> </form> </body> </html>
-
在 spring-mvc.xml 文件中配置文件上传解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
-
编写对应的控制器方法
package com.laoyang.mvc.controller; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.multipart.MultipartFile; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; /** * @ClassName FileAndDownController * @Description: 文件上传和下载 * @Author Laoyang * @Date 2022/1/16 16:36 */ @Controller public class FileAndDownController { /** * 文件上传 */ @RequestMapping("/upload") public String upload(MultipartFile photo, HttpSession session) throws IOException { // 获取上传的文件名 String fileName = photo.getOriginalFilename(); System.out.println(fileName); // 获取当前工程的上下文路径 ServletContext servletContext = session.getServletContext(); // 给定一个 URI,返回文件系统中 URI对应的绝对路径 String photoPath = servletContext.getRealPath("photo"); File file = new File(photoPath); // 判断 photoPath 所对应的路径是否存在 if (!file.exists()) { // 如果不存在,则创建对应目录 file.mkdirs(); } // 最终的上传地址 String finalPath = photoPath + File.separator + fileName; // 将文件上传到指定的地址下 photo.transferTo(new File(finalPath)); return "success"; } }
这里上传的最终目录设置在
springmvc-demo4\target\springmvc-demo4-1.0-SNAPSHOT
目录下 -
启动Tomcat查看效果
上传成功后可在自己项目的
target\springmvc-demo4-1.0-SNAPSHOT
目录下查看效果
文件上传的重名问题
- 如果指定的目录中有一个和我们即将要上传的文件名称一样的文件,那么就会导致新上传的文件覆盖原来的文件,从而导致一些不必要的问题(详细可了解 IO 流)。
使用 UUID 解决文件重名问题
package com.laoyang.mvc.controller;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
/**
* @ClassName FileAndDownController
* @Description: 文件上传和下载
* @Author Laoyang
* @Date 2022/1/16 16:36
*/
@Controller
public class FileAndDownController {
/**
* 文件上传
*/
@RequestMapping("/upload")
public String upload(MultipartFile photo, HttpSession session) throws IOException {
// 获取上传的文件名
String fileName = photo.getOriginalFilename();
// 截取文件后缀名(lastIndexOf:获取字符串中最后一个 . 的位置)
String suffix = fileName.substring(fileName.lastIndexOf("."));
// 将 UUID 作为文件名,保证唯一性
String uuid = UUID.randomUUID().toString();
// 将 UUID 和后缀名进行拼接,得到完整的文件名
String fullName = uuid + suffix;
// 获取当前工程的上下文路径
ServletContext servletContext = session.getServletContext();
// 给定一个 URI,返回文件系统中 URI对应的绝对路径
String photoPath = servletContext.getRealPath("photo");
File file = new File(photoPath);
// 判断 photoPath 所对应的路径是否存在
if (!file.exists()) {
// 如果不存在,则创建对应目录
file.mkdirs();
}
// 最终的上传地址
String finalPath = photoPath + File.separator + fullName;
// 将文件上传到指定的目录
photo.transferTo(new File(finalPath));
return "success";
}
}
一般来说,UUID是不会重名的