1、前言
主要分析HttpMessageConverter接口、@RequestMapping中的一些属性、@ResponseBody注解、@RequestBody注解以及Json的收发,通过ajax了解这些东西如何在一起使用;如何搭配使用、分别有什么作用、担任什么职责…
-
@RequestMapping注解:请求映射专家,主要负责URI到Controller的映射,以及一些响应的方式、数据格式等的设置…
-
@ResponseBody注解:在Controller响应方法上加入该注解,就会跳过视图解析器;将返回值直接以RequestMapping指定的格式或者MVC配置好的格式响应出去!
-
@RequestBody注解:使用在参数上,将前端响应的数据直接转换为Controller之中的参数(可以是pojo类)…
-
HttpMessageConverter接口:该接口拥有很多实现类、具体的作用就是针对请求与响应的数据进行格式之间的转换!
2、HttpMessageConverter接口
先看一下接口的源代码,对源代码进行分析一波。
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
T read(Class<? extends T> clazz, HttpInputMessage inputMessage);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage);
List<MediaType> getSupportedMediaTypes();
default List<MediaType> getSupportedMediaTypes(Class<?> clazz);
}
-
MediaType类:表示互联网中多媒体数据类型的格式;例如:text/html,text/plain,application/json…
-
@Nullable注解:带有该注解修饰的参数表示该参数可以没有,如果没有就会采用默认的值!
-
canRead方法:检查clazz对象是否能转换为mediaType表示的数据类型,这个mediaType是前端页面请求时设定的contentType格式!
-
read方法:如果canRead方法返回值为true则调用read方法将数据进行格式转换!
-
canWrite方法:检查clazz对象是否能转换为mediaType类型,此时的mediaType表示后端想要响应给前端的数据格式!
-
write方法:如果canWrite返回值为true,则将数据进行格式的转换然后响应!
-
其他:另外两个方法是针对媒体类型进行查看的,媒体类型点进去有非常多的类型…
3、mvc注解驱动
默认情况下SpringMVC框架在配置文件中不进行该注解驱动的支持情况下,会默认给应用使用4个实现类用于接受/响应数据的转换,在开启后会增加到7个左右(版本不同)。
只需要重点关注蓝色的String以及Json格式的转换。
3.1、不开启驱动
在前端控制器DispatcherServlet类中的HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());中打上断点进行debug即可看到默认是开启的四个实现类用于基本消息数据的转换。
3.2、开启注解驱动
在springmvc的核心额皮质文件中加入这句话继续debug。
<mvc:annotation-driven></mvc:annotation-driven>
可以看到开启之后有8个实现类参与,但是这些实现类都没有进行编码集的设置,而是采用了默认的编码集。
3.3、配置注解驱动
一般情况下会配置json和字符串的编码格式,以降低乱码的几率。
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="failOnUnknownProperties" value="false"/>
</bean>
</property>
<property name="defaultCharset" value="UTF-8"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
4、ajax请求与设置
ajax请求中前后端规定的数据格式尽量一一对应,当开启注解驱动之后所有的json前后交换与解析不再需要手动通过ObjectMapper进行对数据的解析与封装,直接返回对象即可得到解析。
<script src="${pageContext.request.contextPath}/js/jquery-3.4.1.js"></script>
<script>
function sub(){
var user = {
"username" : $('#username').val(),
"password" : $('#password').val(),
}
$.ajax({
url: "${pageContext.request.contextPath}/ajax/test",
type: "POST",
data: JSON.stringify(user),
contentType: "application/json;charset=utf-8",
dataType : "json",
success: function (data){
alert(data)
}
})
}
</script>
<div>
<form action="${pageContext.request.contextPath}/ajax/test" method="post">
用户名: <input type="text" id="username" required>
密码: <input type="text" id="password" required>
<input type="button" value="提交" onclick="sub()">
</form>
</div>
只需要这样写即可,json的解析与封装由于在xml中配置了驱动的支持,全部都由框架自动完成!
@RequestMapping(value = "/test")
@ResponseBody
public Account test (@RequestBody Account account){ //将json格式数据自动转换为pojo对象
System.out.println(account.toString());
return account; //配置文件自动进行转换
}
执行顺序:
-
发起请求,框架先找到HttpMessageConverter接口的实现类调用canRead方法以及read方法解析json数据。
-
然后根据@RequestBody注解将对应的数据自动封装到对应的pojo中,注意这里需要使得数据的属性名与请求的参数一致!
-
框架根据控制器方法的返回值类型,找到HttpMessageConverter接口的实现类。最后找到MappingJackson2HttpMessageConverter。
-
使用MappingJackson2HttpMessageConverter实现类先执行canWrite方法进行判断,然后在执行write方法,把account对象转换为json格式的数据。
-
最后框架使用@ResponseBody注解,把转换后的json输出给浏览器进行响应!
补充:
当然也可以不使用框架的配置,进行手动实现;处理解析与封装的时候手动使用jackson包,并且在RequestMapping中设置produces的返回值类型也能做到。
@RequestMapping(value = "/t", produces = "application/json;charset=utf-8")
@ResponseBody
public String t (String data) throws JsonProcessingException { //data是json格式
System.out.println(data);
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(data);
String username = root.path("username").asText();
String password = root.path("password").asText();
System.out.println(username + ": " + password);
Account account = new Account(username, password);
return mapper.writeValueAsString(account);
}