SpringMVC 源码解析笔记
作者笔记仓库:https://github.com/seazean/javanotes
欢迎各位关注我的笔记仓库,clone 仓库到本地后使用 Typora 阅读效果更好。
一、调度函数
请求进入原生的 HttpServlet 的 doGet() 方法处理,调用子类 FrameworkServlet 的 doGet() 方法,最终调用 DispatcherServlet 的 doService() 方法,为请求设置相关属性后调用 doDispatch(),请求和响应的以参数的形式传入

//request 和 response 为 Java 原生的类
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);
// 找到当前请求使用哪个 HandlerMapping(Controller的方法)处理,返回执行链
mappedHandler = getHandler(processedRequest);
// 没有合适的处理请求的方式 HandlerMapping 直接返回
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//根据映射器获取当前 handler 处理器适配器,用来处理当前的请求
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 获取发出此次请求的方法
String method = request.getMethod();
// 判断请求是不是 GET 方法
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 执行处理方法,返回的是 ModelAndView 对象,封装了所有的返回值数据
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 设置视图名字
applyDefaultViewName(processedRequest, mv);
// 执行拦截器链中的方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
}
// 处理 程序调用的结果,进行结果派发
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
//....
}
笔记参考视频:https://www.bilibili.com/video/BV19K4y1L7MT
二、请求映射
2.1 映射器
doDispatch() 中调用 getHandler 方法获取所有的映射器
总体流程:
所有的请求映射都在 HandlerMapping 中,RequestMappingHandlerMapping 处理 @RequestMapping 注解的所有映射规则
遍历所有的 HandlerMapping 看是否可以匹配当前请求,匹配成功后返回,匹配失败设置 HTTP 404 响应码
用户可以自定义的映射处理,也可以给容器中放入自定义 HandlerMapping
访问 URL:http://localhost:8080/user
@GetMapping("/user")
public String getUser(){
return "GET";
}
HandlerMapping 处理器映射器,保存了所有 @RequestMapping 和 handler 的映射规则
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
//遍历所有的 HandlerMapping
for (HandlerMapping mapping : this.handlerMappings) {
//尝试去每个 HandlerMapping 中匹配当前请求的处理
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}

mapping.getHandler(request):调用 AbstractHandlerMapping#getHandlerObject handler = getHandlerInternal(request):获取映射器,底层调用 RequestMappingInfoHandlerMapping 类的方法,又调用 AbstractHandlerMethodMapping#getHandlerInternalString lookupPath = initLookupPath(request):地址栏的 uri,这里的 lookupPath 为 /userthis.mappingRegistry.acquireReadLock():防止并发handlerMethod = lookupHandlerMethod(lookupPath, request):获取当前 HandlerMapping 中的映射规则AbstractHandlerMethodMapping.lookupHandlerMethod():
directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath):获取当前的映射器与当前请求的 URI 有关的所有映射规则
addMatchingMappings(directPathMatches, matches, request):匹配某个映射规则for (T mapping : mappings):遍历所有的映射规则match = getMatchingMapping(mapping, request):去匹配每一个映射规则,匹配失败返回 nullmatches.add(new Match()):匹配成功后封装成匹配器添加到匹配集合中
Match bestMatch = matches.get(0):匹配完成只剩一个,直接获取返回对应的处理方法if (matches.size() > 1):当有多个映射规则符合请求时,报错return bestMatch.getHandlerMethod():返回匹配器中的处理方法
executionChain = getHandlerExecutionChain(handler, request):为当前请求和映射器的构建一个拦截器链return executionChain:返回拦截器链,包含 HandlerMapping 和拦截方法
2.2 适配器
doDispatch() 中 调用 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler())
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
//遍历所有的 HandlerAdapter
for (HandlerAdapter adapter : this.handlerAdapters) {
//判断当前适配器是否支持当前 handle
//return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler))
//这里返回的是True,
if (adapter.supports(handler)) {
//返回的是 RequestMappingHandlerAdapter
return adapter;
}
}
}
throw new ServletException();
}
2.3 方法执行
2.3.1 执行流程
实例代码:
@GetMapping("/params")
public String param(Map<String, Object> map, Model model, HttpServletRequest request) {
map.put("k1", "v1"); //都可以向请求域中添加数据
model.addAttribute("k2", "v2"); //它们两个都在数据封装在 BindingAwareModelMap
request.setAttribute("m", "HelloWorld");
return "forward:/success";
}

