SpringBoot分析原理篇-SpringMVC视图渲染过程

version:2.2.0.RELEASE

视图渲染过程:从前端到后台

SpringMVC流程架构图:
1.SpingMVC架构设计时序图
按照一般思考的流程,这里直接从DispatcherServlet前端控制器的方法render()开始分析。

DispatcherServlet入口

  1. init():DispatcherServlet 继承FrameworkServlet;而FrameworkServlet继承HttpServletBean,并实现 了ApplicationContextAware接口,HttpServletBean实现init()方法,加载web.xml中DispatcherServlet配置,调用FrameworkServlet # initServletBean()。
  2. initServletBean():FrameworkServlet # initServletBean(),调用FrameworkServlet#initWebApplicationContext()。
  3. initWebApplicationContext():FrameworkServlet#initWebApplicationContext(),初始化WebApplicationContext 容器(IOC容器);调用FrameworkServlet#createWebApplicationContext(WebApplicationContext)
  4. createWebApplicationContext(WebApplicationContext):获取或生成容器,调用DispatcherServlet#onRefresh(ApplicationContext)。[^1]
  5. onRefresh(ApplicationContext): DispatcherServlet 的onRefresh() 方法,用于在ApplicationContext刷新后进行策略组件的初始化。
  6. initStrategies(ApplicationContext):DispatcherServlet 的initStrategies(ApplicationContext):初始化9个策略组件。
  7. doService()::设置request的相关属性
  8. doDispatch():前端控制器DispatcherServlet在doDispatch( )方法中去获取HandlerMapping和HandlerAdapter。
  9. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException):异常分析,存在ModelAndView后调用render方法进行渲染,渲染完成后调用HandlerInterceptor拦截器的afterCompletion方法。
  10. 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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值