SpringMVC2️⃣响应&请求、RESTful
1、响应资源
响应内容有 2 种类型
- 页面跳转:通过字符串、通过 ModelAndView 对象
- 响应数据:普通字符串、JSON 字符串(对象、集合)
搭建环境
-
导入依赖,整合 Tomcat。
-
添加 web 框架支持,项目结构添加 lib 目录。
-
web.xml 中配置
DispatcherServlet
。<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>
-
创建
spring-mvc.xml
:开启注解扫描、自定义视图前后缀。<context:component-scan base-package="indi.jaywee.controller"/> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/jsp/"/> <property name="suffix" value=".jsp"/> </bean>
1.1、页面跳转
页面跳转的 2 种方式:字符串、ModelAndView 对象
注:两种方式都会与视图解析器的前后缀拼接后生成虚拟路径。
-
自定义前缀
/jsp/
,自定义后缀.jsp
。 -
视图名
hello
经过拼接,虚拟路径为/jsp/hello.jsp
。
1.1.1、字符串
@RequestMapping("/hello1")
public String helloJsp1() {
return "hello";
}
1.1.2、ModelAndView 对象
-
回顾 SpringMVC 流程:Handler 处理完请求后,返回的 ModelAndView。
-
使用方式
-
局部变量
-
方法形参:SpringMVC 会自动注入实参(推荐)
@RequestMapping("/hello2") public ModelAndView helloJsp2() { ModelAndView mv = new ModelAndView(); mv.setViewName("hello"); return mv; } @RequestMapping("/hello3") public ModelAndView helloJsp3(ModelAndView mv) { mv.setViewName("hello"); return mv; }
-
1.1.3、页面间转发数据
注:使用内部资源视图解析器的页面跳转方式是转发,而非重定向(属于外部跳转)
如何实现在页面转发时传递数据?
-
传统 Java Web 思路:通过
request作用域
@RequestMapping("/hello11") public String helloJsp11(HttpServletRequest req) { req.setAttribute("username", "Jaywee"); return "hello"; }
-
Spring MVC:通过 ModelAndView 的
addObject()
(还可使用
Model
或ModelMap
,此处不作展示)@RequestMapping("/hello22") public ModelAndView helloJsp22(ModelAndView mv) { mv.addObject("username", "Jaywee"); mv.setViewName("hello"); return mv; }
1.2、响应数据(❗)
响应数据的 2 种类型
- 普通字符串
- JSON 字符串:对象、集合
任何类型的参数都是以字符串的形式进行传输。
-
基本类型:表面上是直接传数值,实际上内部自动进行转换。
-
引用类型:发送端将对象转换为 JSON 字符串,接收端将 JSON 字符串解析成对象。
1.2.1、普通字符串
-
传统 Java Web:通过 response 的
getWriter()
。@RequestMapping("/data1") public void data1(HttpServletResponse resp) throws IOException { resp.getWriter().print("hello"); }
-
Spring MVC:使用
@ResponseBody
表示此方法返回字符串,而非视图。-
具体来说,此注解的作用是标识方法返回的内容作为响应报文的报文实体。
-
相比使用 resp.getWriter(),会设置默认编码为 ISO-8859-1。
-
涉及 HTTP 知识:Java Web 简单介绍 HTTP、计算机网络:HTTP协议。
@RequestMapping("/data2") @ResponseBody public String data2() { return "hello"; }
-
1.2.2、JSON 字符串(❗)
发送端将对象转换为 JSON 字符串,接收端将 JSON 字符串解析成对象。
示例:User(name, age)
-
手动拼接:麻烦,易出错(可自定义工具类,但不如使用第三方工具)
@RequestMapping("/json1") @ResponseBody public String jsonStr1() { User user = new User("Jaywee", "123456"); return "{\"name\":\"" + user.getName() + "\",\"password\":" + user.getPassword() + "}"; }
-
调用第三方工具的方法(如 FastJson、Jackson)
@RequestMapping("/json2") @ResponseBody public String jsonStr2() { User user = new User("Jaywee", "123456"); return JSON.toJSONString(user); }
-
配置 Jackson,方法返回值可直接声明为 POJO 类型。
-
导入依赖
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.13.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.13.1</version> </dependency>
-
spring-mvc 配置:处理器适配器
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> </list> </property> </bean>
-
使用:返回值直接声明为 User 类型。
@RequestMapping("/json3") @ResponseBody public User jsonStr3() { return new User("Jaywee", "123456"); }
-
-
SpringMVC 注解驱动(!)
-
以上的配置太繁琐,因此 SpringMVC 提供了注解驱动。
-
该配置自动载入 处理器映射器、处理器适配器(底层集成 Jackson)
-
建议为 POJO 添加 getter 和 setter,避免封装实体时出错。
-
使用方式同上,方法返回值声明为 POJO 类型。
<mvc:annotation-driven/>
-
1.2.3、@RestController
以上案例中,均使用
@Controller
+@ResponseBody
使方法返回字符串。@Controller public class xxxController { @RequestMapping("/xxx") @ResponseBody public String hello(){ return ""; } }
@RestController
相当于以上两个注解的结合。
-
使用:在 Controller 上使用,将 Controller 注册到 Spring 容器中。
-
说明:使用该注解的类,会忽略视图解析器配置,所有方法均会返回字符串,而非视图。
@RestController public class xxxController { @RequestMapping("/xxx") public String hello(){ return ""; } }
2、请求:获取参数(❗)
请求的参数类型分为 3 类
-
简单类型
- 基本类型、字符串类型
- 简单类型数组
-
POJO 类型
-
集合类型
说明
- 参数格式:默认情况下,客户端请求参数是以键值对格式提交(
name=value&name=value
) - 类型转换:SpringMVC 会将请求参数进行类型转换(如 int 转 String)。
- 注解:
@RequestParam
、@RequestBody
2.1、简单类型
请求参数的 name 属性与 Controller 的方法参数名一致。
两种实现方式
- 方法参数名设置成与 name 属性同名。
- 方法参数使用
@RequestParam
映射的 name 属性(此时参数名可任意)
2.1.1、基本类型、字符串类型
以字符串类型为例
-
页面:注意表单项的 name 属性
用户<input type="text" name="name"><br> 密码<input type="password" name="password"> <input type="submit">
-
Controller:设置方法参数名或使用
@RequestParam
@RequestMapping("/login1") public void login1(String name, String password) { System.out.println(name + ": " + password); } @RequestMapping("/login11") public void login11(@RequestParam("name") String username, String password) { System.out.println(username + ": " + password); }
2.1.2、简单类型数组
请求参数的 name 属性与 Controller 的方法参数名一致,通过
[]
区分下标。
-
页面:注意表单项的 name 属性
用户1<input type="text" name="nameList"><br> 用户2<input type="text" name="nameList"><br> 用户3<input type="text" name="nameList"><br>
-
Controller:设置方法参数名或使用
@RequestParam
@RequestMapping("/login2") public void login2(String[] nameList) { System.out.println(Arrays.asList(nameList)); } @RequestMapping("/login22") public void login22(@RequestParam("nameList") String[] list) { System.out.println(Arrays.asList(list)); }
2.2、POJO 类型(❗)
请求参数的 name 属性与 POJO 的成员变量名一致。
工作原理
- 优先使用无参构造方法,并通过 setter 注入。
- 若没有无参构造方法,使用有参构造方法。
示例
-
POJO:根据以上工作原理,需提供相应的构造方法和 setter(最好都添加)
public class User { private String name; private String password; // 构造方法、getter/setter、toString() }
-
页面:注意表单项的 name 属性,与 POJO 的成员变量名一致。
用户<input type="text" name="name"><br> 密码<input type="password" name="password"> <input type="submit">
-
Controller:使用 POJO 作为方法参数。
@RequestMapping("/login3") public void login2(User user) { System.out.println(user); }
2.3、集合类型
- 数组:接收多个相同 name 属性的简单类型参数时,通常使用数组。
- 集合:接收多个 POJO 类型参数时,通常使用集合。
2.3.1、VO
VO(Value Object、View Object)
获取集合类型的请求参数时,需要将集合参数封装到一个 POJO 中,即 VO
- 将集合定义为 VO 的成员变量(而不是直接作为 Controller 方法参数)。
- 使用 VO 作为 Controller 方法参数
- 通过请求参数的 name 属性与 VO 成员变量名的映射关系接收参数。
2.3.2、示例
以接收多个 User 为例。
-
VO:封装 POJO 集合(根据 2.3 的工作原理,需提供构造方法和 setter)
public class UserVO { private List<User> userList; // 构造方法、setter、toString() }
-
页面:注意表单项的 name 属性
-
格式:
集合[下标].属性
-
集合名称与 VO 的成员变量名一致
-
通过下标区分元素
用户1<input type="text" name="userList[0].name"><br> 密码1<input type="password" name="userList[0].password"><br> 用户2<input type="text" name="userList[1].name"><br> 密码2<input type="password" name="userList[1].password"><br> 用户3<input type="text" name="userList[2].name"><br> 密码3<input type="password" name="userList[2].password"><br> <input type="submit">
-
-
Controller:使用 VO 作为方法参数
@RequestMapping("/login4") public void login4(UserVO userVO) { System.out.println(userVO); }
2.4、@RequestBody(❗)
通常情况下,前后端通过 JSON 格式交换数据。
- 若前端页面通过
AJAX
发送请求参数,且请求方式为POST
,请求参数为 JSON 格式。 - Controller 可使用相应类型的对象(包括集合对象)作为方法参数,并在参数前使用
@RequestBody
注解。
2.4.1、使用
-
页面:
-
导入 jquery 工具,通过 AJAX 发送 POST 请求,参数类型为 JSON 格式。
-
注:<script> 必须是成对标签,自闭合可能无法生效。
<script src="${pageContext.request.contextPath}/js/jquery.js"></script> <script> let userList = new Array(); userList.push({name: "Jaywee1", password: "123456"}) userList.push({name: "Jaywee2 ", password: "888888"}) $.ajax({ type: "POST", url: "/req_resp/login5", data: JSON.stringify(userList), contentType: "application/json;charset=utf-8" }); </script>
-
-
Controller:使用集合对象作为方法参数,无需封装 VO
@RequestMapping("/login5") public void login5(@RequestBody List<User> userList) { System.out.println(userList); }
-
spring-mvc.xml:配置静态资源过滤,避免 JS 文件被 DispatcherServlet 拦截
<!-- mapping 表示访问资源的虚拟路径,location 表示真实路径--> <mvc:resources mapping="/js/**" location="/js/"/>
2.4.2、对比 @RequestParam
@RequestParam
用于标注 Controller 方法上的简单类型参数,有以下常用属性
- value:请求参数名
- required:请求中是否必须包含该参数
- 默认 true:若请求中没有该参数则报错
- false:可有可无,相当于
@Nullable
- defaultValue:当没有指定请求参数时,使用该默认值
对比
尤其是前后端分离式开发中,需注意这两个注解的区别。
@RequestParam | @RequestBody | |
---|---|---|
接收参数格式 | 键值对格式(name=value) | JSON 格式 |
请求方式 | 通常是 GET。 若使用 POST 也只能接收 URL 中的参数,无法接收请求体参数 |
只能是 POST,接收请求体中的参数 |
传参方式 | URL 拼接属性 表单( enctype=multipart/urlencoder ) |
AJAX(contentType: "application/json ) |
2.5、常见问题
2.5.1、静态资源过滤
当请求中涉及静态资源时,必须在 spring-mvc.xml 中配置静态资源过滤,避免被 DispatcherServlet 拦截过滤。
原因:DispatcherServlet 的 url-pattern 配置的是 /
,表示过滤所有资源。
<!-- mapping 表示访问资源的虚拟路径,location 表示真实路径-->
<mvc:resources mapping="/js/**" location="/js/"/>
在实际开发中,涉及很多类型的静态资源(HTML、JS、CSS 等)
-
若通过以上方式,需要配置所有类型的静态资源过滤。
<mvc:resources mapping="/js/**" location="/js/"/> <mvc:resources mapping="/css/**" location="/css/"/> <mvc:resources mapping="/html/**" location="/html/"/>
-
SpringMVC 提供
<mvc:default-servlet-handler>
,可以简化以上配置工作-
DispatcherServlet 只能对处理器(Controller)进行处理,无法处理静态资源。
-
SpringMVC 会将静态资源交给默认 Servlet 处理器(即 Tomcat)进行处理。
<mvc:default-servlet-handler/>
-
2.5.2、乱码问题
当使用 POST 方式提交参数时,服务器端接收的参数会出现乱码(原因见 Servlet 3.3-请求&响应)
传统 Java Web 中解决乱码问题的方式
-
在 Serlvet 中分别设置 request 和 response 对象的字符编码格式
request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8");
-
Filter:在过滤器中设置字符编码格式(全局),有两种配置方式
-
注解:
@WebFilter
@WebFilter("/*") public class EncodingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); chain.doFilter(request, response); } // init()、destory() }
-
web.xml:
<filter>
<filter> <filter-name>encodingFilter</filter-name> <filter-class>indi.jaywee.filter.EncodingFilter</filter-class> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
SpringMVC 解决乱码问题
基于 XML 配置,使用 Spring 提供的字符编码过滤器(同以上方案二)
<filter>
<filter-name>encodingFilter</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>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2.5.3、类型转换器
- SpringMVC 默认配置了常用的类型转换器,将客户端的请求参数进行类型转换(如 int 转 String)
- 支持自定义类型转换器(如转换日期格式)
示例:将客户端的日期字符串转换为日期对象。
-
实现 Converter 接口:泛型分别为源类型、目标类型。
import org.springframework.core.convert.converter.Converter; public class DateConverter implements Converter<String, Date> { @Override public Date convert(String source) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); try { return sdf.parse(source); } catch (ParseException e) { e.printStackTrace(); } return null; } }
-
配置:注册转换器、引用转换器
<bean id="myConversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="indi.jaywee.utils.DateConverter"/> </set> </property> </bean> <mvc:annotation-driven conversion-service="myConversionService"/>
-
Controller
@RequestMapping("/date") public void date(Date date) { System.out.println(date); }
-
测试:成功将字符串格式进行转换为对象。
说明
- SpringMVC 默认的日期格式:
yyyy/MM/dd
- 建议前后端以时间戳的格式传输日期参数,以提高通用性。
2.5.4、Controller 实参注入
以上案例中,可直接在 Controller 方法中声明方法形参,SpringMVC 在调用时会自动注入实参。
常用对象
- Servlet API 相关:
- HttpServletRequest
- HttpServletResponse
- HttpSession
- Spring MVC 相关
- ModelAndView
- Model
5.5.5、@RequestHeader
- RequestHeader:请求报文首部
- 参考:3、HTTP报文
获取方式
- 传统 Java Web:通过
request.getHeader(name)
- Spring MVC:使用
@RequestHeader(name)
示例:获取 Cookie
通过以下方式,获取的是一个 Cookie 字符串,内容是上图展示的所有 Cookie 值。
@RequestMapping("/header1")
public void header1(HttpServletRequest req) {
String cookies = req.getHeader("cookie");
System.out.println(cookies);
}
@RequestMapping("/header2")
public void header2(@RequestHeader(value = "cookie", required = false) String cookies) {
System.out.println(cookies);
}
如何直接获得某个 Cookie?
- 传统 Java Web:通过
request.getCookies()
,按名称遍历查找 - Spring MVC:使用
@CookieValue(name)
示例:获取名为 JSESSIONID 的 Cookie。
显然,通过 @CookieValue
获取的方式,代码更加简洁。
@RequestMapping("/cookie1")
public void cookie1(HttpServletRequest req) {
Cookie[] cookies = req.getCookies();
for (Cookie cookie : cookies) {
if ("JSESSIONID".equals(cookie.getName())) {
System.out.println(cookie.getValue());
}
}
}
@RequestMapping("/cookie2")
public void cookie2(@CookieValue("JSESSIONID") String sessionId) {
System.out.println(sessionId);
}
3、*请求转发和重定向
在前后端开发中,这部分内容不是重点,了解即可。
在 Spring MVC 中使用视图解析器,都属于请求转发。
- 原因:视图解析器是对服务器内部资源进行解析,而重定向属于外部资源跳转。
- 若要实现重定向,参考 Java Web 3.4、请求转发 & 重定向
4、RESTful 风格
4.1、REST
Representational State Transfer
- REST 定义了架构的约束条件和原则,满足这些约束条件和原则的就是 RESTful。
- RESTful 是软件设计风格,而不是标准或协议。
- 基于 RESTful 风格设计的软件更简洁,更有层次,更易实现缓存机制等。
4.2、请求方式
RESTful 风格通过 【
url
和 请求方式】表示一次请求
通常使用以下 4 种请求方式(具体见 3.1.1 HTTP 请求方法)
- 新建资源:POST
- 删除资源:DELETE
- 更新资源:PUT
- 获取资源:GET
对比
-
传统方式:通过不同的请求地址,代表不同的功能。
-
RESTful 风格:相同的请求地址可实现不同的功能,通过请求方式区分。
传统方式 RESTful 风格 添加 /item/add /item 删除 /item/delete?id=1 /item/1 更新 /item/update /item 查询 /item/query?id=1 /item/1
4.3、使用
4.3.1、Controller
-
映射地址:使用
{}
占位符 -
方法参数:使用 @PathVariable 绑定 URI 模板变量。
@PostMapping("/add") public void addUser(User user) { System.out.println(user); } @DeleteMapping("/delete/{username}") public void deleteUser(@PathVariable String username) { System.out.println(username); } @PutMapping("/update") public void updateUser(User user) { System.out.println(user); } @GetMapping("/query/{username}") public void queryUser(@PathVariable int username) { System.out.println(username); }
4.3.1、@XxxMapping
RESTful 通过请求方式区分功能,因此 Controller 方法需指定请求方式。
2 种指定方式:
-
@RequestMapping
:method 属性-
格式:
@RequestMapping(value="",method = 请求方式)
-
请求方式:Spring 框架定义的枚举类
public enum RequestMethod { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE }
-
-
@RequestMapping
的衍生注解- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
- ...