doDispatch() 中调用 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()) 执行目标方法
AbstractHandlerMethodAdapter#handle → RequestMappingHandlerAdapter#handleInternal
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
// 是否是 Session 加锁
if (this.synchronizeOnSession) {
} else {
//使用适配器执行方法
mav = invokeHandlerMethod(request, response, handlerMethod);
}
//是否在响应头中设置了缓存的属性
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
//是否通过 SessionAttributes 设置了会话属性
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
//使用给定的缓存秒数并生成相应的 HTTP 标头
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
//根据此生成器的设置准备接受的响应,设置缓存的时间
prepareResponse(response);
}
}
return mav;
}
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
//封装成 SpringMVC 的接口,用于通用 Web 请求拦截器,使能够访问通用请求元数据,而不是用于实际处理请求
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
//WebDataBinder 用于从 Web 请求参数到 JavaBean 对象的数据绑定,获取创建该实例的工厂
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
//创建 Model 实例,用于向模型添加属性
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
//方法执行器
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
//参数解析器,有很多
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
//返回值处理器,也有很多
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
//设置数据绑定器
invocableMethod.setDataBinderFactory(binderFactory);
//设置参数检查器
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
//新建一个 ModelAndViewContainer 并进行初始化和一些属性的填充
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
//设置一些属性
//执行目标方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
//异步请求
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
// 获取 ModelAndView 对象,封装了 ModelAndViewContainer
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
ServletInvocableHandlerMethod#invokeAndHandle:执行目标方法
returnValue = invokeForRequest(webRequest, mavContainer, providedArgs):执行自己写的 controller 方法,返回的就是自定义方法中 return 的值Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs):参数处理的逻辑,遍历所有的参数解析器解析参数或者将 URI 中的参数进行绑定,绑定完成后开始执行目标方法parameters = getMethodParameters():获取此处理程序方法的方法参数的详细信息Object[] args = new Object[parameters.length]:存放所有的参数for (int i = 0; i < parameters.length; i++):遍历所有的参数args[i] = findProvidedArgument(parameter, providedArgs):获取调用方法时提供的参数,一般是空if (!this.resolvers.supportsParameter(parameter)):寻找可以解析当前参数的参数解析器return getArgumentResolver(parameter) != null:获取参数的解析是否为空for (HandlerMethodArgumentResolver resolver : this.argumentResolvers):遍历容器内所有的解析器if (resolver.supportsParameter(parameter)):是否支持当前参数PathVariableMethodArgumentResolver#supportsParameter:解析标注 @PathVariable 注解的参数ModelMethodProcessor#supportsParameter:解析 Map 类型的参数ModelMethodProcessor#supportsParameter:解析 Model 类型的参数,Model 和 Map 的作用一样ExpressionValueMethodArgumentResolver#supportsParameter:解析标注 @Value 注解的参数RequestParamMapMethodArgumentResolver#supportsParameter:解析标注 @RequestParam 注解ModelAttributeMethodProcessor#supportsParameter:解析标注 @ModelAttribute 注解或者不是简单类型- 子类 ServletModelAttributeMethodProcessor 是解析自定义类型 JavaBean 的解析器
- 简单类型有 Void、Enum、Number、CharSequence、Date、URI、URL、Locale、Class
args[i] = this.resolvers.resolveArgument():开始解析参数,每个参数使用的解析器不同resolver = getArgumentResolver(parameter):获取参数解析器return resolver.resolveArgument():开始解析PathVariableMapMethodArgumentResolver#resolveArgument:@PathVariable,包装 URI 中的参数为 MapMapMethodProcessor#resolveArgument:调用mavContainer.getModel()返回默认的 BindingAwareModelMap 对象ModelAttributeMethodProcessor#resolveArgument:自定义的 JavaBean 的绑定封装,下一小节详解
return doInvoke(args):真正的执行方法Method method = getBridgedMethod():从 HandlerMethod 获取要反射执行的方法ReflectionUtils.makeAccessible(method):破解权限method.invoke(getBean(), args):执行方法,getBean 获取的是标记 @Controller 的 Bean 类,其中包含执行方法
进行返回值的处理,响应部分详解,处理完成进入下面的逻辑
RequestMappingHandlerAdapter#getModelAndView:获取 ModelAndView 对象
modelFactory.updateModel(webRequest, mavContainer):Model 数据升级到会话域(请求域中的数据在重定向时丢失)updateBindingResult(request, defaultModel):把绑定的数据添加到 Model 中if (mavContainer.isRequestHandled()):判断请求是否已经处理完成了ModelMap model = mavContainer.getModel():获取包含 Controller 方法参数的 BindingAwareModelMap 对象(本节开头)mav = new ModelAndView():把 ModelAndViewContainer 和 ModelMap 中的数据封装到 ModelAndViewif (!mavContainer.isViewReference()):视图是否是通过名称指定视图引用if (model instanceof RedirectAttributes):判断 model 是否是重定向数据,如果是进行重定向逻辑return mav:任何方法执行都会返回 ModelAndView 对象
2.3.2 参数解析
解析自定义的 JavaBean 为例
Person.java:
@Data
@Component //加入到容器中
public class Person {
private String userName;
private Integer age;
private Date birth;
}
Controller:
@RestController //返回的数据不是页面
public class ParameterController {
// 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
@GetMapping("/saveuser")
public Person saveuser(Person person){
return person;
}
}
访问 URL:http://localhost:8080/saveuser?userName=zhangsan&age=20
进入源码:ModelAttributeMethodProcessor#resolveArgument
name = ModelFactory.getNameForParameter(parameter):获取名字,此例就是 personann = parameter.getParameterAnnotation(ModelAttribute.class):是否有 ModelAttribute 注解if (mavContainer.containsAttribute(name)):ModelAndViewContainer 中是否包含 person 对象attribute = createAttribute():创建一个实例,空的 Person 对象binder = binderFactory.createBinder(webRequest, attribute, name):Web 数据绑定器,可以利用 Converters 将请求数据转成指定的数据类型,绑定到 JavaBean 中bindRequestParameters(binder, webRequest):利用反射向目标对象填充数据servletBinder = (ServletRequestDataBinder) binder:类型强转servletBinder.bind(servletRequest):绑定数据mpvs = new MutablePropertyValues(request.getParameterMap()):获取请求 URI 参数中的 KV 键值对addBindValues(mpvs, request):子类可以用来为请求添加额外绑定值doBind(mpvs):真正的绑定的方法,调用applyPropertyValues应用参数值,然后调用setPropertyValues方法AbstractPropertyAccessor#setPropertyValues():List<PropertyValue> propertyValues:获取到所有的参数的值,就是 URI 上的所有的参数值for (PropertyValue pv : propertyValues):遍历所有的参数值setPropertyValue(pv):填充到空的 Person 实例中nestedPa = getPropertyAccessorForPropertyPath(propertyName):获取属性访问器tokens = getPropertyNameTokens():获取元数据的信息nestedPa.setPropertyValue(tokens, pv):填充数据processLocalProperty(tokens, pv):处理属性if (!Boolean.FALSE.equals(pv.conversionNecessary)):数据是否需要转换了if (pv.isConverted()):数据已经转换过了,转换了直接赋值,没转换进行转换oldValue = ph.getValue():获取未转换的数据valueToApply = convertForProperty():进行数据转换TypeConverterDelegate#convertIfNecessary:进入该方法的逻辑if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)):判断能不能转换GenericConverter converter = getConverter(sourceType, targetType):获取类型转换器converter = this.converters.find(sourceType, targetType):寻找合适的转换器sourceCandidates = getClassHierarchy(sourceType.getType()):原数据类型targetCandidates = getClassHierarchy(targetType.getType()):目标数据类型for (Class<?> sourceCandidate : sourceCandidates) {
//双重循环遍历,寻找合适的转换器
for (Class<?> targetCandidate : targetCandidates) {
GenericConverter converter = getRegisteredConverter(..):匹配类型转换器return converter:返回转换器
conversionService.convert(newValue, sourceTypeDesc, typeDescriptor):开始转换converter = getConverter(sourceType, targetType):获取可用的转换器result = ConversionUtils.invokeConverter():执行转换方法converter.convert():调用转换器的转换方法(GenericConverter#convert)
return handleResult(sourceType, targetType, result):返回结果
ph.setValue(valueToApply):设置 JavaBean 属性(BeanWrapperImpl.BeanPropertyHandler)Method writeMethod:获取 set 方法Class<?> cls = getClass0():获取 Class 对象writeMethodName = Introspector.SET_PREFIX + getBaseName():set 前缀 + 属性名writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args):获取只包含一个参数的 set 方法setWriteMethod(writeMethod):加入缓存
ReflectionUtils.makeAccessible(writeMethod):设置访问权限writeMethod.invoke(getWrappedInstance(), value):执行方法
bindingResult = binder.getBindingResult():获取绑定的结果mavContainer.addAllAttributes(bindingResultModel):把所有填充的参数放入 ModelAndViewContainerreturn attribute:返回填充后的 Person 对象
三、响应处理
3.1 响应数据
以 Person 为例:
@ResponseBody //利用返回值处理器里面的消息转换器进行处理
@GetMapping(value = "/person")
public Person getPerson(){
Person person = new Person();
person.setAge(28);
person.setBirth(new Date());
person.setUserName("zhangsan");
return person;
}
直接进入方法执行完后的逻辑 ServletInvocableHandlerMethod#invokeAndHandle:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 执行目标方法,return person 对象
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 {
// 返回值的处理
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {}
}
没有加 @ResponseBody 注解的返回数据按照视图(页面)处理的逻辑,ViewNameMethodReturnValueHandler(视图详解)
此例是加了注解的,返回的数据不是视图,HandlerMethodReturnValueHandlerComposite#handleReturnValue:
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
//获取合适的返回值处理器
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException();
}
//使用处理器处理返回值(详解源码中的这两个函数)
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
HandlerMethodReturnValueHandlerComposite#selectHandler:
boolean isAsyncValue = isAsyncReturnValue(value, returnType):是否是异步请求for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers):遍历所有的返回值处理器RequestResponseBodyMethodProcessor#supportsReturnType:处理标注 @ResponseBody 注解的返回值ModelAndViewMethodReturnValueHandler#supportsReturnType:处理返回值类型是 ModelAndView 的处理器ModelAndViewResolverMethodReturnValueHandler#supportsReturnType:直接返回 true,处理所有数据
RequestResponseBodyMethodProcessor#handleReturnValue:处理返回值
mavContainer.setRequestHandled(true):设置请求处理完成inputMessage = createInputMessage(webRequest):获取输入的数据outputMessage = createOutputMessage(webRequest):获取输出的数据writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage):使用消息转换器进行写出if (value instanceof CharSequence):判断返回的数据是不是字符类型body = value:把 value 赋值给 body,此时 body 中就是填充后的 Person 对象if (isResourceType(value, returnType)):当前数据是不是流数据MediaType selectedMediaType:内容协商后选择使用的类型,浏览器和服务器都支持的媒体(数据)类型MediaType contentType = outputMessage.getHeaders().getContentType():获取响应头的数据if (contentType != null && contentType.isConcrete()):判断当前响应头中是否已经有确定的媒体类型selectedMediaType = contentType:说明前置处理已经使用了媒体类型,直接继续使用该类型acceptableTypes = getAcceptableMediaTypes(request):获取浏览器支持的媒体类型,请求头字段this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request)):调用该方法for(ContentNegotiationStrategy strategy:this.strategies):默认策略是提取请求头的字段的内容,策略类为HeaderContentNegotiationStrategy,可以配置添加其他类型的策略List<MediaType> mediaTypes = strategy.resolveMediaTypes(request):解析 Accept 字段存储为 ListheaderValueArray = request.getHeaderValues(HttpHeaders.ACCEPT):获取请求头中 Accept 字段List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues):解析成 List 集合MediaType.sortBySpecificityAndQuality(mediaTypes):按照相对品质因数 q 降序排序

