本文章是把SpringBoot处理请求参数的原理 流程 例子全部细讲了一次,其中还有SpringMVC的知识,预计字数6w+,预计阅读时间1-2小时,虽然很长,但是流程清晰,代码基本上都有批注,读完就能完全理解请求参数处理的原理。
Rest风格支持(使用HTTP请求方式动词来表示对资源的操作):
以前: /getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
现在: /user *GET-*获取用户 *DELETE-*删除用户 *PUT-*修改用户 *POST-*保存用户
核心Filter;HiddenHttpMethodFilter
用法: 表单method=post,隐藏域 _method=put
SpringBoot.yaml中手动开启页面表单的Rest功能
spring:
mvc:
hiddenmethod:
filter:
#开启页面表单的Rest功能
enabled: true
创建controller层
@RestController
public class HelloController {
@RequestMapping("/2.png")
public String hello()
{
return "3.png";
}
@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
return "GET-张三";
}
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
return "POST-张三";
}
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
return "PUT-张三";
}
@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
return "DELETE-张三";
}
}
当controller层收到表单的请求后会找到对应的controller进行处理
如果要把_method 这个名字换成我们自己喜欢的就需要自己创建一个配置类,在配置类中配置HiddenHttpMethodFilter
@Configuration(proxyBeanMethods = false)
public class webConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
//设置我们自己喜欢的_method值
methodFilter.setMethodParam("_m");
return methodFilter;
}
}
在WebMvcAutoConfiguration自动配置的时候,会默认帮我们配置一个HiddenHttpMethodFilter,
public class WebMvcAutoConfiguration {
//多余代码已省略
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
//多余代码已省略
当我们通过yaml配置文件修改enable=true的时候,请求会到HiddenHttpMethodFilter(OrderedHiddenHttpMethodFilter的父类)中的doFilterInternal()
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
//多余代码已省略
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {
HttpServletRequest requestToUse = request;
//判断发送的请求是否是POST,如果是就进入语句
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
//拿到_method中的value(兼容以下请求PUT,DELETE,PATCH)
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
//包装为一个新的request
//原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
//过滤器链放行的时候用wrapper以后的方法调用getMethod是调用requesWrapper的
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter(requestToUse, response);
}
//多余代码已省略
Rest使用客户端工具
如PostMan直接发送Put.delete等方式请求,无需Filter。
从这里开始直到handler目标方法返回值处理结束,我们会从举一个具体的例子
/**
* 姓名:
* 年龄:
* 生日:
* 宠物姓名:
* 宠物年龄:
*/
@Data
public class Person {
private String userName;
private Integer age;
private Date birth;
private Pet pet;
}
@Data
public class Pet {
private String name;
private String age;
}
<form action="/saveuser" method="post">
姓名: <input name="userName" value="zhangsan"/> <br/>
年龄: <input name="age" value="18"/> <br/>
生日: <input name="birth" value="2019/12/10"/> <br/>
宠物姓名:<input name="pet.name" value="阿猫"/><br/>
宠物年龄:<input name="pet.age" value="5"/>
<input type="submit" value="保存"/>
form>
@Controller
public class ParameterTestController {
@ResponseBody
@PostMapping("/saveuser")
public Person saveuser(Person person){
return person;
}
}
把请求的是怎么找到对应的Handler(3.1.2.2)—>解析、绑定每一个参数的(3.2.2)—>调用handler对应的目标方法(3.2.2.2.1.1.3.1.2)—>处理返回值(4.1.1.1)—>数据响应到页面和内容协商(4.1.4)的流程和内部的一些原理分析一次
DispatcherServlet:前端控制器,不需要工程师开发,由框架提供
作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求
HandlerMapping:处理器映射器,不需要工程师开发,由框架提供(相当于就是找controller的)
Handler:处理器(就是controller),需要工程师开发
作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理
HandlerAdapter:处理器适配器,不需要工程师开发,由框架提供 (相当于就是调用执行controller的)
作用:调用具体的controller对用户发来的请求来进行处理。当handlerMapping获取到执行请求的controller时,DispatcherServlte会根据controller对应的controller类型来调用相应的HandlerAdapter来进行处理。
ViewResolver:视图解析器,不需要工程师开发,由框架提供
作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView
View:视图
作用:将模型数据通过页面展示给用户
HttpServletBean中未重写doGet(),doPost(),doService()先忽略掉
FrameworkServlet中重写了doGet(),doPost(),很显然他们都执行了processRequest(request, response)
//=====================================FrameworkServlet====================================================
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//多余代码已省略
try {
//调用的是DispatcherServlet中重写的 doService(request, response)
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
DispatcherServlet中重写的doService(request, response)
//=============================================DispatcherServlet===========================================
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
//多余代码已省略
try {
//执行DispatcherServlet中的doDispatch(request, response);
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
if (this.parseRequestPath) {
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
}
//SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 找到当前请求使用哪个Handler(Controller的方法)处理,3.1.2.2看这里
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//多余代码省略
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
//请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
for (HandlerMapping mapping : this.handlerMappings) {
//从这里获取到能处理该请求路径的controller
HandlerExecutionChain handler = mapping.getHandler(request);
// 如果有就找到这个请求对应的handler,如果没有就是继续查下一个 HandlerMapping
if (handler != null) {
return handler;
}
}
}
return null;
}
RequestMappingHandlerMapping:SpringBoot自动配置了默认 的 RequestMappingHandlerMapping(解析加了@RequestMapping的控制器)
WelcomePageHandlerMapping :SpringBoot自动配置欢迎页的 ,访问 /能访问到index.html;
@PathVariable 获取请求参数(一般用在restful风格中用/{}知名的)
例:“/car/{id}/owner/{username}”
@RequestHeader 获取请求头
@ModelAttribute
@RequestParam 获取请求参数(一般用在?拼接的参数或者是get请求)
例:“/cars/{path}?xxx=xxx&aaa=ccc” queryString 查询字符串。@RequestParam;
@MatrixVariable
@CookieValue 根据cookie的name,获取cookie的值
@RequestBody 获取传过来的json对象(ajax 如果发送的是json字符串,服务端接收时必须要使用@RequestBody注解。始终记住,json字符串,"application/json”,@RequestBody 这三者之间是一一对应的,要有都有,要没有都没有。)
<a href="http://localhost:8080/car/3/owner/lisiage=18&interests=basketball&interests=game">
car/{id}/owner/{username}
a>
@RestController
public class ParameterTestController {
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariabl e("username") String name,
@PathVariable Map<String,String>pv,
@RequestHeader("Host") String host,
@RequestHeader Map<String,String> headers,
@RequestParam("age") Integer age,
@RequestParam("interests")List<String> interests,
@RequestParam Map<String,String> params,
@CookieValue("password") String password
){
HashMap<String, Object> map = new HashMap<>();
map.put("Id",id);
map.put("Username",name);
map.put("Pv",pv);
map.put("Host",host);
map.put("Headers",headers);
map.put("Age",age);
map.put("Interests",interests);
map.put("Params",params);
map.put("Password",password);
return map;
}
@PostMapping("/save")
public Map postMethod(@RequestBody String user){
HashMap<String, Object> map = new HashMap<>();
map.put("User",user);
return map;
}
}
@RequestAttribute 页面转发的时候可以将request中的值直接赋给方法中的参数
@Controller
public class RequestController {
@GetMapping("/goto")
public String goToPage(HttpServletRequest request){
request.setAttribute("msg","成功了...");
request.setAttribute("code",200);
//转发到 /success请求
return "forward:/success";
}
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute("msg") String msg,
@RequestAttribute("code") Integer code,
HttpServletRequest request
)
{
Object msg1=request.getAttribute("msg");
Object code1=request.getAttribute("code");
Map<String,Object> map=new HashMap<>();
map.put("reqMethod_msg",msg1);
map.put("annotation_msg",msg);
map.put("reqMethod_code",code1);
map.put("annotation_code",code);
return map;
}
Map.Model(map.model里面的数据会被放在request的请求request.setAttribute).
Errors/BindingResult
RedirectAttributes( 重定向携带数据) .
ServletResponse(response) .
SessionStatus
UriComponentsBuilder.ServletUriComponentsBuilder
Map<String,Object> map, Model model, HttpServletRequest request 都是可以给request域中放数据
通过request.getAttribute();即可获取
@Controller
public class RequestController {
@GetMapping("/params")
public String testParam(Map<String,Object> map,
Model model,
HttpServletRequest request,
HttpServletResponse response)
{
map.put("map","mapValue");
model.addAttribute("model","modelValue");
request.setAttribute("request","requestValue");
Cookie cookie = new Cookie("MyCookie","MyCookieValue");
cookie.setDomain("localhost");
response.addCookie(cookie);
return "forward:/success1";
}
@ResponseBody
@GetMapping("/success1")
public Map success1(HttpServletRequest request){
HashMap<String, Object> hashMap = new HashMap<>();
Object map = request.getAttribute("map");
Object model = request.getAttribute("model");
Object request1 = request.getAttribute("request");
hashMap.put("map",map);
hashMap.put("model",model);
hashMap.put("request1",request1);
return hashMap;
}
}
可以自动类型转换与格式化,可以级联封装。
/**
* 姓名:
* 年龄:
* 生日:
* 宠物姓名:
* 宠物年龄:
*/
@Data
public class Person {
private String userName;
private Integer age;
private Date birth;
private Pet pet;
}
@Data
public class Pet {
private String name;
private String age;
}
<form action="/saveuser" method="post">
姓名: <input name="userName" value="zhangsan"/> <br/>
年龄: <input name="age" value="18"/> <br/>
生日: <input name="birth" value="2019/12/10"/> <br/>
宠物姓名:<input name="pet.name" value="阿猫"/><br/>
宠物年龄:<input name="pet.age" value="5"/>
<input type="submit" value="保存"/>
form>
@Controller
public class ParameterTestController {
@ResponseBody
@PostMapping("/saveuser")
public Person saveuser(Person person){
return person;
}
}
测试使用3.2.1.3.2 自定义对象参数的案例
1.HandlerMapping中找到能处理请求的Handler(Controller)
2.为当前Handler找一个适配器 HandlerAdapter;RequestMappingHandlerAdapter
适配器执行目标方法并确定方法参数的每一个值,HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
3.真正执行hanlder的方法, ha.handle(processedRequest, response, mappedHandler.getHandler());
//SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//找到当前请求使用哪个Handler(Controller的方法)处理,3.1.2.2看这里
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//找到当前请求对应的controller类型来调用相应的HandlerAdapter
//适配器执行目标方法并确定方法参数的每一个值,3.2.2.1看这里
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//调用AbstractHandlerMethodAdapter.handle()
//真正执行hanlder的方法,3.2.2.2看这里
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//处理派发结果,也就是去哪些页面5看这里
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
找到当前请求使用哪个Handler(Controller的方法)处理
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
//请求进来,挨个尝试所有的HandlerMapping对应的HandlerAdapter
for (HandlerAdapter adapter : this.handlerAdapters) {
//判断HandlerAdapter是否是HandlerMethod
//是就返回HandlerAdapter
if (adapter.supports(handler)) {
return adapter;
}
}
}
}
//判断HandlerAdapter是否是HandlerMethod
//是就返回HandlerAdapter
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
0-RequestMappingHandlerAdapter支持方法上标注@RequestMapping 的Controller
1-HandlerFunctionAdapter支持函数式编程的Controller
…
真正执行hanlder的方法
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {
//执行handler的方法是调用的RequestMappingHandlerAdapter. handleInternal(),3.2.2.2.1看这里
return handleInternal(request, response, (HandlerMethod) handler);
}
3.2.2.2.1执行handler的方法是调用的RequestMappingHandlerAdapter. handleInternal()
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 执行handler的目标方法,3.2.2.2.1.1看这里
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
3.2.2.2.1.1执行handler的目标方法 RequestMappingHandlerAdapter.invokeHandlerMethod(request, response, handlerMethod)
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
//为handler的目标方法添加所有的参数解析器,什么是参数解析器?3.2.2.2.1.1.1看这里
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
//为handler的目标方法添加所有的返回值处理器,什么是返回值处理器?4.1.1.1看这里
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
//创建一个ModelAndViewContainer
//为了保存的所有视图数据和模型数据,详细解释看5.2.2.1.1.2.2
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
//执行并处理handler的目标方法3.2.2.2.1.1.3看这里
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
//发出请求已完成的信号
webRequest.requestCompleted();
}
}
3.2.2.2.1.1.1参数解析器-HandlerMethodArgumentResolver
作用:
确定将要执行的目标方法的每一个参数的值是什么;
SpringMVC目标方法能写多少种参数类型。取决于参数解析器有多少种—它们都实现了HandlerMethodArgumentResolver顶层接口。
原理:
所有的参数处理器实现了的顶层接口HandlerMethodArgumentResolver
当前解析器是否支持解析这种参数—supportPatameter()
支持就将方法参数解析为给定请求的参数值— resolveArgument()
在本次测试案例中调用的是ServletRequestMethodArgumentResolver,具体流程见3.2.2.2.1.1.3.1.1.1
3.2.2.2.1.1.3如何确定目标方法每一个参数的值
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//真正地执行handler目标方法,3.2.2.2.1.1.3.1走这里
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
//3.2.2.2.1.1.3.2处理handler的目标方法的返回值结果
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
3.2.2.2.1.1.3.1真正地执行handler目标方法InvocableHandlerMethod.invokeForRequest(webRequest, mavContainer, providedArgs)
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//获取handler的所有请求参数值,3.2.2.2.1.1.3.1.1走这里
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
//把获取到的参数放入doInvoke(args)真正的执行目标方法返回一个Person类型的对象,3.2.2.2.1.1.3.1.2走这里
return doInvoke(args);
}
3.2.2.2.1.1.3.1.1获取handler的所有请求参数InvocableHandlerMethod. getMethodArgumentValues(request, mavContainer, providedArgs)
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//拿到handler的目标方法的所有参数值(详细信息)
MethodParameter[] parameters = getMethodParameters();
//如果handler的目标方法没有参数列表,直接就返回空
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
//如果handler的目标方法有参数列表,那么就创建一个长度为参数长度的Object数组
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
//拿到第i个参数
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
//判断当前参数解析器是否可以处理当前第i个参数的注解,3.2.2.2.1.1.3.1.1.1走这里
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
//解析第i个参数的值,3.2.2.2.1.1.3.1.1.2走这里
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
3.2.2.2.1.1.3.1.1.1判断是否可以处理当前第i个参数的注解—参数处理器的执行流程ServletRequestMethodArgumentResolver
这一步可以理解为确定第i个参数的参数处理器是什么
//=========================== supportsParameter(MethodParameter parameter)=================================
//==================================getArgumentResolver(parameter)=========================================
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
//先从缓存中拿
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
//如果拿不到挨个判断所有参数解析器(26种)哪个支持解析这个参数
//在本次测试案例中是只有一个Person类型
//在这里就通过调用ServletRequestMethodArgumentResolver.supportsParameter(parameter)
//因为ServletRequestMethodArgumentResolver实现了顶级接口HandlerMethodArgumentResolver
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
//=====================ServletRequestMethodArgumentResolver.supportsParameter(parameter)====================
public boolean supportsParameter(MethodParameter parameter) {
//判断是否标注了@ModelAttribute--->在这个测试中是false
//或者是判断是否这个注解是必须的--->在这个测试中是true,并且这个属性是否是简单属性--->在这个测试中是false但是有!所以是true
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
//============================parameter.getParameterType()================================================
public Class<?> getParameterType() {
Class<?> paramType = this.parameterType;
if (paramType != null) {
return paramType;
}
if (getContainingClass() != getDeclaringClass()) {
paramType = ResolvableType.forMethodParameter(this, null, 1).resolve();
}
if (paramType == null) {
paramType = computeParameterType();
}
this.parameterType = paramType;
//返回的是Person类
return paramType;
}
//===============================BeanUtils.isSimpleProperty()=============================================
public static boolean isSimpleProperty(Class<?> type) {
Assert.notNull(type, "'type' must not be null");
//判断是否是简单类型--->这里是false
//或者是否是数组类型--->这里是false并且Person类是否是简单类型--->这里是false
return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
}
//=====================================type.getComponentType()=============================================
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
3.2.2.2.1.1.3.1.1.2解析第i个参数的值—resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory)
这一步可以理解为确定第i个参数的是什么—这里是Person类型(参数解析器是ServletRequestMethodArgumentResolver)
//========================ServletRequestMethodArgumentResolver. resolveArgument()===========================
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//拿到第i个参数的参数解析器---这里是Person类型,参数解析器是ServletRequestMethodArgumentResolver
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
//开始解析第i个参数
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
//获取参数的名字---这里是person
String name = ModelFactory.getNameForParameter(parameter);
//获取参数上的注解---这里是null
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
//看mavContainer是否有person类型
if (mavContainer.containsAttribute(name)) {
//如果有直接就从mavContainer拿
attribute = mavContainer.getModel().get(name);
}
else {
try {
//创建一个空的Person实例
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
else {
attribute = ex.getTarget();
}
bindingResult = ex.getBindingResult();
}
}
if (bindingResult == null) {
//创建了一个web数据绑定器
//WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
//拿到Person对象,如果不为空
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
//将request请求中的参数和web数据绑定器进行绑定,3.2.2.2.1.1.3.1.1.2.1看这里
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
3.2.2.2.1.1.3.1.1.2.1ServletModelAttributeMethodProcessor.bindRequestParameters()将request请求中的参数和web数据绑定器进行
绑定
//ServletModelAttributeMethodProcessor.bindRequestParameters(WebDataBinder binder, NativeWebRequest request)
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
Assert.state(servletRequest != null, "No ServletRequest");
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
//开始进行数据绑定(确定每一个参数的值)
servletBinder.bind(servletRequest);
}
//======================================= bind(ServletRequest request)=====================================
public void bind(ServletRequest request){
//拿到所有的请求参数的K,V,如图1所示
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
else if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.MULTIPART_FORM_DATA_VALUE))
{
HttpServletRequest httpServletRequest = WebUtils.getNativeRequest(request, HttpServletRequest.class);
if (httpServletRequest != null && HttpMethod.POST.matches(httpServletRequest.getMethod())) {
StandardServletPartUtils.bindParts(httpServletRequest, mpvs, isBindEmptyMultipartFiles());
}
}
addBindValues(mpvs, request);
//添加绑定值
doBind(mpvs);
}
//====================================doBind(MutablePropertyValues mpvs)====================================
@Override
protected void doBind(MutablePropertyValues mpvs) {
checkFieldDefaults(mpvs);
checkFieldMarkers(mpvs);
adaptEmptyArrayIndices(mpvs);
super.doBind(mpvs);
}
protected void doBind(MutablePropertyValues mpvs) {
checkAllowedFields(mpvs);
checkRequiredFields(mpvs);
applyPropertyValues(mpvs);
}
//===========================applyPropertyValues(MutablePropertyValues mpvs) ===============================
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
//将请求参数绑定到目标对象上。
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
}
catch (PropertyBatchUpdateException ex) {
// Use bind error processor to create FieldErrors.
for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
}
}
//==========================================getPropertyAccessor()==========================================
protected ConfigurablePropertyAccessor getPropertyAccessor() {
return getInternalBindingResult().getPropertyAccessor();
}
//======== setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)==============
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
throws BeansException {
List<PropertyAccessException> propertyAccessExceptions = null;
List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
if (ignoreUnknown) {
this.suppressNotWritablePropertyException = true;
}
try {
//遍历每一个request的K,V
for (PropertyValue pv : propertyValues) {
try {
//通过反射开始设置真正地将请求参数绑定到目标对象上。
//并且当返回值类型不符的时候会通过GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(比如JavaBean -- Integer),如图2
setPropertyValue(pv);
}
catch (NotWritablePropertyException ex) {
if (!ignoreUnknown) {
throw ex;
}
// Otherwise, just ignore it and continue...
}
catch (NullValueInNestedPathException ex) {
if (!ignoreInvalid) {
throw ex;
}
// Otherwise, just ignore it and continue...
}
catch (PropertyAccessException ex) {
if (propertyAccessExceptions == null) {
propertyAccessExceptions = new ArrayList<>();
}
propertyAccessExceptions.add(ex);
}
}
}
finally {
if (ignoreUnknown) {
this.suppressNotWritablePropertyException = false;
}
}
// If we encountered individual exceptions, throw the composite exception.
if (propertyAccessExceptions != null) {
PropertyAccessException[] paeArray = propertyAccessExceptions.toArray(new PropertyAccessException[0]);
throw new PropertyBatchUpdateException(paeArray);
}
}
图1:
图2:
3.2.2.2.1.1.3.1.2调用doInvoke(args)把解析绑定到的参数放入真正的执行目标方法
protected Object doInvoke(Object... args) throws Exception {
//获取到handler的桥接方法(就是 public Person saveuser(Person person))
Method method = getBridgedMethod();
try {
if (KotlinDetector.isSuspendingFunction(method)) {
return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
}
//通过反射调用该方法,就调用到了真正的public Person saveuser(Person person)
return method.invoke(getBean(), args);
}
//多余代码省略
//=============================== public Person saveuser(Person person)=====================================
@ResponseBody
@PostMapping("/saveuser")
public Person saveuser(Person person){
return person;
}
}
自此我们就完成了参数的解析、绑定,以及handler对应方法的调用,接下来我们就要进行参数的返回值的处理了。
使用:
<form action="/saveuser" method="post">
姓名: <input name="userName" value="zhangsan"/> <br/>
年龄: <input name="age" value="18"/> <br/>
生日: <input name="birth" value="2019/12/10"/> <br/>
宠物: <input name="pet" value="啊猫,3"/>
<input type="submit" value="保存"/>
form>
//WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
//添加一个converter将String类型转化为Pet类型
registry.addConverter(new Converter<String, Pet>() {
@Override
//source是页面提交过来的值(字符串类型)
public Pet convert(String source) {
// 啊猫,3
if(!StringUtils.isEmpty(source)){
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
原理:
在3.2.2.2.1.1.3.1.1.2.1中进行web参数绑定的时候,我们可以在WebMvcConfigurer中自定义绑定handler的目标方法传递过来的converter—>使用逗号分隔姓名和年龄实现自定义参数传递
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// Custom editor for this type?
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
ConversionFailedException conversionAttemptEx = null;
// 没有converter能处理,但是自定义配置了converter
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
//在这里一直往下step into就可以找到调用自定义的converter
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
catch (ConversionFailedException ex) {
// fallback to default conversion logic below
conversionAttemptEx = ex;
}
}
}
//===================================ConversionUtils.invokeConverter()=====================================
@Nullable
public static Object invokeConverter(GenericConverter converter, @Nullable Object source,
TypeDescriptor sourceType, TypeDescriptor targetType) {
try {
return converter.convert(source, sourceType, targetType);
}
catch (ConversionFailedException ex) {
throw ex;
}
catch (Throwable ex) {
throw new ConversionFailedException(sourceType, targetType, source, ex);
}
}
//===========================GenericConversionService.invokeConverter()=====================================
@Override
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return convertNullSource(sourceType, targetType);
}
//在这里就真正的调用到了自定义的converter
return this.converter.convert(source);
}
这两个东西配置以后就会给前端自动返回json数据
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
//web场景自动引入了json场景
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jsonartifactId>
<version>2.3.4.RELEASEversion>
<scope>compilescope>
dependency>
在这里使用测试类,测试类是返回的Person类型,所以使用的是RequestResponseBodyMethodProcessor
作用:
确定将要执行的目标方法的返回值类型的参数
SpringMVC目标方法能写多少种参数类型。取决于返回值处理器多少种。
SpringMVC支持的返回值处理器
0-ModelAndView
1-Model
2-View
3-ResponseEntity
4-ResponseBodyEmitter
5-StreamingResponseBody
6-HttpEntity
7-HttpHeaders
8-Callable
9-DeferredResult
10-ListenableFuture
11-CompletionStage
12-WebAsyncTask
13-有@ModelAttribute 且为对象类型的
14-RequestResponseBodyMethodProcessor--->标注了@ResponseBody 注解的handler
原理:
所有的返回值处理器实现了的顶层接口HandlerMethodReturnValueHandler
supportsReturnType—返回值处理器判断是否支持这种类型返回值
handleReturnValue—支持就调用直接处理响应来处理给定的返回值
在本次测试案例中调用的是RequestResponseBodyMethodProcessor
4.1.1.1.1返回值处理器执行流程
try {
//3.2.2.2.1.1.3.2的详细步骤走这里
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
//===========this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);=========================
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//选择合适的返回值处理器
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " +returnType.getParameterType().getName());
}
//用上一步获取到的返回值(RequestResponseBodyMethodProcessor)处理器开始处理返回值
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
//======================selectHandler(returnValue, returnType)=============================================;
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
//(在这里是Person,并且选择的是RequestResponseBodyMethodProcessor返回值处理器)
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
//================RequestResponseBodyMethodProcessor.handleReturnValue()==================================
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
// 使用消息转换器进行写出操作,详细步骤4.2.2.2看这里
//returnvalue:返回值
//returnValue:返回值类型
//inputMessage,outputMessage:请求和响应
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
根据客户端接收能力不同,返回不同媒体类型的数据。
媒体类型就是:Accpet字段的那些类型,可以通知服务器,客户端能够处理的媒体类型以及媒体类型的相对优先级,q为优先级权重(0-1),用;分隔,默认为1
比如application/json,application/xml…
在浏览器上显示JSON类型数据,在Android上显示XML数据…
1.引入xml依赖—因为浏览器只能响应JSON数据,所以在这里我们引入这个依赖就可以在浏览器显示xml格式的数据
<dependency>
<groupId>com.fasterxml.jackson.dataformatgroupId>
<artifactId>jackson-dataformat-xmlartifactId>
dependency>
2.postman分别测试返回json和xml
ACCEPT字段:可以通知服务器,客户端能够处理的媒体类型以及媒体类型的相对优先级,q为优先级权重(0-1),用;分隔,默认为1
3.开启浏览器参数方式内容协商功能
为了方便内容协商,在yaml配置中开启基于请求参数的内容协商功能。
spring:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
发请求:
http://localhost:8080/saveuser**?format=xml**
[http://localhost:8080/test/saveuser**?format=json**
内容协商的基本流程:
在响应json的时候:
RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
1.利用 MessageConverters 进行处理 将数据写为json(所有的MessageConverters合起来可以支持各种媒体类型数据的读和写)
1.1内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
ACCEPT字段:
1.2服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
1.3SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
1.3.1得到MappingJackson2HttpMessageConverter可以将对象写为json
1.3.2利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
Object body;
Class<?> valueType;
Type targetType;
//判断传入的returnValue返回值是否是字符串类型
if (value instanceof CharSequence) {
body = value.toString();
valueType = String.class;
targetType = String.class;
}
else {
//将传入的Person对象赋值过去
body = value;
//拿到Person对象的类型
valueType = getReturnValueType(body, returnType);
//需要将Person对象转换的目标类型
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}
//判断返回值类型是否是资源类型(流数据)(Person类型不是)
if (isResourceType(value, returnType)) {
outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
outputMessage.getServletResponse().getStatus() == 200) {
Resource resource = (Resource) value;
try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
body = HttpRange.toResourceRegions(httpRanges, resource);
valueType = body.getClass();
targetType = RESOURCE_REGION_LIST_TYPE;
}
catch (IllegalArgumentException ex) {
outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
}
}
}
//选中的媒体类型(内容协商)
MediaType selectedMediaType = null;
//1.首先去拿到响应头中有没有所需要的内容类型
MediaType contentType = outputMessage.getHeaders().getContentType();
boolean isContentTypePreset = contentType != null && contentType.isConcrete();
//1.1如果响应头中已经有能够处理的内容类型了
if (isContentTypePreset) {
if (logger.isDebugEnabled()) {
logger.debug("Found 'Content-Type:" + contentType + "' in response");
}
//1.2就直接把响应头中的内容类型传递出来
selectedMediaType = contentType;
}
//2.如果响应头中有没有所需要的内容类型,获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)
//Accept:代表客户端能够接受的数据类型
//Content-Type:代表客户端|服务器发送的实体数据的数据类型
else {
//2.1首先拿到request对象
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> acceptableTypes;
try {
//2.2再从request对象中拿到客户端中可以接收的内容类型,4.1.2.2.1走这里
acceptableTypes = getAcceptableMediaTypes(request);
}
catch (HttpMediaTypeNotAcceptableException ex) {
int series = outputMessage.getServletResponse().getStatus() / 100;
if (body == null || series == 4 || series == 5) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring error response content (if any). " + ex);
}
return;
}
throw ex;
}
//2.3服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,4.1.2.2.2走这里(第一次HttpMessageConverter匹配)
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
List<MediaType> mediaTypesToUse = new ArrayList<>();
//3.将服务器能生产出的媒体类型数据和浏览器能接收的媒体类型数据进行最佳匹配
//如果匹配成功就把匹配成功的媒体类型加入到mediaTypeToUse中
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
//4.如果mediaTypeToUse为空,就抛出异常
if (mediaTypesToUse.isEmpty()) {
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
if (logger.isDebugEnabled()) {
logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
return;
}
//将mediaTypesToUse中的媒体类型进行排序(按权重q从大到小)
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
//5.将mediaTypeToUse赋值给selectedMediaType(排序后权重最大的在第一个)
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Using '" + selectedMediaType + "', given " +
acceptableTypes + " and supported " + producibleTypes);
}
}
//6.如果配对到了能够匹配的类型(selectedMediaTypeb不为空)
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
/**
7.SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter(开始进行第二次匹配,第一次匹配见4.1.2.2.2)
将浏览器能够处理的类型与后台所能处理的类型进行对比,找出最适合的类型转换
HttpMessageConverter原理见4.1.3
进行两次匹配的原因见4.1.2.3
*/
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
//8.判断HttpMessageConverter是否能够支持写操作
if (genericConverter != null ?((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType):
converter.canWrite(valueType, selectedMediaType)) {
//9.如果支持就把写出的内容赋值给body(Person对象)
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
//10.写之前加一些头部信息进去
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
//11.将Person对象以Json的方式写出去,4.1.2.2.3走这里
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
}
if (body != null) {
Set<MediaType> producibleMediaTypes =
(Set<MediaType>) inputMessage.getServletRequest()
.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
throw new HttpMessageNotWritableException(
"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
}
throw new HttpMediaTypeNotAcceptableException(getSupportedMediaTypes(body.getClass()));
}
}
4.1.2.2.1从request对象中拿到浏览器中可以接收的内容类型 getAcceptableMediaTypes(request)
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
throws HttpMediaTypeNotAcceptableException {
//contentNegotiationManager内容协商管理器,默认使用基于请求头策略(HeaderContentNegotiationStrategy)
return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
}
//==================ContentNegotiationManager.resolveMediaTypes(NativeWebRequest request)============
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException
{
//遍历所有的内容协商管理器(在这里还未开启基于参数的内容协商策略 所以只有一个HeaderContentNegotiationStrategy)
for (ContentNegotiationStrategy strategy : this.strategies) {
//找到浏览器可以接受数据的类型
List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
continue;
}
return mediaTypes;
}
return MEDIA_TYPE_ALL_LIST;
}
//=========HeaderContentNegotiationStrategy.resolveMediaTypes(NativeWebRequest request)===========
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException {
//获取到ACCEPT字段的所有的值
String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
if (headerValueArray == null) {
return MEDIA_TYPE_ALL_LIST;
}
//将获取到的ACCEPT所有的字段数组转化为List
List<String> headerValues = Arrays.asList(headerValueArray);
try {
//将List转化为能够处理的所有媒体类型
List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
//按照q的权重进行从大到小排序
MediaType.sortBySpecificityAndQuality(mediaTypes);
//返回排序后的媒体类型
return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotAcceptableException(
"Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
}
}
}
4.1.2.2.2 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据 getProducibleMediaTypes(request, valueType, targetType)
protected List<MediaType> getProducibleMediaTypes(
HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
Set<MediaType> mediaTypes =
(Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<>(mediaTypes);
}
List<MediaType> result = new ArrayList<>();
//遍历所有的HttpMessageConverter(第一次HttpMessageConverter匹配)
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter && targetType != null) {
//如果有支持处理Person媒体类型的converter,把Person媒体类型的converter所支持的媒体类型统计出来。
if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
//将支持的媒体类型放到集合中去
result.addAll(converter.getSupportedMediaTypes(valueClass));
}
}
else if (converter.canWrite(valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes(valueClass));
}
}
return (result.isEmpty() ? Collections.singletonList(MediaType.ALL) : result);
}
4.1.2.2.3将Person对象以Json的方式写出去,write(body, targetType, selectedMediaType, outputMessage)
@Override
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//Accept:代表客户端能够接受的数据类型
//Content-Type:代表客户端|服务器发送的实体数据的数据类型
//获取响应头的Content-Type
final HttpHeaders headers = outputMessage.getHeaders();
//添加一个响应头Content-Type--->application/json
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
@Override
public OutputStream getBody() {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}));
}
else {
//响应JSON数据(将返回值Person,以JSON数据写出)
writeInternal(t, type, outputMessage);
outputMessage.getBody().flush();
}
}
//===AbstractJackson2HttpMessageConverter. writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)===
@Override
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//多余代码省略
//可用于将任何 Java 值序列化为JSON 输出的方法
objectWriter.writeValue(generator, value);
//多余代码省略
}
//==========================ObjectWriter. writeValue(JsonGenerator g, Object value)===========================================
public void writeValue(JsonGenerator g, Object value) throws IOException
{
_assertNotNull("g", g);
_configureGenerator(g);
if (_config.isEnabled(SerializationFeature.CLOSE_CLOSEABLE)
&& (value instanceof Closeable)) {
Closeable toClose = (Closeable) value;
try {
_prefetch.serialize(g, value, _serializerProvider());
if (_config.isEnabled(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)) {
g.flush();
}
} catch (Exception e) {
ClassUtil.closeOnFailAndThrowAsIOE(null, toClose, e);
return;
}
toClose.close();
} else {
_prefetch.serialize(g, value, _serializerProvider());
if (_config.isEnabled(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)) {
//真正把Person类型的返回值以json写出到客户端
g.flush();
}
}
}
//================================= UTF8JsonGenerator.java.flush()========================================
public void flush() throws IOException
{
_flushBuffer();
if (_outputStream != null) {
if (isEnabled(Feature.FLUSH_PASSED_TO_STREAM)) {
_outputStream.flush();
}
}
}
自此我们就完成了参数解析、绑定、handler对应的目标方法调用以及返回值写出到页面的操作。
接下来就要开始进行最后一步处理派发结果了,但是因为本次的例子只是为了响应一个JSON数据出来,并没有做渲染的处理,所以返回的ModelAndView为空,不用进行页面跳转等操作,而且因为篇幅有限我们把最后一步处理派发结果放到下一篇来讲解,敬请期待。。。
为什么排序过后还要进行一次HttpMessageConverter匹配,而不是直接拿到4.1.2.2.2匹配到的HttpMessageConverter使用?
答:
第一次匹配只是统计了具有能够处理Person对象的能力的HttpMessageConverter(可能有多个)
第二次匹配把当前第一次统计的HttpMessageConverter,选出真正能够把Person对象转化为目标媒体类型的HttpMessageConverter(只有唯一一个)
HttpMessageConverter: 看是否支持将此Class类型的对象,转为MediaType(就是请求头里面的application/json,application/xml等)类型的数据。
例子:Person对象转为JSON。或者 JSON转为Person
0 - 只支持Byte类型的
1 - String
2 - String
3 - Resource
4 - ResourceRegion
5 - DOMSource. class SAXSource. class ) \ StAXSource. class \ StreamSource. class Source. class
6 - MultiValueMap
7 - JSON
8 - JSON
9 - 支持注解方式xml处理的
最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)
导入了jackson处理xml的包,xml的converter就会自动进来
<dependency>
<groupId>com.fasterxml.jackson.dataformatgroupId>
<artifactId>jackson-dataformat-xmlartifactId>
dependency>
//===========================================WebMvcConfigurationSupport.java===================================================
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new ResourceRegionHttpMessageConverter());
if (!shouldIgnoreXml) {
try {
messageConverters.add(new SourceHttpMessageConverter<>());
}
catch (Throwable ex) {
// Ignore when no TransformerFactory implementation is available...
}
}
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (romePresent) {
messageConverters.add(new AtomFeedHttpMessageConverter());
messageConverters.add(new RssChannelHttpMessageConverter());
}
//导入了jackson处理xml的包,xml的converter就会自动进来
if (!shouldIgnoreXml) {
if (jackson2XmlPresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
}
else if (jaxb2Present) {
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
}
if (kotlinSerializationJsonPresent) {
messageConverters.add(new KotlinSerializationJsonHttpMessageConverter());
}
if (jackson2Present) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
else if (gsonPresent) {
messageConverters.add(new GsonHttpMessageConverter());
}
else if (jsonbPresent) {
messageConverters.add(new JsonbHttpMessageConverter());
}
if (jackson2SmilePresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
}
if (jackson2CborPresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
}
}
除了使用SpringMVC自带的MessageConverter和第三方导入的外,我们还可以自定义MessageConverter实现自定义的内容协商
需求:
如果是浏览器发送请求返回XML(application/xml)—>找到JacksonXmlConverter
如果是AJAX发送请求返回JSON(application/json)—>找到 JacksonJSONConverter
如果是某个app发请求,返回自定义协商数据 (application/x-app)—>找到 xxxxConverter
自定义内容:属性1;属性2
实现步骤:
1.添加自定义的MessageConverter
2.系统底层会统计出所有能够处理该类型的MessageConverter
3.客户端内容协商(x-app—>xxxxConverter)
1.在webConfig配置类中注册WebMvcConfigurer定制化SpringMVC的功能,重写extendMessageConverters添加扩展自定义的MessageConverter
@Configuration(proxyBeanMethods = false)
public class webConfig {
/**
* WebMvcConfigurer定制化SpringMVC的功能
*
* @return
*/
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
/**
* 添加扩展自定义的MessageConverter
* @param converters
*/
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new XXXXMessageConverter());
}
};
}
}
2.创建XXXXMessageConverter实现 HttpMessageConverter接口用于制定自定义消息类型转换器规则
/**
* 自定义的converter
* @author WxrStart
* @create 2022-03-30 8:20
*/
public class XXXXMessageConverter implements HttpMessageConverter<Person> {
/**
* 是否可读
*
* @param clazz
* @param mediaType
* @return
*/
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}
/**
* 是否可写
*
* @param clazz
* @param mediaType
* @return
*/
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
//如果传入的类是Person类就返回true
return clazz.isAssignableFrom(Person.class);
}
/**
* 服务器要统计所有的MessageConverter都能写出哪些内容类型
* 相当于在这里告诉SpringMVC我们这个XXXXMessageConverter能支持哪些媒体类型
* application/x-app类型
*
* @return
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
// MediaType.parseMediaTypes()将字符串类型转化为媒体类型
return MediaType.parseMediaTypes("application/x-app");
}
@Override
public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
/**
* 自定义协议的数据写出
*
* @param person
* @param contentType
* @param outputMessage
* @throws IOException
* @throws HttpMessageNotWritableException
*/
@Override
public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//自定义协议数据的写出
String data = person.getUserName() + ";" + person.getAge() + ";" + person.getBirth();
//将自定义的数据通过输出流写出去
OutputStream body = outputMessage.getBody();
body.write(data.getBytes());
}
}
3.用postman测试结果
4.补充在浏览器地址栏使用?format=x-app实现自定义消息类型转换
实现:
1.开启浏览器的请求参数内容协商模式
spring:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
2.在webConfig配置类中注册WebMvcConfigurer定制化SpringMVC的功能,重写extendMessageConverters添加扩展自定义的MessageConverter
@Configuration(proxyBeanMethods = false)
public class webConfig {
/**
* WebMvcConfigurer定制化SpringMVC的功能
*
* @return
*/
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
/**
* 自定义内容协商策略
* @param configurer
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
//使用参数的内容协商策略
Map<String, MediaType> map = new HashMap();
//添加自定义类型
map.put("json", MediaType.APPLICATION_JSON);
map.put("xml", MediaType.APPLICATION_XML);
map.put("x-app", MediaType.parseMediaType("application/x-app"));
//指定支持解析哪些参数对应的媒体类型
ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(map);
configurer.strategies(Arrays.asList(parameterStrategy));
}
/**
* 添加扩展自定义的MessageConverter
* @param converters
*/
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new XXXXMessageConverter());
}
};
}
}
3.创建XXXXMessageConverter实现 HttpMessageConverter接口用于制定自定义消息类型转换器规则
/**
* 自定义的converter
*
* @author WxrStart
* @create 2022-03-30 8:20
*/
public class XXXXMessageConverter implements HttpMessageConverter<Person> {
/**
* 是否可读
*
* @param clazz
* @param mediaType
* @return
*/
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}
/**
* 是否可写
*
* @param clazz
* @param mediaType
* @return
*/
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
//如果传入的类是Person类就返回true
return clazz.isAssignableFrom(Person.class);
}
/**
* 服务器要统计所有的MessageConverter都能写出哪些内容类型
* 相当于在这里告诉SpringMVC我们这个XXXXMessageConverter能支持哪些媒体类型
* application/x-app类型
*
* @return
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
// MediaType.parseMediaTypes()将字符串类型转化为媒体类型
return MediaType.parseMediaTypes("application/x-app");
}
@Override
public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
/**
* 自定义协议的数据写出
*
* @param person
* @param contentType
* @param outputMessage
* @throws IOException
* @throws HttpMessageNotWritableException
*/
@Override
public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//自定义协议数据的写出
String data = person.getUserName() + ";" + person.getAge() + ";" + person.getBirth();
//将自定义的数据通过输出流写出去
OutputStream body = outputMessage.getBody();
body.write(data.getBytes());
}
}
4.用浏览器测试结果
原理:
在4.1.2.2.1中,会调用内容协商部分的getAcceptableMediaTypes,使用开启基于请求参数的内容协商功能
spring:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
当浏览器携带了
http://localhost:8080/saveuser?format=json
http://localhost:8080/saveuser?format=xml
http://localhost:8080/saveuser?format=x-app
到4.1.2.2.1内容协商的时候,会使用到我们刚刚开启的请求参数内容协商模式
在contentNegotiationManager内容协商管理器中,使用自定义的参数内容协商策略
补充说明:
在添加了基于浏览器的自定义参数协商策略后,基于请求头策略(HeaderContentNegotiationStrategy)的效果就被覆盖掉了,再postman的测试下,所有的accept请求头都会按最高权重匹配到application/json,不管是传x-app,x-xml都是application/json数据类型,所以导致了默认功能失效。
有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。
我们可以完全靠自定义内容协商策略的时候把基于请求头策略也加进去
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
//使用参数的内容协商策略
Map<String, MediaType> map = new HashMap();
map.put("json",MediaType.APPLICATION_JSON);
map.put("xml",MediaType.APPLICATION_XML);
map.put("x-app",MediaType.parseMediaType("application/x-app"));
HeaderContentNegotiationStrategy headerContentNegotiationStrategy = new HeaderContentNegotiationStrategy();
//指定支持解析哪些参数对应的媒体类型
ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(map);
//将这个基于请求头的策略也加进去
configurer.strategies(Arrays.asList(parameterStrategy,headerContentNegotiationStrategy));
}
大家考虑,上述功能除了我们完全自定义外?SpringBoot有没有为我们提供基于配置文件的快速修改媒体类型功能?怎么配置呢?【提示:参照SpringBoot官方文档web开发内容协商章节】