1.视图解析之ViewResolver
ViewResolver :只是将String类型的视图名|逻辑视图和locale解析为View类型的视图。
视图View:是用来渲染页面的,通俗点讲就是将程序返回的参数填充到模板里面,生成HTML等文件中。
- 使用哪个模板。
- 使用何种技术(或者规则)填充参数。
这其实就是ViewResolver主要要做的工作。ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图类型)进行渲染,具体的渲染过程则交给不同的视图自己完成。我们最常用的UrlBaseViewResolver系列的解析器都是针对单一视图类型进行解析的,只需要找到使用的模版就可以了,比如InternalResourceViewResolver只是针对JSP类型的视图,FreeMarkerViewResolver只是针对FreeMarker,VelocityViewResolver只针对Velocity。而ResourceBundleViewResolver、XmlViewResolver、BeanNameViewResolver等解析器可以同时解析多种类型的视图。如前面说过的ResourceBundleViewResolver,它是根据properties配置文件解析的,配置文件里就需要同时配置class和url两项内容,比如:
hello.(class)=org.springframework.Web.servlet.view.InternalResourceView
hello.url=/WEB-INF/go.jsp
这样就将hello配置到/WEB-INF/go.jsp模板的jsp类型的视图了,当Controller返回hello时会使用这个视图来进行渲染。这里的jsp类型并不是根据go.jsp的后缀来确定的,这个后缀只是个文件名,可以把它改成任意的格式,视图类型是根据.class的配置项来确定的。
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
...
mappedHandler.triggerAfterCompletion(request, response, null);
视图解析:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
...
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
// 解析视图
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
...
}else {
view = mv.getView();
}
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// 利用ModelAndView 中数据渲染视图
view.render(mv.getModelInternal(), request, response);
...
}
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
// 视图解析器 ViewResolver 通过视图名解析视图,返回视图实例View
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
1.1.初始化变量
注解方法:RequestMappingHandlerMappering AbstractHandlerMethodMapping
AbstractHandlerMethodMapping实现了InitializingBean接口,Spring容器设置初始值后会调用afterPropertiesSet()方法,相当于初始化。
初始化过程:
protected void initHandlerMethods() {
//从容器中获取实例ID
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));
//遍历此时容器中所有的Bean ID
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
beanType = obtainApplicationContext().getType(beanName);
if (beanType != null && isHandler(beanType)) {
**detectHandlerMethods**(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected void detectHandlerMethods(final Object handler) {
//获取到实例
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
final Class<?> userType = ClassUtils.getUserClass(handlerType);
//解析方法、类找对对应的注解,从而拿到类以及方法上对应的路径,最后封装在Map中
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
return getMappingForMethod(method, userType);
});
for (Map.Entry<Method, T> entry : methods.entrySet()) {
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
//最后将所有相关的信息封装在MappingRegistry对象中
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
}
视图和视图解析器:请求处理方法执行完成之后,最终返回一个ModelAndView对象。对于那些返回String、View以及ModelMap等类型的处理方法,SpringMVC也会在内部将他们封装成一个ModelAndView对象,它包含了逻辑名和模型对象的视图。
Spring MVC借助视图解析器ViewResolver得到最终的视图解析View,最终视图可以是JSP,也可能是Excel、JFreeChart等各种表现形式的视图。
对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器工作的重点聚焦在生产模型数据的工作上,从而实现MVC的充分解耦。
视图作是 渲染模型数据将模型数据以某 种形式呈现给客户。
为了实现视图模型和具体实现技术 耦 Spring 在org.springframework.web.servlet 包中定义了一个度抽 View接口。
2.theme主题
3.FlashMapManager
FlashMap相关的内容在前面已经介绍过了,主要用在redirect中传递参数。而FlashMapManager是用来管理FlashMap的,定义如下:
public interface FlashMapManager {
FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
}
retrieveAndUpdate方法用于恢复参数,并将恢复过的和超市的参数从保存介质中删除;saveOutputFlashMap用于将参数保存起来。默认实现是SessionFlashMapManager,它将参数保存到session中。
整个redirect的参数通过FlashMap传递的过程分三步:
- 在处理器中将需要传递的参数设置到outputFlashMap中,可以直接使用
request.getAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE);
拿到outputFlashMap,然后将参数put进去,也可以将需要参数设置到处理器的RedirectAttribute类型的参数中,当处理器处理完请求时,如果是redirect类型的返回值RequestMappingHandlerAdapter会将其设置到outputFlashMap中。 - 在RedirectView的renderMergedOutputModel方法中调用FlashMapManager的saveOutputFlashMap方法,将outputFlashMap中的参数设置到session中。
- 请求redirect后DiapatchServlet的doservice会调用FlashMapManager的redirectAndUpdate方法从session中获取inputFlashMap并设置到Request的属性中备用,同时从session中删除。
4.拦截器HandlerInterceptor
拦截器以实现WebMvcConfigurationSupport接口为例「不推荐使用」。
根据SpringBoot启动原理WebMvcAutoConfiguration#requestMappingHandlerMapping
方法返回的RequestMappingHandlerMapping实例必定会通过反射被调用。
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
// Must be @Primary for MvcUriComponentsBuilder to work
return super.requestMappingHandlerMapping();
}
}
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
//此处就会回调自定义拦截器addInterceptors方法,最终被 WebMvcConfigurationSupport#interceptors持有
//以及AbstractHandlerMapping#interceptors属性持有。
mapping.setInterceptors(getInterceptors());
mapping.setContentNegotiationManager(mvcContentNegotiationManager());
mapping.setCorsConfigurations(getCorsConfigurations());
...
return mapping;
}
执行aware#setApplicationContext:
public final void setApplicationContext(@Nullable ApplicationContext context) throws BeansException {
...
// 最终执行 AbstractHandlerMapping#initInterceptors方法
initApplicationContext(context);
}
protected void initInterceptors() {
if (!this.interceptors.isEmpty()) {
for (int i = 0; i < this.interceptors.size(); i++) {
Object interceptor = this.interceptors.get(i);
if (interceptor == null) {
throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
}
// 最终的拦截器由 AbstractHandlerMapping属性adaptedInterceptors持有。
// 在 DispatcherServlet处理目标handler过程中初始化HandlerExecutionChain 时会通过 AbstractHandlerMapping自己持有拦截器,并由此分别调用拦截器的 preHandle、postHandle、afterCompletion等方法
this.adaptedInterceptors.add(adaptInterceptor(interceptor));
}
}
}