Spring Boot之源码分析篇-SpringMVC视图渲染过程
从不剑走偏锋,只会贱走源码
spring-boot-version:2.2.0.RELEASE
视图渲染过程:从前端到后台
SpringMVC流程架构图:
按照一般思考的流程,这里直接从DispatcherServlet前端控制器的方法render()开始分析。
DispatcherServlet入口
- init():DispatcherServlet 继承FrameworkServlet;而FrameworkServlet继承HttpServletBean,并实现 了ApplicationContextAware接口,HttpServletBean实现init()方法,加载web.xml中DispatcherServlet配置,调用FrameworkServlet # initServletBean()。
- initServletBean():FrameworkServlet # initServletBean(),调用FrameworkServlet#initWebApplicationContext()。
- initWebApplicationContext():FrameworkServlet#initWebApplicationContext(),初始化WebApplicationContext 容器(IOC容器);调用FrameworkServlet#createWebApplicationContext(WebApplicationContext)
- createWebApplicationContext(WebApplicationContext):获取或生成容器,调用DispatcherServlet#onRefresh(ApplicationContext)。[^1]
- onRefresh(ApplicationContext): DispatcherServlet 的onRefresh() 方法,用于在ApplicationContext刷新后进行策略组件的初始化。
- initStrategies(ApplicationContext):DispatcherServlet 的initStrategies(ApplicationContext):初始化9个策略组件。
- doService()::设置request的相关属性
- doDispatch():前端控制器DispatcherServlet在doDispatch( )方法中去获取HandlerMapping和HandlerAdapter。
- processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException):异常分析,存在ModelAndView后调用render方法进行渲染,渲染完成后调用HandlerInterceptor拦截器的afterCompletion方法。
- render(): 解析视图名称获取对应View,调用View的render方法通过Model来渲染视图。
DispatcherServlet#Render(ModelAndView,request,response)方法
DispatcherServlet#Render()源码如下:
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
... ...
/**
* Render the given ModelAndView.//
* <p>This is the last stage in handling a request. It may involve resolving the view by name.
* @param mv the ModelAndView to render
* @param request current HTTP servlet request
* @param response current HTTP servlet response
* @throws ServletException if view is missing or cannot be resolved
* @throws Exception if there's a problem rendering the view
*/
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 根据request中的Accept-language请求头信息对应国际化响应方式
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
//设置响应的国际化
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
//ViewName视图名称:由前缀,返回值,后缀组成
//mv.getModelInternal()返回ModelMap,ModelMap继承LinkedHashMap<String, Object>,该参数可以为空
//locale:国际化
//视图名不为空,通过循环viewResolve,获取到对应的视图解析器,如Thymeleaf的视图解析器为:ThymeleafViewResolver,通过视图解析器获取对应的View对象实例
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
//不需要进行向上查询,没有视图名,但ModelAndView object包含这个View Object
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
//设置响应的状态码
response.setStatus(mv.getStatus().value());
}
//开始渲染:调用具体的View对象,进行视图渲染
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
... ...
}
View#render(model,request,response)方法
SpringMVC用于处理视图最重要的两个接口是ViewResolver和View,
View接口最主要的方法是这个Render的方法。
package org.springframework.web.servlet;
/**
* MVC View主要用于web交互,渲染内容,展示model,单个视图可以展示多个model属性
* MVC View for a web interaction. Implementations are responsible for rendering
* content, and exposing the model. A single view exposes multiple model attributes.
* 亚马逊这本书《Expert One-On-One J2EE Design and Development》里面有相关MVC类和函数的分析
* <p>This class and the MVC approach associated with it is discussed in Chapter 12 of
* <a href="https://www.amazon.com/exec/obidos/tg/detail/-/0764543857/">Expert One-On-One J2EE Design and Development</a>
* by Rod Johnson (Wrox, 2002).
*
* <p>View implementations may differ widely. An obvious implementation would be
* JSP-based. Other implementations might be XSLT-based, or use an HTML generation library.
* This interface is designed to avoid restricting the range of possible implementations.
*
* <p>Views should be beans. They are likely to be instantiated as beans by a ViewResolver.
* As this interface is stateless, view implementations should be thread-safe.
*
*/
public interface View {
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
/**
* Return the content type of the view, if predetermined.
* <p>Can be used to check the view's content type upfront,
* i.e. before an actual rendering attempt.
* @return the content type String (optionally including a character set),
* or {@code null} if not predetermined
*/
@Nullable
default String getContentType() {
return null;
}
/**
* Model传进来的时候要去渲染,Model是一个上下文传递的数据源,request是一个请求源,response响应源。
* Render the view given the specified model.
* <p>The first step will be preparing the request: In the JSP case, this would mean
* setting model objects as request attributes. The second step will be the actual
* rendering of the view, for example including the JSP via a RequestDispatcher.
* @param model a Map with name Strings as keys and corresponding model
* objects as values (Map can also be {@code null} in case of empty model)
* @param request current HTTP request
* @param response he HTTP response we are building
* @throws Exception if rendering failed
*/
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
render()方法主要是根据具体对应的模版视图解析器,处理页面渲染。
模版包括:
- Thymeleaf
- Freemaker
- JSP
- Velocity
- JSON
- XML
- 其他
ViewResolver视图解析器
通过viewName来解析对应View实例
package org.springframework.web.servlet;
import java.util.Locale;
import org.springframework.lang.Nullable;
/**
* Interface to be implemented by objects that can resolve views by name.
*
* <p>View state doesn't change during the running of the application,
* so implementations are free to cache views.
*
* <p>Implementations are encouraged to support internationalization,
* i.e. localized view resolution.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see org.springframework.web.servlet.view.InternalResourceViewResolver
* @see org.springframework.web.servlet.view.ResourceBundleViewResolver
* @see org.springframework.web.servlet.view.XmlViewResolver
*/
public interface ViewResolver {
/**
* Resolve the given view by name.
* <p>Note: To allow for ViewResolver chaining, a ViewResolver should
* return {@code null} if a view with the given name is not defined in it.
* However, this is not required: Some ViewResolvers will always attempt
* to build View objects with the given name, unable to return {@code null}
* (rather throwing an exception when View creation failed).
* @param viewName name of the view to resolve
* @param locale the Locale in which to resolve the view.
* ViewResolvers that support internationalization should respect this.
* @return the View object, or {@code null} if not found
* (optional, to allow for ViewResolver chaining)
* @throws Exception if the view cannot be resolved
* (typically in case of problems creating an actual View object)
*/
//通过viewName来解析对应View实例,同时需要传递国际化参数
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
}
这里我们以Thymeleaf模版为例,Thymeleaf视图解析器ThymeleafViewResolver的resolveViewName方法,由其父类AbstractCachingViewResolver实现.
ThymeleafViewResolver.java
package org.thymeleaf.spring5.view;
public class ThymeleafViewResolver extends AbstractCachingViewResolver implements Ordered {
... ...
//创建视图
protected View createView(String viewName, Locale locale) throws Exception {
}
//加载视图
protected View loadView(String viewName, Locale locale) throws Exception {
}
}
AbstractCachingViewResolver#resolveViewName(viewName,locale)
package org.springframework.web.servlet.view;
public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {
public static final int DEFAULT_CACHE_LIMIT = 1024;
private CacheFilter cacheFilter = DEFAULT_CACHE_FILTER;
/** Fast access cache for Views, returning already cached instances without a global lock */
private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<>(DEFAULT_CACHE_LIMIT);//concurrenthashmap的size方法原理
/**
* The number of key-value mappings contained in this map.
*/
transient int size;
public int size() {
return size;
}
/** Map from view key to View instance, synchronized for View creation */
@SuppressWarnings("serial")
private final Map<Object, View> viewCreationCache =
new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) { //LinkedHashMap:满足一定条件时删除老的数据
if (size() > getCacheLimit()) {
viewAccessCache.remove(eldest.getKey());
return true;
}
else {
return false;
}
}
};
/**
* Return if caching is enabled.
* 注意这里的缓存处理方式,cacheLimit这个属性的初始值为1024
*/
public boolean isCache() {
return (this.cacheLimit > 0);//第一次进来this对象是ThymeleafViewResolver,第二次是InternalResourrceViewResolver
}
//视图解析器
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (!isCache()) {
return createView(viewName, locale);
}
else {
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {//判断是否有缓存,并不是每次都要去读这个对象
synchronized (this.viewCreationCache) {//加上同步锁
view = this.viewCreationCache.get(cacheKey);
if (view == null) {//判断View是否存在
// Ask the subclass to create the View object.
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
}
}
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace(formatKey(cacheKey) + "served from cache");
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
... ...
}
下面查看这个resolveViewName()方法在哪里调用
视图解析器:整个访问流程先执行的这个最佳匹配原则
ContentNegotiatingViewResolver#resolveViewName(viewName,locale)
内容调停视图解析器,先执行resolveViewName(viewName,locale)
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
implements ViewResolver, Ordered, InitializingBean {
//1:视图解析器:访问流程是先执行的这个视图解析器,再执行上面的模版对应的视图解析器(1)
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
//根据MediaType获取所有的View对象,调用上面的视图解析器
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
//获取最佳匹配的View对象
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;//返回最佳匹配视图
}
}
String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
" given " + requestedMediaTypes.toString() : "";
if (this.useNotAcceptableStatusCode) {
if (logger.isDebugEnabled()) {
logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
}
return NOT_ACCEPTABLE_VIEW;
}
else {
logger.debug("View remains unresolved" + mediaTypeInfo);
return null;
}
}
//内容协调:根据MediaType来获取View对象集合
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
throws Exception {
List<View> candidateViews = new ArrayList<>();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);//调用对应的视图处理器处理视图
if (view != null) {
candidateViews.add(view);
}
//根据MediaType获取扩展
for (MediaType requestedMediaType : requestedMediaTypes) {
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
for (String extension : extensions) {
String viewNameWithExtension = viewName + '.' + extension;
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
if (view != null) {
candidateViews.add(view);
}
}
}
}
}
if (!CollectionUtils.isEmpty(this.defaultViews)) {
candidateViews.addAll(this.defaultViews);
}
return candidateViews;
}
}
所有调用完毕后,返回View到DispatcherServlet#render()方法,其中的很多细节,如通过getOrder()排序指定视图解析器,国际化等内容,就不一一描述了。
Proverbs: It is not our abilities that show what we truly are, it is our choices.
来源:CSDN
作者:路过雨林
链接:https://blog.csdn.net/zhuqing338/article/details/103702890