4. SpringMVC的请求与响应
4.1 @RequestMapping
使用@RequestMapping注解可以定义不同的处理器映射规则。
1. URL路径映射:
@RequestMapping(value=“/queryAll”)或@RequestMapping("/queryAll”)
value的值是数组,可以将多个url映射到同一个方法
2. 窄化请求映射
在class上添加@RequestMapping(url)指定通用请求前缀, 限制此类下的所有方法请求url必须以请求前缀开头,通过此方法对url进行分类管理。
如下:
@RequestMapping放在类名上边,设置请求前缀
@Controller
@RequestMapping(“/user”)
方法名上边设置请求映射url:
@RequestMapping放在方法名上边,如下:
@RequestMapping("/queryAll ")
访问地址为:/user/queryAll .action
3. 限制Http请求方式
- 限定GET方法
@RequestMapping(method = RequestMethod.GET)
果通过Post访问则报错:
HTTP Status 405 - Request method ‘POST’ not supported
例如:
@RequestMapping(value=“/editItem”,method=RequestMethod.GET)
- 限定POST方法
@RequestMapping(method = RequestMethod.POST)
如果通过Get访问则报错:
HTTP Status 405 - Request method ‘GET’ not supported
- GET和POST都可以
@RequestMapping(method={RequestMethod.GET,RequestMethod.POST})
4.替换写法
Springmvc提供一组注解用于替换@RequestMapping注解的
- @GetMapping get请求
- @PostMapping post请求
- @PutMapping put请求
- @DeleteMapping delete请求
4.2 Controller的返回值
SpringMVC的Controller的返回值有两大作用:
- 页面跳转
- 响应数据
4.2.1 页面跳转
Controller的返回值作为页面跳转,有两种形式, 一个是以字符串的形式, 一个是以ModelAndView的形式
4.2.1.1 返回字符串形式
- 直接返回字符串:此种方式会将返回的字符串与视图解析器的前后缀拼接后跳转。
controller方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。
真正视图(jsp路径)=前缀+逻辑视图名+后缀
-
redirect 重定向
redirect重定向特点:浏览器地址栏中的url会变化。修改提交的request数据无法传到重定向的地址。因为重定向后重新进行request(request无法共享),注意重定向是访问不到WEB-INF下面的资源.
-
forward 转发
通过forward进行页面转发,浏览器地址栏url不变,request可以共享。
4.2.1.2 返回ModelAndView对象
@RequestMapping("/index1")
public ModelAndView toIndex1(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/index.jsp");
return modelAndView;
}
@RequestMapping("/index2")
public ModelAndView toIndex2(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("forward:/WEB-INF/views/index.jsp");
return modelAndView;
}
@RequestMapping("/index3")
public ModelAndView toIndex3(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("index");
return modelAndView;
}
注意:
使用关键字redirect或者forward时, 视图是不经过视图解析器的
4.2.1.3 向request域存储数据
在进行转发时,往往要向request域中存储数据,在jsp页面中显示,那么Controller中怎样向request
域中存储数据呢?
- 通过SpringMVC框架注入的request对象setAttribute()方法设置
@RequestMapping("/test1")
public String test1(HttpServletRequest request){
request.setAttribute("name","zhangsan");
return "index";
}
- 通过ModelAndView的addObject()方法设置
@RequestMapping("/test2")
public ModelAndView test2(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("forward:/WEB-INF/views/index.jsp");
modelAndView.addObject("name","lisi");
return modelAndView;
}
- 通过Model的addAttribute()方法设置
@RequestMapping("/test3")
public String test3(Model model){
model.addObject("name","wangwu");
return "index";
}
4.2.2 响应数据
在JavaWeb阶段,客户端访问服务器端,如果想直接回写字符串作为响应体返回的话,只需要使用
response.getWriter().print(“hello world”) 即可,那么在Controller中想直接回写字符串该怎样呢?
-
通过SpringMVC框架注入的response对象,使用response.getWriter().print(“hello world”) 回写数
据,此时不需要视图跳转,业务方法返回值为void。
@RequestMapping("/test4")
public void test4(HttpServletResponse response) throws IOException {
response.getWriter().print("hello world");
}
- 将需要回写的字符串直接返回,但此时需要通过**@ResponseBody**注解告知SpringMVC框架,方法返回的字符串不是跳转是直接在http响应体中返回
@RequestMapping("/test5")
@ResponseBody
public String test5() throws IOException {
return "hello springMVC!!!";
}
在异步项目中,客户端与服务器端往往要进行json格式字符串交互,此时我们可以手动拼接json字符串返回,
@RequestMapping("/test6")
@ResponseBody
public String test6() throws IOException {
return "{\"name\":\"zhangsan\",\"age\":18}";
}
上述方式手动拼接json格式字符串的方式很麻烦,开发中往往要将复杂的java对象转换成json格式的字符串,我们可以使用json转换工具jackson进行转换,导入jackson依赖。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
jackson-databind依赖会传递jackson-core,jackson-annotations两个依赖
通过jackson转换json格式字符串,回写字符串。
@RequestMapping("/test7")
@ResponseBody
public User test7() throws IOException {
User user = new User();
user.setUsername("zhangsan");
user.setAge(18);
return user;
}
通过SpringMVC帮助我们对对象或集合进行json字符串的转换并回写,为处理器适配器配置消息转换参数,指定使用jackson进行对象或集合的转换,因此需要在spring-mvc.xml中进行如下配置:
<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>
但是这样配置比较麻烦,配置的代码比较多,因此,我们可以使用mvc的注解驱动代替上述配置。
<!--mvc的注解驱动--> <mvc:annotation-driven/>
在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。
使用mvc:annotation-driven自动加载 RequestMappingHandlerMapping(处理映射器)和
RequestMappingHandlerAdapter( 处 理 适 配 器 ),可用在Spring-mvc.xml配置文件中使用
mvc:annotation-driven替代注解处理器和适配器的配置。
同时使用mvc:annotation-driven默认底层就会集成jackson进行对象或集合的json格式字符串的转换。
4.3 SpringMVC 获得请求数据
从客户端请求key/value数据,经过参数绑定,将key/value数据绑定到controller方法的形参上。
springmvc中,接收页面提交的数据是通过方法形参来接收。而不是在controller类定义成员变更接收!!!!这与Struts2刚刚相反的.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VQPfxJgJ-1670923279691)(assets/image-20220821100336839.png)]
4.3.1 默认支持数据类型
直接在controller方法形参上定义下边类型的对象,就可以使用这些对象。在参数绑定过程中,如果遇到下边类型直接进行绑定。
- HttpServletRequest 请求对象
- HttpServletResponse 响应对象
- HttpSession session对象
- Model/ModelMap Model是一个接口,ModelMap是一个接口实现 。作用:将model数据填充到request域。
4.3.2 简单数据类型
Controller中的业务方法的参数名称要与请求参数的name一致,参数值会自动映射匹配。
http://localhost:8080/test8?username=zhangsan&age=12
@RequestMapping("/test8")
@ResponseBody
public void test8(String username,int age) throws IOException {
System.out.println(username);
System.out.println(age);
}
当请求的参数名称与Controller的业务方法参数名称不一致时,就需要通过@RequestParam注解显示的绑定。
http://localhost:8080/test8?username=zhangsan&age=12
@RequestMapping("/test8")
@ResponseBody
public void test8(@RequestParam("username")String name,int age) throws IOException {
System.out.println(name);
System.out.println(age);
}
注解@RequestParam还有如下参数可以使用:
value:与请求参数名称
required:此在指定的请求参数是否必须包括,默认是true,提交时如果没有此参数则报错
defaultValue:当没有指定请求参数时,则使用指定的默认值赋值
@RequestMapping("/test8")
@ResponseBody
public void test8(@RequestParam(value="username",required =
false,defaultValue = "lisi") String name) throws IOException {
System.out.println(username);
}
4.3.3 获得POJO类型参数
Controller中的业务方法的POJO参数的属性名与请求参数的name一致,参数值会自动映射匹配。
http://localhost:8080/test9?username=zhangsan&age=12
public class User {
private String username;
private int age;
//getter/setter…
}
@RequestMapping("/test9")
@ResponseBody
public void test9(User user) throws IOException {
System.out.println(user);
}
4.3.4 自定义类型转换器
SpringMVC 默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int型进行参数设置。
但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器。
自定义类型转换器的使用步骤:
- 定义转换器类实现Converter接口
- 在配置文件中声明转换器
- 在
<mvc:annotation-driven>
中引用转换器
比如controller形参中pojo对象,如果属性中有日期类型,需要自定义参数绑定。因为前端传递的是String,所以需要把String转换为要转换的日期类型和pojo中日期属性的类型保持一致。
自定义全局的日期转换器:
- 定义转换器类实现Converter接口
package com.suke.converter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
@Slf4j
public class DateConverter implements Converter<String,Date> {
@Override
public Date convert(String source) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
try {
Date date = format.parse(source);
return date;
} catch (ParseException e) {
log.error("日期转换异常:{}",e.getMessage());
}
return null;
}
}
- 在配置文件中声明转换器
ConversionServiceFactoryBean类是spring-context-support依赖中,所以需要导入spring-context-support
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>5.2.15.RELEASE</version> </dependency>
<!--类型转换器工厂Bean-->
<bean id="conversionService2" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.suke.converter.DateConverter"/>
</set>
</property>
</bean>
- 在
<mvc:annotation-driven>
中引用转换器
<mvc:annotation-driven conversion-service="conversionService2"/>
注意:
上面配置的日期转换器是全局的,对整个项目都有效,也可以使用Spring的自带的日期转换器,只需要在实体类的对应的日期属性上使用:@DateTimeFormat,但是使用@DateTimeFormat只能对你指定的属性有效,是局部的.
4.3.5 获得数组类型参数
Controller中的业务方法数组名称与请求参数的name一致,参数值会自动映射匹配。
http://localhost:8080/test10?ids=1001&ids=1002&ids=1003
@RequestMapping("/test10")
@ResponseBody
public void test10(Integer[] ids) throws IOException {
System.out.println(Arrays.asList(ids));
}
4.3.6 获得集合类型参数
通常在需要批量提交数据时,将提交的数据绑定到list<pojo>
中,比如:成绩录入(录入多门课成绩,批量提交),
本例子需求:批量用户添加,在页面输入多个用户信息,将多个用户信息提交到controller方法中。
注意:使用List接收页面提交的批量数据,通过包装类接收,在包装类中定义list属性
JSP页面:
<form action="${pageContext.request.contextPath}/user/add" method="post">
用户名: <input type="text" name="users[0].username"><br>
年龄: <input type="text" name="users[0].age"><br>
性别: <input type="text" name="users[0].gender"><br>
用户名:<input type="text" name="users[1].username"><br>
年龄:<input type="text" name="users[1].age"><br>
性别:<input type="text" name="users[1].gender"><br>
<input type="submit" value="提交"><br>
</form>
UserVo.java
import com.suke.pojo.User;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class UserVo {
private List<User> users = new ArrayList<>();
}
UserController.java
@PostMapping("add")
@ResponseBody
public void add(UserVo userVo){
System.out.println(userVo.getUsers());
}
注意: 如果页面数据包含中文,Controller类得到的数据是乱码, 这时,需要在web.xml文件中配置一个编码过滤器
<!-- 配置post乱码处理 --> <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> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
4.4 JSON数据交互
- JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation)
- JSON 是轻量级的文本数据交换格式
- JSON 独立于语言:JSON 使用 Javascript语法来描述数据对象,但是 JSON 仍然独立于语言和平台。JSON 解析器和 JSON 库支持许多不同的编程语言。 目前非常多的动态(PHP,JSP,.NET)编程语言都支持JSON。
- JSON 具有自我描述性,更易理解
SpringMVC与JSON的交互:
1、请求json、输出json,要求请求的是json串,所以在前端页面中需要将请求的内容转成json,不太方便。但是有一些前端框架请求的数据是json,比如axios
2、请求key/value、输出json。此方法比较常用。
mvc:annotation-driven默认底层就会集成jackson进行对象或集合的json格式字符串的转换。但是如果想对jackson进行自定义的相关设置, 可以在 mvc:annotation-driven 加入MappingJackson2HttpMessageConverter,可以对json进行设置:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="com.fasterxml.jackson.databind.ObjectMapper">
<!-- 处理responseBody 里面日期类型 -->
<!-- <property name="dateFormat">
<bean class="java.text.SimpleDateFormat">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd HH:mm:ss" />
</bean>
</property> -->
<!-- 为null字段时不显示 -->
<property name="serializationInclusion">
<value type="com.fasterxml.jackson.annotation.JsonInclude.Include">NON_NULL</value>
</property>
<property name="propertyNamingStrategy">
<!--<bean class="com.xxx.serializer.MyPropertyNamingStrategyBase" />-->
<bean class="com.fasterxml.jackson.databind.PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy" />
</property>
</bean>
</property>
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json; charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
PropertyNamingStrategy六种策略
- SNAKE_CASE:示例“userName”转化为“user_name”。
- UPPER_CAMEL_CASE:示例“userName”转化为“UserName”。
- LOWER_CAMEL_CASE:默认模式,示例“userName”转化为“userName”。
- LOWER_CASE:示例“userName”转化为“username”。
- KEBAB_CASE:示例“userName”转化为“user-name”。
- LOWER_DOT_CASE:示例“userName”转化为“user.name”。
我们也可以不在 <mvc:annotation-driven>
中配置MappingJackson2HttpMessageConverter这种全局配置, 可以直接在pojo的属性上添加jackson提供的注解:
- @JsonIgnore 忽略该属性转换为json
- @JsonInclude(JsonInclude.Include.NON_NULL) 对null忽略
- @JsonFormat(pattern=“yyyy-MM-dd”) 对日期的转换
重要的注解:
- @RequestBody
作用:
@RequestBody注解用于读取http请求的内容(字符串),通过springmvc提供的HttpMessageConverter接口将读到的内容转换为json、xml等格式的数据并绑定到controller方法的参数上
- @ResponseBody
作用:
该注解用于将Controller的方法返回的对象,通过HttpMessageConverter接口转换为指定格式的数据如:json,xml等,通过Response响应给客户端
测试:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script type="text/javascript" src="/vue/vue.js"></script>
<script type="text/javascript" src="/vue/axios.min.js"></script>
</head>
<body>
<div id="app">
<form id="my-form">
用户名: <input type="text" name="name" v-model="condition.name" /><br/>
地址:
<select name="address" v-model="condition.address">
<option value="长沙">长沙</option>
<option value="北京">北京</option>
</select>
<br/>
邮箱:<input type="text" name="email" v-model="condition.email"><br/>
<button type="button" @click="search()">查询</button>
</form>
</div>
<script type="text/javascript">
new Vue({
el:'#app',
data(){
return {
condition:{
name:'zhangsan',
address:'长沙',
email:'23111@qq.com',
},
}
},
methods:{
search(){
var that = this;
axios.post('/user/test11',that.condition )
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
}
});
</script>
</body>
</html>
@Data
public class UserCondition {
private String name;
private String address;
private String email;
}
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
private Integer id;
private String name;
private String gender;
@JsonIgnore
private Integer age;
private String address;
private String email;
private String qq;
private String photo;
@JsonFormat(pattern="yyyy-MM-dd")
private Date birthday;
}
@RequestMapping("/test11")
@ResponseBody
public User test11(@RequestBody UserCondition userCondition) throws IOException {
User user = new User();
BeanUtils.copyProperties(userCondition,user);
user.setId(1001);
user.setBirthday(new Date());
return user;
}
当使用ajax提交时,可以指定contentType为json形式,那么在方法参数位置使用@RequestBody可以直接接收集合数据而无需使用POJO进行包装。
search2(){ var userList = new Array(); userList.push({name: "zhangsan",address:'长沙',email:'zhangsan@163.com'}); userList.push({name: "lisi",address:'北京',email:'lisi@163.com'}); axios.post('/user/test12',userList) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); }
@RequestMapping("/test12") @ResponseBody public List<User> test12(@RequestBody List<UserCondition> userConditionList) throws IOException { List<User> users = new ArrayList<>(); for (int i = 0; i < userConditionList.size(); i++) { User user = new User(); user.setId(i+1); BeanUtils.copyProperties(userConditionList.get(i),user); users.add(user); } return users; }
注意: 如果我们的前端控制器设置的拦截url为
<url-pattern>/</url-pattern>
<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:springmvc.xml</param-value> </init-param> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
通过谷歌开发者工具抓包发现,没有加载到vue.js和axios文件,原因是SpringMVC的前端控制器
DispatcherServlet的url-pattern配置的是/,代表对所有的资源都进行过滤操作,我们可以通过以下两种
方式指定放行静态资源:
- 在spring-mvc.xml配置文件中指定放行的资源
<mvc:resources mapping="/js/**" location="/js/"/>
- 使用
<mvc:default-servlet-handler/>
标签<mvc:default-servlet-handler/>
4.5 Restful风格
Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。
Restful风格的请求是使用**“url+请求方式”**表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:
-
GET:用于获取资源
-
POST:用于新建资源
-
PUT:用于更新资源
-
DELETE:用于删除资源
例如:
/user/1 GET : 得到 id = 1 的 user
/user/1 DELETE: 删除 id = 1 的 user
/user/1 PUT: 更新 id = 1 的 user
/user POST: 新增 user
获得Restful风格的参数
上述url地址/user/1中的1就是要获得的请求参数,在SpringMVC中可以使用占位符进行参数绑定。地址/user/1可以写成/user/{id},占位符{id}对应的就是1的值。在业务方法中我们可以使用@PathVariable注解进行占位符的匹配获取工作。
http://localhost:8080/user/test13/zhangsan
@RequestMapping("/test13/{name}")
@ResponseBody
public void test13(@PathVariable(value = "name",required = true) String name){
System.out.println(name);
}
4.6 其他参数
4.6.1 获得请求头 @RequestHeader
使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name)
@RequestHeader注解的属性如下:
-
value:请求头的名称
-
required:是否必须携带此请求头
@RequestMapping("/test14")
@ResponseBody
public void test14(
@RequestHeader(value = "User-Agent",required = false) String
headerValue){
System.out.println(headerValue);
}
4.6.2 获取Cookie的参数
使用@CookieValue可以获得指定Cookie的值
@CookieValue注解的属性如下:
-
value:指定cookie的名称
-
required:是否必须携带此cookie
@RequestMapping("/test15")
@ResponseBody
public void test15(
@CookieValue(value = "JSESSIONID",required = false) String jsessionid){
System.out.println(jsessionid);
}