producibleTypes = getProducibleMediaTypes(request, valueType, targetType):服务器能生成的媒体类型request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE):从请求域获取默认的媒体类型for (HttpMessageConverter<?> converter : this.messageConverters):遍历所有的消息转换器converter.canWrite(valueClass, null):是否支持当前的类型result.addAll(converter.getSupportedMediaTypes()):把当前 MessageConverter 支持的所有类型放入 result
List<MediaType> mediaTypesToUse = new ArrayList<>():存储最佳匹配内容协商:
for (MediaType requestedType : acceptableTypes) { //遍历所有的浏览器能接受的媒体类型
for (MediaType producibleType : producibleTypes) { //遍历所有服务器能产出的
if (requestedType.isCompatibleWith(producibleType)) { //判断类型是否匹配,最佳匹配
//数据协商匹配成功
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse):按照相对品质因数 q 排序,降序排序,越大的越好for (MediaType mediaType : mediaTypesToUse):遍历所有的最佳匹配selectedMediaType = mediaType:赋值给选择的类型selectedMediaType = selectedMediaType.removeQualityValue():媒体类型去除相对品质因数for (HttpMessageConverter<?> converter : this.messageConverters):遍历所有的 HTTP 数据转换器GenericHttpMessageConverter genericConverter:MappingJackson2HttpMessageConverter 可以将对象写为 JSON((GenericHttpMessageConverter) converter).canWrite():转换器是否可以写出给定的类型AbstractJackson2HttpMessageConverter#canWritif (!canWrite(mediaType)):是否可以写出指定类型MediaType.ALL.equalsTypeAndSubtype(mediaType):是不是*/*类型getSupportedMediaTypes():支持application/json和application/*+json两种类型return true:返回 true
objectMapper = selectObjectMapper(clazz, mediaType):选择可以使用的 objectMappercauseRef = new AtomicReference<>():获取并发安全的引用if (objectMapper.canSerialize(clazz, causeRef)):objectMapper 可以序列化当前类return true:返回 true
body = getAdvice().beforeBodyWrite():要响应的所有数据,Person 对象addContentDispositionHeader(inputMessage, outputMessage):检查路径genericConverter.write(body, targetType, selectedMediaType, outputMessage):调用消息转换器的 write 方法AbstractGenericHttpMessageConverter#write:该类的方法addDefaultHeaders(headers, t, contentType):设置响应头中的数据类型
writeInternal(t, type, outputMessage):真正的写出数据的函数Object value = object:value 引用 Person 对象ObjectWriter objectWriter = objectMapper.writer():获取用来输出 JSON 对象的 ObjectWriterobjectWriter.writeValue(generator, value):写出数据为 JSON
3.2 协商策略
开启基于请求参数的内容协商模式:(SpringBoot 方式)
spring.mvc.contentnegotiation:favor-parameter: true #开启请求参数内容协商模式
发请求: http://localhost:8080/person?format=json,解析 format
策略类为 ParameterContentNegotiationStrategy,运行流程如下:
acceptableTypes = getAcceptableMediaTypes(request):获取浏览器支持的媒体类型mediaTypes = strategy.resolveMediaTypes(request):解析请求 URL 参数中的数据return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest)):getMediaTypeKey(webRequest):request.getParameter(getParameterName()):获取 URL 中指定的需求的数据类型getParameterName():获取参数的属性名 formatgetParameter():获取 URL 中 format 对应的数据
resolveMediaTypeKey():解析媒体类型,封装成集合
自定义内容协商策略:
public class WebConfig implements WebMvcConfigurer {
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override //自定义内容协商策略
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json", MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_XML);
mediaTypes.put("person",MediaType.parseMediaType("application/x-person"));
//指定支持解析哪些参数对应的哪些媒体类型
ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
//请求头解析
HeaderContentNegotiationStrategy headStrategy = new HeaderContentNegotiationStrategy();
//添加到容器中,即可以解析请求头 又可以解析请求参数
configurer.strategies(Arrays.asList(parameterStrategy,headStrategy));
}
@Override //自定义消息转换器
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new GuiguMessageConverter());
}
}
}
}
也可以自定义 HttpMessageConverter,实现 HttpMessageConverter 接口重写方法即可
四、视图解析
4.1 返回解析
请求处理:
@GetMapping("/params")
public String param(){
return "forward:/success";
//return "redirect:/success";
}
进入执行方法逻辑 ServletInvocableHandlerMethod#invokeAndHandle,进入 this.returnValueHandlers.handleReturnValue:
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
//获取合适的返回值处理器:调用 if (handler.supportsReturnType(returnType))判断是否支持
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException();
}
//使用处理器处理返回值
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
ViewNameMethodReturnValueHandler#supportsReturnType
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
//返回值是否是void 或者 是 CharSequence 字符序列
return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}
ViewNameMethodReturnValueHandler#handleReturnValue
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
// 返回值是字符串,是 return "forward:/success"
if (returnValue instanceof CharSequence) {
String viewName = returnValue.toString();
//把视图名称设置进入 ModelAndViewContainer 中
mavContainer.setViewName(viewName);
//判断是否是重定向数据 `viewName.startsWith("redirect:")`
if (isRedirectViewName(viewName)) {
//如果是重定向,设置是重定向指令
mavContainer.setRedirectModelScenario(true);
}
}
else if (returnValue != null) {
// should not happen
throw new UnsupportedOperationException();
}
}
4.2 结果派发
doDispatch()中的 processDispatchResult:处理派发结果
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler,
@Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
}
// mv 是 ModelAndValue
if (mv != null && !mv.wasCleared()) {
// 渲染视图
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {}
}
DispatcherServlet#render:
Locale locale = this.localeResolver.resolveLocale(request):国际化相关String viewName = mv.getViewName():视图名字,是请求转发 forward:/success(响应数据部分解析了该名字存入 ModelAndView 是通过 ViewNameMethodReturnValueHandler)view = resolveViewName(viewName, mv.getModelInternal(), locale, request):解析视图for (ViewResolver viewResolver : this.viewResolvers):遍历所有的视图解析器view = viewResolver.resolveViewName(viewName, locale):根据视图名字解析视图,调用内容协商视图处理器 ContentNegotiatingViewResolver 的方法attrs = RequestContextHolder.getRequestAttributes():获取请求的相关属性信息requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest()):获取最佳匹配的媒体类型,函数内进行了匹配的逻辑candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes):获取候选的视图对象for (ViewResolver viewResolver : this.viewResolvers):遍历所有的视图解析器View view = viewResolver.resolveViewName(viewName, locale):解析视图AbstractCachingViewResolver#resolveViewName:调用此方法请求转发:实例为 InternalResourceView
returnview = createView(viewName, locale):UrlBasedViewResolver#createViewif (viewName.startsWith(FORWARD_URL_PREFIX)):视图名字是否是forward:的前缀forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()):名字截取前缀view = new InternalResourceView(forwardUrl):新建 InternalResourceView 对象并返回return applyLifecycleMethods(FORWARD_URL_PREFIX, view):Spring 中的初始化操作
重定向:实例为 RedirectView
returnview = createView(viewName, locale):UrlBasedViewResolver#createViewif (viewName.startsWith(REDIRECT_URL_PREFIX)):视图名字是否是redirect:的前缀redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()):名字截取前缀RedirectView view = new RedirectView():新建 RedirectView 对象并返回
bestView = getBestView(candidateViews, requestedMediaTypes, attrs):选出最佳匹配的视图对象
view.render(mv.getModelInternal(), request, response):页面渲染mergedModel = createMergedOutputModel(model, request, response):把请求域中的数据封装到 MapprepareResponse(request, response):响应前的准备工作,设置一些响应头renderMergedOutputModel(mergedModel, getRequestToExpose(request), response):渲染输出的数据请求转发 InternalResourceView 的逻辑:
getRequestToExpose(request):获取 Servlet 原生的方式exposeModelAsRequestAttributes(model, request):暴露 model 作为请求域的属性model.forEach():遍历 Model 中的数据request.setAttribute(name, value):设置到请求域中
exposeHelpers(request):自定义接口dispatcherPath = prepareForRendering(request, response):确定调度分派的路径,此例是 /successrd = getRequestDispatcher(request, dispatcherPath):获取 Servlet 原生的 RequestDispatcher 实现转发rd.forward(request, response):实现请求转发
重定向 RedirectView 的逻辑:
targetUrl = createTargetUrl(model, request):获取目标 URLenc = request.getCharacterEncoding():设置编码 UTF-8appendQueryProperties(targetUrl, model, enc):添加一些属性,比如url + ?name=123&&age=324sendRedirect(request, response, targetUrl, this.http10Compatible):重定向response.sendRedirect(encodedURL):使用 Servlet 原生方法实现重定向
SpringMVC 源码解析笔记的更多相关文章
- SpringMVC源码解析- HandlerAdapter - ModelFactory(转)
ModelFactory主要是两个职责: 1. 初始化model 2. 处理器执行后将modle中相应参数设置到SessionAttributes中 我们来看看具体的处理逻辑(直接充当分析目录): 1 ...
- springMVC源码解析--ViewResolver视图解析器执行(三)
之前两篇博客springMVC源码分析--ViewResolver视图解析器(一)和springMVC源码解析--ViewResolverComposite视图解析器集合(二)中我们已经简单介绍了一些 ...
- SpringMVC源码解析- HandlerAdapter - ModelFactory
ModelFactory主要是两个职责: 1. 初始化model 2. 处理器执行后将modle中相应参数设置到SessionAttributes中 我们来看看具体的处理逻辑(直接充当分析目录): 1 ...
- spring-mvc源码阅读笔记
简要的做一些spring-mvc部分的源码学习笔记 Spring-mvc做的工作主要是俩大方面吧:一个是初始化一个ioc容器,一个是mvc部分的控制和视图模块的实现. 先说下ioc容器的初始化部分:i ...
- springMVC源码解析--ViewResolverComposite视图解析器集合(二)
上一篇博客springMVC源码分析--ViewResolver视图解析器(一)中我们介绍了一些springMVC提供的很多视图解析器ViewResolver,在开发的一套springMVC系统中是可 ...
- springMVC源码解析--HandlerMethodArgumentResolverComposite参数解析器集合(二)
上一篇博客springMVC源码分析--HandlerMethodArgumentResolver参数解析器(一)中我们已经介绍了参数解析相关的东西,并且也提到了HandlerMethodArgume ...
- springmvc源码解析MvcNamespaceHandler之<mvc:view-resolvers>
说在前面 本次主要介绍springmvc配置解析. springmvc配置解析 本次介绍MvcNamespaceHandler. 进入到这个方法org.springframework.web.serv ...
- SpringMVC源码解析
一:springmvc运行过程: 1. dispatcherServlet 通过 HandlerMapping 找到controller2. controller经过后台逻辑处理得到结果集modela ...
- 深入了解SpringMVC源码解析
Spring MVC源码解析 Spring MVC的使用原理其实是通过配置一个Servlet来接管所有的请求,所有的请求由这个Servlet来进行分发处理. 我们可以从web.xml里面看出这一点 & ...
随机推荐
- win10下简单截图
win10 下面可以 win+shift+s 拖动截图,个人感觉是最简单的
- 阿里云中quick bi用地图分析数据时维度需转换为地理区域类型
1.到数据集里面点击编辑要做地图分析的数据集 2.找到要分析的地理维度字段,选择转换为对应的类型,这里为市级,所以选择转换为市,其它类似,然后点击右上角保存即可. 3.返回数据集,点击新建仪表板 4. ...
- jQuery筛选选择器
<!DOCTYPE html><html><head> <meta http-equiv="Content-type" conten ...
- excel函数提取内容中的汉字
RIGHT(A2,LENB(A2)-LEN(A2)) 函数LENB将每个汉字(双字节字符)的字符数按2计数,LEN函数则对所有的字符都按1计数.因此"LENB(A2)-LEN(A2)&quo ...
- 分布式AKF拆分原则
1. 前言 当我们需要分布式系统提供更强的性能时,该怎样扩展系统呢?什么时候该加机器?什么时候该重构代码?扩容时,究竟该选择哈希算法还是最小连接数算法,才能有效提升性能? 在面对 Scalabilit ...
- 深入学习Netty(3)——传统AIO编程
前言 之前已经整理过了BIO.NIO两种I/O的相关博文,每一种I/O都有其特点,但相对开发而言,肯定是要又高效又简单的I/O编程才是真正需要的,在之前的NIO博文(深入学习Netty(2)--传统N ...
- 资源:VMware秘钥许可证
一. 激活密钥 YG5H2-ANZ0H-M8ERY-TXZZZ-YKRV8 UG5J2-0ME12-M89WY-NPWXX-WQH88 UA5DR-2ZD4H-089FY-6YQ5T-YPRX6 GA ...
- 《PHP基础知识总结》系列分享专栏
总结PHP基础知识,对初学者还是高手都值得参考巩固. <PHP基础知识总结>已整理成PDF文档,点击可直接下载至本地查阅https://www.webfalse.com/read/2017 ...
- 浅谈C++11中的多线程(一)
摘要 本篇文章围绕以下几个问题展开: 进程和线程的区别 何为并发?C++中如何解决并发问题?C++中多线程的基本操作 同步互斥原理以及多进程和多线程中实现同步互斥的两种方法 Qt中的多线程应用 c++ ...
- FastTunnel-内网穿透原理揭秘
之前写了一篇关于GVP开源项目FastTunnel的一篇介绍 <FastTunnel-开源内网穿透框架>,只简单介绍了使用方法,不少伙伴对其原理表示好奇,今天画抽空了一下其内部实现原理流程 ...