springboot内嵌容器mvc执行过程
2022-01-12 17:40:35 5 举报
AI智能生成
springboot内嵌容器mvc执行过程
作者其他创作
大纲/内容
springboot(2.1.3.RELEASE)内嵌容器mvc执行过程
1. 在容器启动后第一次发起请求时触发Serlvet#init方法初始化
1. 在 \"springboot内嵌容器初始化#2.7.9.2.2.5\" 时会将DispatcherServlet注册到servlet上下文中
springboot内嵌容器初始化
2. 最终会执行的DispatcherServlet#initStrategies方法,原因见链接中的uml图
DispatcherServlet继承关系uml图
1. 初始化文件上传相关 initMultipartResolver(context);
1. 从bean容器中取名称为multipartResolver的bean赋值给this#multipartResolver MultipartAutoConfiguration配置中会注册该名称的bean,类型为StandardServletMultipartResolver font color=\"#0076b3\
2. 初始化国际化相关 initLocaleResolver(context);
1. 从bean容器中获取名称为localeResolver的bean赋值给this#localeResolver 在配置了spring.mvc.locale的时候WebMvcAutoConfiguration中会注册该bean 如果没有渠道localeResolver的bean则使用默认策略给this#localeResolver 赋值为AcceptHeaderLocaleResolver对象 font color=\"#0076b3\
3. 初始化主题相关,用得较少 initThemeResolver(context);
4. 初始化请求处理映射 initHandlerMappings(context);
1. 如果this.detectAllHandlerMappings为true(默认为true) 则从bean容器中取出HandlerMapping类型的bean 放在this.handlerMappings中,会取出右侧5个HandlerMapping bean
1. faviconHandlerMapping(SimpleUrlHandlerMapping)
2. requestMappingHandlerMapping(RequestMappingHandlerMapping) 这个Bean非常的重要,它的继承关系查看链接
RequestMappingHandlerMapping相关类图
1. SpringBoot中引入mvc模块且没有手动开启@EnableWebMvc注解(如果手动开启会Import DelegatingWebMvcConfiguration,就不满足@ConditionalOnMissin gBean(WebMvcConfigurationSupport.class)条件了,建议不要手动开启,除非在非SpringBoot环境中) 则会加载WebMvcAutoConfiguration,WebMvcAutoConfiguration中的静态内部类EnableWebMvcConfiguration(继承自DelegatingWebMvcConfiguration) 里面有定义一个requestMappingHandlerMapping bean
2. 在实例化requestMappingHandlerMapping bean的时候会执行 EnableWebMvcConfiguration#requestMappingHandlerMapping方法
1. 执行超类WebMvcConfigurationSupport#requestMappingHandlerMapping方法 return super.requestMappingHandlerMapping();
1. 执行子类EnableWebMvcConfiguration#createRequestMappingHandlerMapping方法 子类的方法中主要是判断this.mvcRegistrations是不是有一个requestMappingHandlerMapping 的对象了,有的话就就不去实例化一个 RequestMappingHandlerMapping 对象了 RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
2. 为./1中创建的mapping设置拦截器 mapping.setInterceptors(getInterceptors());
1. 实例化一个InterceptorRegistry对象 InterceptorRegistry registry = new InterceptorRegistry();
2. 调用DelegatingWebMvcConfiguration#addInterceptors方法 (在DelegatingWebMvcConfiguration配置中通过@Autowired将容器中所有的WebMvcConfigurer配置 添加到了WebMvcConfigurerComposite类型的configurers属性中,在执行addInterceptors方法的时候 实际上就是循环执行WebMvcConfigurer#addInterceptors(registry),所以我们可以通过实现WebMvcConfigurer 接口重写addInterceptor就能添加拦截器了) addInterceptors(registry);
添加拦截器示例(参考方式一)
添加拦截器
添加自定义拦截器实现原理
3. 添加内置 ConversionServiceExposingInterceptor 过滤器,在执行拦截器的preHandle方法时会为request添加一个 \"org.springframework.core.convert.ConversionService\"属性,值为mvcConversionService()返回值 registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService()));
4. 添加内置 ResourceUrlProviderExposingInterceptor 过滤器,在执行拦截器的preHandle方法时会为request添加一个 \"org.springframework.web.servlet.resource.ResourceUrlProvider\"属性,值为mvcResourceUrlProvider()返回值 registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
5. 取出上面添加到过滤器,赋值给this.interceptors进行返回最终设置到mapping#interceptors中 this.interceptors = registry.getInterceptors();
3. 在./2 实例化完后会执行该 bean的初始化操作,由于该bean实现了ApplicationContextAware接口 所以会执行ApplicationObjectSupport#setApplicationContext方法(参考\"doGetBean源码分析#4.7.1.4.5.8.2\")
doGetBean源码分析
1. 为applicationContext赋值 this.applicationContext = context;
2. 为messageSourceAccessor 赋值 this.messageSourceAccessor = new MessageSourceAccessor(context);
3. 执行子类WebApplicationObjectSupport#initApplicationContext方法 最终会执行AbstractHandlerMapping#initApplicationContext方法 initApplicationContext(context);
1. 供子类扩展的方法,这里的this.interceptors就是#1.2.4.1.2.2.1.2中添加的那些拦截器 extendInterceptors(this.interceptors);
2. 从bean容器中取出MappedInterceptor类型的bean放入添加到this.adaptedInterceptors中 所以通过直接在bean容器中注册MappedInterceptor类型bean的方式也是可以添加拦截器的 detectMappedInterceptors(this.adaptedInterceptors);
添加MappedInterceptor方式注册拦截器示例(参考方式二)
注册bean的方式添加拦截器
3. 将this.adaptedInterceptors中的那些拦截器适配后添加到this.adaptedInterceptors中 initInterceptors();
4. 在执行完./3后由于该bean又实现了InitializingBean接口,所以接着会执行 RequestMappingHandlerMapping#afterPropertiesSet(参考\"doGetBean源码分析#4.7.1.4.5.8.3\")
1. 实例化一个BuilderConfiguration对象赋值给this.config this.config = new RequestMappingInfo.BuilderConfiguration();
2. 设置urlPathHelper默认为UrlPathHelper.class this.config.setUrlPathHelper(getUrlPathHelper());
3. 默认为AntPathMatcher,路径匹配校验器 this.config.setPathMatcher(getPathMatcher());
3. 是否支持后缀补充,默认为true(比如url为/api/user.do的/api/user.html也能匹配上) this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
4. 是否添加\"/\"后缀,默认为true(比如url为/api/user的/api/user/也能匹配上) this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
5. 是否采用mediaType匹配模式,比如.json/.xml模式的匹配,默认为false this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
6. mediaType处理类:ContentNegotiationManager this.config.setContentNegotiationManager(getContentNegotiationManager());
7. 调用父类的方法 super.afterPropertiesSet();
1. 执行请求处理方法的初始化,会取出容器中所有的bean进行循环调用 processCandidateBean方法,需要注意的是只会从当前容器所有bean 默认不会取父容器的bean(可通过detectHandlerMethodsInAncestorContexts参数控制) processCandidateBean(beanName);
1. 通过bean名称取出bean的类型 beanType = obtainApplicationContext().getType(beanName);
2. 如果beanType不为null且该类有标注@Controller或@RequestMapping注解 则进行检查请求处理方法 detectHandlerMethods(beanName);
1. 如果传入的是字符串类型则说明是beanName,则通过名称取出它的类型 如果是个对象则直接取出对象的类型 Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass());
handlerType不为null
2. 解析这个类中所标注了@RequestMapping注解的自定义方法放入一个Map中 key为Method,值为通过@RequestMapping信息创建的一个RequestMappingInfo对象font color=\"#0076b3\
3. 循环上面解析出来的Map,将值注册到MappingRegistry中 handler此处为bean名称,invocableMethod为可执行的方法,mapping为RequestMappingInfo对象 font color=\"#0076b3\
registerHandlerMethod中会调用font color=\"#0076b3\
2. 判断此次添加的mapping是不是之前就有添加过对应的handlerMethod映射了 如果有了而且和这次新创建的handlerMethod不同则抛出异常 font color=\"#0076b3\
4. 添加url与mapping的映射添加到urlLookup中 font color=\"#0076b3\
比如/index这个请求url可能对应有Get方法的@RequestMapping和Post方法的@RequestMapping
5. 保存name和handlerMethod的映射关系 font color=\"#0076b3\
6. 添加mapping与MappingRegistration对象的关系font color=\"#0076b3\
3. beanNameHandlerMapping(BeanNameUrlHandlerMapping)
4. resourceHandlerMapping(SimpleUrlHandlerMapping) 这个主要是用来匹配静态资源用的,在RequestMappingHandlerMapping没有匹配到 符合要求的Handler的时候会进入到这个HandlerMapping里面进行匹配,它里面匹配出来的 是HttpRequestHandler类型的Handler,与其对应的是HttpRequestHandlerAdapter适配器
5. welcomePageHandlerMapping(WelcomePageHandlerMapping)
2. 如果this.detectAllHandlerMappings为false,表示不去检测所有的HandlerMapping 直接从bean容器中取名称为handlerMapping的bean添加到this.handlerMappings中
3. 如果上面没有取到HandlerMapping则使用默认策略提供的HandlerMapping
像在非springboot项目的普通spring mvc项目中容器中是没有相关bean的会使用策略
5. 初始化请求处理适配器 initHandlerAdapters(context);
和../4逻辑一致,只不过是取出HandlerAdapter类型的bean放在this.handlerAdapters中,包含右侧3个bean
1. RequestMappingHandlerAdapter 所有MethodHandler类型的Handler都会匹配这个适配器
1. 参考#1.2.4.1.2.1中注册requestMappingHandlerMapping bean 会注册requestMappingHandlerAdapter
2. 和参考#1.2.4.1.2.2类似,bean实例化的时候会执行 WebMvcConfigurationSupport#requestMappingHandlerAdapter方法
1. 实例化一个RequestMappingHandlerAdapter 对象,主要添加了4个消息转换器 不过在./3中会被重新赋值 RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
1. this.messageConverters.add(new ByteArrayHttpMessageConverter());
2. this.messageConverters.add(stringHttpMessageConverter);
3. this.messageConverters.add(new SourceHttpMessageConverter<>());
4. this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
2. 设置内容协商管理器 adapter.setContentNegotiationManager(mvcContentNegotiationManager());
3. 为adapter设置消息转换器 adapter.setMessageConverters(getMessageConverters());
1. 获取messageConverters getMessageConverters()
如果getMessageConverters()为null
1. this.messageConverters = new ArrayList<>();
2. 该方最终会从调用WebMvcConfigurer配置类中的configureMessageConverters方法进行自定义设置 不过需要注意的是springboot中默认注册了一个WebMvcConfigurer配置WebMvcAutoConfigurationAdapter 这个配置类中重写了configureMessageConverters方法,该方法会去bean容器中查找HttpMessageConverters类型的bean HttpMessageConvertersAutoConfiguration类中有定义这个类型的bean,在实例化这个bean的时候最终会执行./3中的方法 添加默认的MessageConverters,具体可查看HttpMessageConverters的构造 configureMessageConverters(this.messageConverters);
3. 如果this.messageConverters仍是null,则设置默认值,在./2中实例化HttpMessageConverters时会执行该方法 会加载右侧10个messageConverter addDefaultHttpMessageConverters(this.messageConverters);
ByteArrayHttpMessageConverter
StringHttpMessageConverter
ResourceHttpMessageConverter
ResourceRegionHttpMessageConverter
SourceHttpMessageConverter
AllEncompassingFormHttpMessageConverter
MappingJackson2HttpMessageConverter
Jaxb2RootElementHttpMessageConverter
4. 供用户通过WebMvcConfigurer配置自定义messageConverter的扩展接口 extendMessageConverters(this.messageConverters);
5. 返回this.messageConverters,会被设置给adapter#messageConverters retrun this.messageConverters;
2. 将上面获取的messageConverters设置给adapter#messageConverters this.messageConverters = messageConverters;
4. 为adapter设置webBindingInitializer, 在#2.3.5.4.11.4.2创建WebDataBinderFactory对象时会设置该webBindingInitializer属性 这个初始化器中设置了一些数据绑定的全局设置,在对请求数据进行绑定的时候会起作用 adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
先会去bean容器中查找是否有ConfigurableWebBindingInitializer类型的bean如果没有找到则实例化一个ConfigurableWebBindingInitializer对象(默认的实例化的时候会设置ConversionService为WebConversionService)
5. 为adapter设置用户自定义参数解析器(通过WebMvcConfigurer配置自定义) adapter.setCustomArgumentResolvers(getArgumentResolvers());
6. 为adapter设置用户自定义返回值处理器(通过WebMvcConfigurer配置自定义) adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
如果jackson2Present的值为true (在WebMvcConfigurationSupport的静态代码块中会通过判断是否有引入jackson2的包来设置此值)
7. adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
8. adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
9. spring mvc异步支持相关设置
3. 实例化完初始化的时候由于bean实现了ApplicationContextAware接口接口 所以会调用setApplicationContext方法为applicationContext赋值,这一个只是普通的赋值操作 不像requestMappingHandlerMapping做了一些其他的事情
4. 在执行完./3后由于该bean又实现了InitializingBean接口,所以接着会执行 RequestMappingHandlerAdapter#afterPropertiesSet方法
1. 初始化ControllerAdvice缓存 initControllerAdviceCache();
1. 查找bean容器中有标注@ControllerAdvice注解的bean构造成ControllerAdviceBean对象 List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
2. 定义一个List用来存放上面adviceBeans 中那些RequestBodyAdvice或ResponseBodyAdvice类型的bean List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
循环./1中 adviceBeans
3. 查找当前bean中没有标注@RequestMapping注解但标注了@ModelAttribute 注解的方法放到modelAttributeAdviceCache缓存中 font color=\"#0076b3\
4. 查找当前bean中标注了@InitBinder注解的方法添加到initBinderAdviceCache缓存中 font color=\"#0076b3\
5. 如果当前bean是RequestBodyAdvice或ResponseBodyAdvice类型则添加到./2 中的requestResponseBodyAdviceBeans
6. 如果./2中的requestResponseBodyAdviceBeans不为空 则将requestResponseBodyAdviceBeans添加到RequestMappingHandlerAdapter#requestResponseBodyAdvice列表的最前面
如果this.argumentResolvers为null,则添加默认值
2. 获取默认值
1. List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
2. 添加基于注解的默认参数解析器,共15个
3. 添加基于类型的默认参数解析器,共9个
4. 添加#1.2.5.1.2.5用户自定义的那些参数解析器 resolvers.addAll(getCustomArgumentResolvers());
5. font color=\"#0076b3\
6. resolvers.add(new ServletModelAttributeMethodProcessor(true)); ServletModelAttributeMethodProcessor这个解析器在./2中添加过了,不过构造参数是false 此处又重新添加了个构造为true的,主要是为annotationNotRequired属性赋值为true,这样那些我们自定义的实体类最终也能被这个解析器处理 这个解析器放在最后面,在上面的那些解析器都解析不了的时候做兜底用的
7. return resolvers;
3. this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
如果this.initBinderArgumentResolvers为null,则添加默认值
4. 获取默认值
2. 添加基于注解的默认参数解析器,共9个
3. 添加基于类型的默认参数解析器
resolvers.add(new ServletRequestMethodArgumentResolver());\tresolvers.add(new ServletResponseMethodArgumentResolver());
4. 添加#1.2.5.1.2.5用户自定义的那些参数解析器 resolvers.addAll(getCustomArgumentResolvers());
6. return resolvers;
5. this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
如果this.returnValueHandlers为null,则添加默认值
6. 获取默认值
1. List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();
2. 单个返回值处理相关,共10个
ModelAndViewMethodReturnValueHandler
ModelMethodProcessor
ViewMethodReturnValueHandler
ResponseBodyEmitterReturnValueHandler
StreamingResponseBodyReturnValueHandler
HttpEntityMethodProcessor
HttpHeadersReturnValueHandler
CallableMethodReturnValueHandler
DeferredResultMethodReturnValueHandler
AsyncTaskMethodReturnValueHandler
3. 基于注解的返回值处理相关,共2个
ModelAttributeMethodProcessor
RequestResponseBodyMethodProcessor
4. 多个返回值处理相关,共2个
ViewNameMethodReturnValueHandler
MapMethodProcessor
5. 添加#1.2.5.1.2.6中用户自定义的那些处理器 handlers.addAll(getCustomReturnValueHandlers());
6. 如果this.modelAndViewResolvers为空 handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
7. 如果this.modelAndViewResolvers不为空 handlers.add(new ModelAttributeMethodProcessor(true));
8. return handlers;
7. this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
2. HttpRequestHandlerAdapter 所有HttpRequestHandler类型的Handler会匹配该处理器 比如resourceHandlerMapping中匹配出的Handler
3. SimpleControllerHandlerAdapter
6. 初始化异常处理 initHandlerExceptionResolvers(context);
和./4逻辑一致,只不过是取出HandlerExceptionResolver类型的bean放在this.handlerExceptionResolvers中,包含右侧2个bean
DefaultErrorAttributes
HandlerExceptionResolverComposite
7. 初始化请求到ViewName的转译,通过ViewName可来找到对应的视图 initRequestToViewNameTranslator(context);
1. 从bean容器中去查找RequestToViewNameTranslator类型的bean赋值给this.viewNameTranslator
2. 上面取不到抛出异常会使用默认策略,默认会使用DefaultRequestToViewNameTranslator
8. 初始化视图解析 initViewResolvers(context);
和./4逻辑一致,只不过是取出ViewResolver类型的bean放在this.viewResolvers中,包含右侧4个bean
BeanNameViewResolver
ViewResolverComposite
InternalResourceViewResolver
ContentNegotiatingViewResolver
9. 初始化FlashMap管理,FlashMap主要用在redirect中传递参数 initFlashMapManager(context);
1. 从bean容器中去查找FlashMapManager类型的bean赋值给flashMapManager
2. 上面取不到抛出异常会使用默认策略,默认会使用SessionFlashMapManager
2. 容器初始化后对请求进行处理
1. 请求会进入ApplicationFilterChain#doFilter先执行过滤器,然后才会执行DispatcherServlet#service方法
ApplicationFilterChain模拟代码示例
ApplicationFilterChain模拟
2. 调用DispatcherServlet#service会进入FrameworkServlet#service方法
1. 从request中获取请求的方法类型,然后通过方法类型获取对应的HttpMethod枚举值 HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
2. 如果上一步取到的httpMethod是PATCH值或为null,则直接调用 FrameworkServlet#processRequest方法进行处理了 这一步主要是处理了Servlet不支持PATCH请求类型的情况
3. 经过./2后请求最终都会进入FrameworkServlet#processRequest方法
1. 先从线程上下文localeContextHolder中取LocaleContext(主要是供finally中重置线程上下文使用) 然后通过request.getLocale()构建一个SimpleLocaleContext类型的LocaleContext对象 LocaleContext localeContext = buildLocaleContext(request);
2. 先从线程上下文localeContextHolder中取RequestAttributes (主要是供finally中重置线程上下文使用) 然后通过request和response构建一个ServletRequestAttributes对象(如原先线程上下文中的RequestAttributes不是ServletRequestAttributes类型则返回null) font color=\"#0076b3\
3. 异步管理器相关的东西(后续再分析,先占个位)
4. 将./1和./2中构建出的对象放入到各自的线程上下文中 font color=\"#0076b3\
5. 执行DispatcherServlet#doService方法 font color=\"#0076b3\
1. 如果是request属性中包含“javax.servlet.include.request_uri”属性(即<jsp:incluede page=\"xxx.jsp\"/>的情况) 则将request中的“org.springframework.web.servlet”开头的属性存在一个Map快照中 attributesSnapshot 在finally块中如果发现request中的属性值和快照中的属性值不同会重新设置回快照中的属性值 font color=\"#0076b3\
2. 设置一些常用的属性到request的属性中方便后面获取,主要包括
CONTEXT
LOCALE_RESOLVER
THEME_RESOLVER
THEME_SOURCE
3. 如果是重定向请求,还会额外设置一些重定向相关的属性到request
INPUT_FLASH_MAP
OUTPUT_FLASH_MAP
FLASH_MAP_MANAGER
1. HttpServletRequest processedRequest = request;
2. HandlerExecutionChain mappedHandler = null;
3. 取出异步管理器 \t\tWebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
4. ModelAndView mv = null;
5. 检查是不是文件上传,如果是的话会通过#1.2.1中初始化的StandardServletMultipartResolver#resolveMultipart方法 将request包装为一个StandardMultipartHttpServletRequest对象赋值给processedRequest 这里需要注意的是由于对processedRequest 重新赋值了,所以后续方法中无法再从RequestContextHolder中获取之前设置的属性 processedRequest = checkMultipart(request);
6. 获取当前请求的处理程序执行链HandlerExecutionChain mappedHandler = getHandler(processedRequest);
此方法中会循环#1.2.4.1中初始化的那个几个HandlerMapping如果某个HandlerMapping#getHandler方法有取到值则直接return这里我们只分析最常用的RequestMappingHandlerMapping需要注意的是在RequestMappingHandlerMapping没有匹配到Handler的时候会继续执行后面的HandlerMapping,在执行到resourceHandlerMapping的时候一般是能匹配到的因为它里面有个一条匹配规则是/**,在./8中会匹配到HttpRequestHandlerAdapter适配器进行处理
1. 通过请求获取匹配的处理器 Object handler = getHandlerInternal(request);
1. 取出request中的uri地址 String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
2. 通过lookupPath去查找最终执行的处理器 font color=\"#0076b3\
1. List<Match> matches = new ArrayList<>();
2. 从缓存中取出该lookupPath对应的RequestMappingInfo对象(#1.2.4.1.2.4.7.1.2.3.4有添加所有url和RequestMappingInfo的映射) 由于一个uri可能对应有多个请求类型的(如:Get,Post)@RequestMapping 所以返回的是一个RequestMappingInfo类型的List List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
3. 如果./2获取的directPathMatches不为null,则进一步通过RequestMappingInfo 中的method、headers、consumes这些值 从directPathMatches中过滤出匹配的RequestMappingInfo放入到matches中 font color=\"#0076b3\
4. 如果此时matches仍旧为空,则尝试通过request去匹配缓存中所有的RequestMappingInfo,看能不能找到匹配的 font color=\"#0076b3\
如果到这一步matches不为空
5. 先按method,patterns,params等优先级对matches进行排序处理
6. 取出第一个matches中的第一个,也就是最佳匹配 Match bestMatch = matches.get(0);
7. 如果matches中的个数大于1的话把matches中的第二个也就是次最佳匹配也取出来 如果bestMatch和secondBestMatch用比较器比较是相等则抛出IllegalStateException异常 Match secondBestMatch = matches.get(1);
8. 走到这一步说明没啥问题则将最佳匹配的HandlerMetod放入request的\"HandlerMapping.class.getName() + '.bestMatchingHandler'\"属性中 font color=\"#0076b3\
9. 接着又执行了如下方法,在request中设置了一些其他属性font color=\"#0076b3\
10. 最终返回HandlerMethod return bestMatch.handlerMethod;
11. matches为空,则调用handleNoMatch方法进行处理,这是一个可供子类重写的方法 RequestMappingInfoHandlerMapping有重写该方法进行部分匹配,如果还是没有匹配到则返回null font color=\"#0076b3\
3. 如果取到的handlerMethod不为null,则判断handlerMethod中的bean属性是不是String类型 如果是的话通过该值作为bean名称去容器中查找对应的bean对象赋值给该bean属性 return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
2. 如果./1没有取到就去取默认AbstractHandlerMapping#defaultHandler值 handler = getDefaultHandler();
3. 如果到这一步还没取到值就return null了
4. 如果取到的handler值为String类型,则将该handler值作为bean名称去容器中获取对应的bean赋值给handler (不明白什么时候取出的是一个String,一般是一个HandlerMethod对象)
5. 将handler包装成一个HandlerExecutionChain对象,里面会包含匹配的拦截器font color=\"#0076b3\
1. 如果handler本来就是HandlerExecutionChain类型则直接强制转换就好了 如果handler不是则通过handler构建一个HandlerExecutionChain HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
2. 为上面的chain对象添加匹配的匹配的拦截器(在#1.2.4.1.2.3.3中有将过滤器添加到AbstractHandlerMapping#adaptedInterceptors中) 需要注意的是MappedInterceptor类型的拦截器只有和当前请求匹配才会添加到HandlerExecutionChain#interceptorList中 而非MappedInterceptor类型的会直接添加到HandlerExecutionChain#interceptorList中
在#1.2.4.1.2.2.1.2的时候有添加两个默认的非MappedInterceptor类型的拦截器
ConversionServiceExposingInterceptor
ResourceUrlProviderExposingInterceptor
3. return chain;
6. 对CORS跨域资源共享的支持
7. return executionChain;
7. 如果mappedHandler为null,则判断this.throwExceptionIfNoHandlerFound是否为true 为true的话抛出NoHandlerFoundException异常,否则设置response状态为404然后return请求font color=\"#0076b3\
8. 获取匹配的适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
会循环#1.2.5中初始化的那几个适配器,如果mappedHandler.getHandler() 中取出的处理器是HandlerMethod类型则会交给 RequestMappingHandlerAdapter 处理
9. 如果是Get请求,且请求的内容没有变化,则直接返回
10. 执行拦截器的前置方法font color=\"#0076b3\
1. 将../6.5.2中添加到HandlerExecutionChain#interceptorList中的那些拦截器 转为数组放入HandlerExecutionChain#interceptors中进行返回 HandlerInterceptor[] interceptors = getInterceptors();
如果上面的interceptors不为空则循环这些拦截器此处需要注意的是有两个默认的拦截器会被执行ConversionServiceExposingInterceptor和ResourceUrlProviderExposingInterceptor他们会为request设置两个属性,详情查看#1.2.4.1.2.2.1.2
2. 如果当前拦截器返回的preHandle方法返回false则先在循环执行它前面那些拦截器的 afterCompletion方法后会return false整个方法
3. 如果preHandle返回true则执行下一个拦截器的preHandle
11. 执行适配器的handle方法(此处只分析最常用的RequestMappingHandlerAdapter) font color=\"#0076b3\
1. ModelAndView mav;
2. 对请求进行检查,如检查是否是自己支持的请求方法类型 checkRequest(request);
3. 判断同一个session是否需要串行执行(this.synchronizeOnSession为true) 需要的话会取出session,如果session不为null会对它加同步锁然后再调用invokeHandlerMethod方法
4. 如果同一个session不需要串行则直接执行invokeHandlerMethodfont color=\"#0076b3\
1. font color=\"#0076b3\
2. 处理@InitBinder注解的方法 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
1. 获取处理处理器的bean类型 Class<?> handlerType = handlerMethod.getBeanType();
2. 先尝试通过handlerType从缓存中获取@InitBinder标注的方法 Set<Method> methods = this.initBinderCache.get(handlerType);
如果缓存中没有就去解析
3. 解析handlerType类中被@InitBinder标注的方法 font color=\"#0076b3\
4. 存入缓存中下次进来就直接从缓存中获取 font color=\"#0076b3\
5. List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
6. 循环#1.2.5.1.4.1.4中那些有@InitBinder方法的ControllerAdviceBean
如果handlerType满足ControllerAdviceBean的应用条件(一般没有特别设置是应用到所有类型的) 则将当前ControllerAdviceBean中标注有@InitBinder的那些方法包装成InvocableHandlerMethod对象添加到initBinderMethods中
7. ./3 中解析出的那些方法也包装成InvocableHandlerMethod对象后添加到initBinderMethods中
8. 最后通过initBinderMethods构造一个ServletRequestDataBinderFactory对象 return createDataBinderFactory(initBinderMethods);
3. 处理@ModelAttribute标注的方法,需要注意的先添加的是ControllerAdviceBean中那些@ModelAttribute方法 所以ControllerAdviceBean中的@InitBinder方法优先及更高,在./11.3中可以看出属性名相同时后面的属性是不会再 添加到mavContainer中的 font color=\"#0076b3\
1. 通过handlerMethod获取SessionAttributesHandler,如果该处理器类有标注SessionAttributes注解 会解析取出注解中names属性的值放入SessionAttributesHandler#knownAttributeNames中 knownAttributeNames中包含的属性名称对应的属性会被缓存到session中 SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
2. 接下来的那些操作和../2 中的处理基本相同,只是由@InitBinder注解换成了@ModelAttribute注解
3. 最后构建一个 ModelFactory对象 font color=\"#0076b3\
4. 将handlerMethod包装成ServletInvocableHandlerMethod ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
5. 为invocableMethod设置参数解析器(#1.2.5.1.4.2有设置) invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
6. 为invocableMethod设置返回值处理器(#1.2.5.1.4.6有设置) invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
7. 设置数据绑定工厂为./2中创建的对象 invocableMethod.setDataBinderFactory(binderFactory);
8. 设置用于获取参数名的对象 invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
9. 实例化一个模型视图容器 ModelAndViewContainer mavContainer = new ModelAndViewContainer();
10. FlashMap数据保存到Model中 mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
11. 初始化./3中的modelFactory对象 font color=\"#0076b3\
1. 从获取session中获取knownAttributeNames中包含的那些属性(../3.1中有设置) font color=\"#0076b3\
2. 如果sessionAttributes中的属性在container中不存在则添加到 container中 container即上一步传入的mavContainer,它里面有个ModelMap defaultModel属性会保存添加的属性 container.mergeAttributes(sessionAttributes);
3. 循环执行那些@ModelAttribute方法,会将方法的返回值作为属性添加到container中 font color=\"#0076b3\
4. 这一步会去handlerMethod 中找出那些入参上标注了ModelAttribute注解的参数 如果该参数在SessionAttributes注解的配置中(attributeNames或attributeTypes匹配) 会添加到knownAttributeNames中,添加到knownAttributeNames后还会判断该属性是否存在container中 不存在的话会冲session中取出该属性值添加到container中,如果从session中取不到值则会抛出HttpSessionRequiredException异常
12. 异步相关的一些东西,暂时不管
13. 调用处理器的方法 font color=\"#0076b3\
1. 调用父类InvocableHandlerMethod#invokeForRequest方法执行处理器方法的调用font color=\"#0076b3\
1. 获取处理器方法的实参数组 font color=\"#0076b3\
1. 如果处理器方法参数为空,则返回一个空的对象数组作为实参
2. MethodParameter[] parameters = getMethodParameters();
3. 定义一个args对象数组,用于存储转化后的实参列表 Object[] args = new Object[parameters.length]
循环 parameters
4. MethodParameter parameter = parameters[i];
5. parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
6. 如果可变参数providedArgs中包含当前形参parameter类型的对象,则使用该对象作为实参,继续循环处理下一个形参font color=\"#0076b3\
7. 判断this.resolvers(#2.3.5.4.11.4.5中设置的HandlerMethodArgumentResolverComposite对象)中是否有支持当前形参parameter的参数解析器 没有则抛出IllegalStateException异常 this.resolvers.supportsParameter(parameter)
8. 通过HandlerMethodArgumentResolverComposite#resolveArgument进行解析当前形参parameter font color=\"#0076b3\
1. 循环HandlerMethodArgumentResolverComposite#argumentResolvers中的那些HandlerMethodArgumentResolver 通过HandlerMethodArgumentResolver#supportsParameter方法筛选出匹配的HandlerMethodArgumentResolver HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
2. ./1中没找匹配的resolver则抛出IllegalArgumentException异常
3. 调用匹配的那个HandlerMethodArgumentResolver#resolveArgument方法,将返回值作为实参数进行返回 右侧分析最比较常用的方法解析器font color=\"#0076b3\
1. ServletModelAttributeMethodProcessor 在#1.2.5.1.4.2.3的时候有添加一个该类型的默认的参数处理器 构造时传入了true,表示处理那些没显式标注ModelAttribute注解的自定义类参数
1. 获取参数的名称,如果当前要处理的形参有标注ModelAttribute注解则去注解的value值作为name 否则通过 Conventions#getVariableNameForParameter方法获取参数名称,比如User 为user String name = ModelFactory.getNameForParameter(parameter);
2. 如果当前要处理的形参有标注ModelAttribute,则设置mavContainer中该name属性的绑定状态为绑定中 font color=\"#0076b3\
3. Object attribute = null;
4. BindingResult bindingResult = null;
5. 如果mavContainer中包含该name的属性,则设置attribute 为该name的属性值 attribute = mavContainer.getModel().get(name);
6. 如果mavContainer中不包含该name的属性,则会通过反射实例化该参数类型的对象
7. 通过binderFactory#createBinder方法创建一个WebDataBinder 对象(#2.3.5.4.11.4.7有设置binderFactory) font color=\"#0076b3\
1. 创建一个WebDataBinder实例 font color=\"#0076b3\
2. 将初始化器中的一些全局配置设置到dataBinder中,比如设置conversionService(初始化器ConfigurableWebBindingInitializer的初始化查看#1.2.5.1.2.4) 以及设置validator 为LocalValidatorFactoryBean(在../9中数据校验的时候会用到这个校验器) font color=\"#0076b3\
3. 初始化dataBinder font color=\"#0076b3\
循环执行binderMethods中的那些方法也就是在#2.3.5.4.11.4.2中收集的那些带@InitBinder注解的方法
1. 判断dataBinder中要处理的那个参数名称是否符合@InitBinder处理条件 @InitBinder中没有配置value表示处理任何名称的参数 font color=\"#0076b3\
2. 调标注@InitBinder标注的方法,可以往dataBinder中注册一些属性编辑器,用来做些转化操作,比如CustomDateEditor font color=\"#0076b3\
3. 判断./2中是否有返回值,有返回值抛出异常,所以@InitBinder标注的方法是不允许有返回值的
4. 返回dataBinder return dataBinder;
8. 将请求中的参数绑定到attribute font color=\"#0076b3\
1. 将NativeWebRequest转为ServletRequest类型 ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
2. 将binder强转为ServletRequestDataBinder类型 ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
3. 执行绑定操作 servletBinder.bind(servletRequest);
1. 将请求中的参数放入到MutablePropertyValues 对象中 MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
2. 从请求中取出文件上传MultipartRequest数据,如果不为空则添加到./1 mpvs中
3. 这一步主要是从uri中取出占位符中的参数添加的 mpvs中(如xxx/xxx/{id}这种形式) font color=\"#0076b3\
4. 将上面几步添加到mpvs中的数据绑定到目标对象中 doBind(mpvs);
1. 检查mpvs中是否包含默认参数,即参数名以!开头,比如!age。如果mpvs中没有参数名为age参数 则会将!age去除!添加到mpvs中作为age参数 checkFieldDefaults(mpvs);
2. 和./1中有些类似,会判断是否有参数名以_开头,做一些空值设置 checkFieldMarkers(mpvs);
3. 调用父类DataBinder绑定方法 super.doBind(mpvs);
1. 校验是否有不被允许的字段(可以通过给allowedFields设置来添加自己的允许字段) checkAllowedFields(mpvs);
2. 校验必填字段是否有为空的 checkRequiredFields(mpvs);
3. applyPropertyValues(mpvs);
1. 获取属性访问器即通过 (其实前面很多地方可能已经调用过这个方法进行获取这个访问器了,统一在这个地方分析) getPropertyAccessor()
1. 即获取bindingResult属性,第一次获取时会初始化bindingResult值 getInternalBindingResult()
1. 实例化一个BeanPropertyBindingResult 对象 font color=\"#0076b3\
2. 如果this.conversionService不为空,则设置到result中 result.initConversion(this.conversionService);
3. 如果this.messageCodesResolver不为空,则设置到result中 result.setMessageCodesResolver(this.messageCodesResolver);
4. return result;
2. 通过上一步的bindingResult获取属性访问器,其实就是通过 工厂类PropertyAccessorFactory创建一个BeanWrapper对象 getPropertyAccessor()
1. 通过工厂类PropertyAccessorFactory创建BeanWrapper return PropertyAccessorFactory.forBeanPropertyAccess(this.target);
2. 通过上面的属性访问器进行设置操作 font color=\"#0076b3\
1. 取出PropertyValue列表 \tList<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ? ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
2. 循序propertyValues将PropertyValue设置到目标对象中 setPropertyValue(pv);
最终在设值时涉及到的数据转换工作是会调用委托类TypeConverterDelegate#convertIfNecessary方法会优先查找是否有合适的PropertyEditor,没有的话再ConversionService列表中查找是否有合适的。如果还是没有则判断设置的值类型进行默认的转换操作
ConversionService类关系图
一些数据绑定示例
9. 数据校验,验证绑定到attribute中的数据是否都是合法的,比如attribute对象中通过@NotNull声明某个字段是非空的 而绑定后该字段仍为null,则会抛出BindException异常(注意当前参数的后一个参数为Errors类型时不会抛出异常,详情查看./12) font color=\"#0076b3\
10. 处理attribute对象不是当前参数类型的情况
11. 取出bindingResult bindingResult = binder.getBindingResult();
12. 从bindingResult取出绑定后的属性添加到map中供后面添加到mavContainer中 此处需要注意的是除了添加绑定后的目标对象,还将bindResulte对象本身也作为一个属性添加到了map中 主要是用来处理后./9中数据不合法且后一个参数为Errors类型的情况,ErrorsMethodArgumentResolver参数处理器会将bindResulte对象设置到Errors参数中 font color=\"#0076b3\
13. 先移除mavContainer中存在bindingResultModel中的那些属性 mavContainer.removeAttributes(bindingResultModel);
14. 添加bindingResultModel中的属性到mavContainer中 mavContainer.addAllAttributes(bindingResultModel);
15. return attribute;
2. RequestResponseBodyMethodProcessor 从处理器的supportsParameter方法可以看出该处理器 能处理标注了@RequestBody注解的参数
1. 处理参数是Optional的情况 parameter = parameter.nestedIfOptional();
2. 通过消息转换器将请求体中的数据转换为形参类型的实参对象 font color=\"#0076b3\
1. HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
2. ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
3. font color=\"#0076b3\
1. 取出contentType contentType = inputMessage.getHeaders().getContentType();
2. 如果contentType为null,则设置为application/octet-stream contentType = MediaType.APPLICATION_OCTET_STREAM;
3. 定义一个对象用来存储请求body转换后的对象 Object body = NO_VALUE;
4. 将inputMessage包装为EmptyBodyCheckingHttpInputMessage对象 内部会通过回退流PushbackInputStream判断body是不是空的 message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
循环查找匹配的HttpMessageConverter(#1.2.5.1.2.3.1.3由设置)
5. 取出当前转换器的类型 Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
6. 将当前转换器强转为GenericHttpMessageConverter类型,如果不是该类型则赋值为null GenericHttpMessageConverter<?> genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
7. 通过目标参数类型和contentType等信息调用当前转换器的canRead方法判断当前转换器是否匹配
如果message.hasBody()有内容
8. 循环执行RequestBodyAdvice#supports返还true的RequestBodyAdvice#beforeBodyRead方法 (#1.2.5.1.4.2.2中在实例化RequestResponseBodyMethodProcessor的时候有将那些标注了 @ControllerAdvice且实现了RequestBodyAdvice或ResponseBodyAdvice接口的bean传进来) font color=\"#0076b3\
springboot中默认添加了JsonViewRequestBodyAdvice在beforeBodyRead中会处理那些标注了JsonView注解的参数将inputMessage包装成MappingJacksonInputMessage对象在../9.1中会判断inputMessage是否为MappingJacksonInputMessage类型来对JsonView进行处理
JsonView使用示例
9. 执行反序列化操作 font color=\"#0076b3\
1. 判断inputMessage是否为MappingJacksonInputMessage,是的话处理JsonView
2. 通过objectMapper执行反序列化操作
10. 循环执行RequestBodyAdvice#afterBodyRead方法 font color=\"#0076b3\
如果message.hasBody()没有内容
11. 循环执行RequestBodyAdvice#handleEmptyBody方法 font color=\"#0076b3\
跳出循环
如果此处./3中定义的body仍未 NO_VALUE
12. 如果符合右侧中的某种情况则 retrun null
httpMethod 为 null
contentType为null 且请求体中没有数据
13. 不存在./12中的情况则抛出HttpMediaTypeNotSupportedException
14. return body;
4. 如果转换出来的arg为null且@RequestBody的required值为true则抛出HttpMessageNotReadableException
5. return arg;
3. 形参类型的首字母小写,并不是取形参名称 String name = Conventions.getVariableNameForParameter(parameter);
4. 和../1.7逻辑一样 font color=\"#0076b3\
5. 4. 和../1.9一样会检验转换对象的合法性,只不过这里是抛出MethodArgumentNotValidException异常 font color=\"#0076b3\
6. 和../1.12有些类似,不过只会将bindingResult放入mavContainer中供有校验错误且下一个参数为Errors类型时ErrorsMethodArgumentResolver处理器设值处理 font color=\"#0076b3\
7. 处理参数是Optional的情况 font color=\"#0076b3\
9. 最终返回实参数组args return args;
2. 通过上一步得到的实参数组进行反射调用目标方法 return doInvoke(args);
2. 如果处理器方法有@ResponseStatus注解,则为response设置status setResponseStatus(webRequest);
3. 如果./1 中返回值为null,且满足右侧任一条件,则设置mavContainer请求处理状态为已处理 直接return方法,不进行下面的处理 mavContainer.setRequestHandled(true);
1. Request的NotModified为true
2. 有@ResponseStatus注解标注
3. mavContainer.isRequestHandled()已经为true了
4. 如果./1中返回值不为null,但@ResponseStatus存在reason值则也设置mavContainer请求处理状态为已处理,并返回方法
5. 设置mavContainer请求处理状态为未处理 mavContainer.setRequestHandled(false);
6. 通过../6中设置的returnValueHandlers.handleReturnValue对返回值进行处理 font color=\"#0076b3\
1. 通过HandlerMethodReturnValueHandler #supportsReturnType方法找出匹配的HandlerMethodReturnValueHandler font color=\"#0076b3\
2. 如果没有找到匹配的handler则抛出IllegalArgumentException异常
3. 调用匹配的HandlerMethodReturnValueHandler#handleReturnValue方法对返回值进行处理 font color=\"#0076b3\
14. 获取模型和视图 font color=\"#0076b3\
1. 更新Model font color=\"#0076b3\
1. 取出mavContainer中的ModelMap ModelMap defaultModel = container.getDefaultModel();
2. container中的session状态为complete(即调用了SessionStatus.setComplete()) 如果是则则清除@SessionAttributes中指定缓存在session中的那些属性 this.sessionAttributesHandler.cleanupAttributes(request);
3. 如果container中的session状态不为complete则将defaultModel中被@SessionAttributes注解指定需要 缓存到session中的属性存储到session中 font color=\"#0076b3\
2. 如果mavContainer中的请求处理状态为true,则return null,不进行下面的创建ModelAndView操作 像标注有@ResponseBody注解的Controller或方法在../13.6.3中就会将mavContainer的请求处理状态为true mavContainer.isRequestHandled()
3. 创建ModelAndView
5. 处理没有设置Cache-Control请求头的情况
6. return mav;
12. 异步处理相关
13. 如果mv不为null且!mv.hasView(),则设置默认的视图名称 font color=\"#0076b3\
14. 调用拦截器器的postHandle方法 font color=\"#0076b3\
15. 处理转发结果 font color=\"#0076b3\
1. 如果上面的步骤方法异常,会进入此处的异常处理逻辑
2. mv != null 并且 !mv.wasCleared() 会进入视图渲染 (像标注有@ResponseBody的控制器或方法就直接跳过这一步了) font color=\"#0076b3\
1. 通过localeResolver解析local,并设置到response中
2. 从mv中取出viewName String viewName = mv.getViewName();
3. 如果./2中取出的viewName不为空,则解析该viewName,主要是将字符串的名称解析成一个视图(会通过配置的prefix,suffix等值去查找jsp) 如果解析出的view是null则会抛出ServletException异常 font color=\"#0076b3\
4. 如果./2viewName为null,则直接从mv中去取视图,如果直接去取也没取到则抛出ServletException异常 view = mv.getView();
6. 对视图进行渲染 font color=\"#0076b3\
3. 异步处理相关
4. 执行拦截器的afterCompletion方法,注意能执行到此处说明没有异常的情况,所以异常参数传null font color=\"#0076b3\
16. 如果./15中发生的是Exception 异常,执行拦截器的afterCompletion方法 font color=\"#0076b3\
17. 如果./15中发生的是Throwable 异常,也需要执行拦截器的afterCompletion方法font color=\"#0076b3\
18. 异步调用相关
19. 如果processedRequest != request,说明是文件上传,则清除文件上传使用的资源 cleanupMultipart(processedRequest);
5. ./1中有存储快照,则将快照中的属性内容重新存储到request属性中
6. 重置线程上下文
7. 发布请求处理事件ServletRequestHandledEvent font color=\"#0076b3\
0 条评论
回复 删除
下一页