Spring MVC源码解析 - ViewResolver组件

目录

一. ViewResolver

二. AbstractCachingViewResolver

三. UrlBasedViewResolver

四. InternalResourceViewResolver


DispatcherServlet.properties中,默认的视图解析器是InternalResourceViewResolver。将视图名解析为URL文件。

一. ViewResolver

/**
 * 通过名字解析视图
 */
public interface ViewResolver {

	/**
	 * 解析视图
	 */
	@Nullable
	View resolveViewName(String viewName, Locale locale) throws Exception;

}

 


二.  AbstractCachingViewResolver

/**
 * 一旦解析成功,就缓存View对象
 * 
 * 子类要去实现loadView模板方法。
 *
 */
public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {

	/** Default maximum number of entries for the view cache: 1024. */
	public static final int DEFAULT_CACHE_LIMIT = 1024;

	/** Dummy marker object for unresolved views in the cache Maps. */
	private static final View UNRESOLVED_VIEW = new View() {
		@Override
		@Nullable
		public String getContentType() {
			return null;
		}
		@Override
		public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {
		}
	};


	/** 缓存中键值对最大的数量,1024 */
	private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;

	/** 一旦解析不成功,避免再次解析它,有一个缓存对此进行处理 */
	private boolean cacheUnresolved = true;

	/** 视图的缓存 */
	private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<>(DEFAULT_CACHE_LIMIT);

	/** 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) {
					if (size() > getCacheLimit()) {
						viewAccessCache.remove(eldest.getKey());
						return true;
					}
					else {
						return false;
					}
				}
			};


	/**
	 * 设置缓存中键值对的数量限制
	 */
	public void setCacheLimit(int cacheLimit) {
		this.cacheLimit = cacheLimit;
	}

	/**
	 * 返回缓存中键值对的数量的限制
	 */
	public int getCacheLimit() {
		return this.cacheLimit;
	}

	/**
	 * 开启或者关闭缓存
	 */
	public void setCache(boolean cache) {
		this.cacheLimit = (cache ? DEFAULT_CACHE_LIMIT : 0);
	}

	/**
	 * 返回缓存是否可以使用。即规定的键值对数量限制大于0
	 */
	public boolean isCache() {
		return (this.cacheLimit > 0);
	}

	/**
	 * 缓存解析失败的视图
	 */
	public void setCacheUnresolved(boolean cacheUnresolved) {
		this.cacheUnresolved = cacheUnresolved;
	}

	/**
	 * 返回是否开启了缓存解析失败的视图的功能
	 */
	public boolean isCacheUnresolved() {
		return this.cacheUnresolved;
	}


	/**
	 * 如果开启了缓存,先从缓存中尝试获取视图,如果没有则创建视图,然后放到缓存中;如果没有开启缓存,直接创建视图
	 */
	@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) {
						// Ask the subclass to create the View object.
						view = createView(viewName, locale);
						if (view == null && this.cacheUnresolved) {
							// view = new View();
							view = UNRESOLVED_VIEW;
						}
						if (view != null) {
							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);
		}
	}

	private static String formatKey(Object cacheKey) {
		return "View with key [" + cacheKey + "] ";
	}

	/**
	 * 返回"视图名_locale"的形式
	 */
	protected Object getCacheKey(String viewName, Locale locale) {
		return viewName + '_' + locale;
	}

	/**
	 * 删除指定视图
	 */
	public void removeFromCache(String viewName, Locale locale) {
		if (!isCache()) {
			logger.warn("Caching is OFF (removal not necessary)");
		}
		else {
			Object cacheKey = getCacheKey(viewName, locale);
			Object cachedView;
			synchronized (this.viewCreationCache) {
				this.viewAccessCache.remove(cacheKey);
				cachedView = this.viewCreationCache.remove(cacheKey);
			}
			if (logger.isDebugEnabled()) {
				// Some debug output might be useful...
				logger.debug(formatKey(cacheKey) +
						(cachedView != null ? "cleared from cache" : "not found in the cache"));
			}
		}
	}

	/**
	 * 清楚键值对视图缓存,删除所有的缓存的视图对象
	 */
	public void clearCache() {
		logger.debug("Clearing all views from the cache");
		synchronized (this.viewCreationCache) {
			this.viewAccessCache.clear();
			this.viewCreationCache.clear();
		}
	}


	/**
	 * 创建一个视图对象
	 */
	@Nullable
	protected View createView(String viewName, Locale locale) throws Exception {
		return loadView(viewName, locale);
	}

	/**
	 * 子类必须要去实现这个方法,使用指定的视图名建立一个视图对象。返回的视图对象会被这个ViewResolver基础类所缓存。
	 */
	@Nullable
	protected abstract View loadView(String viewName, Locale locale) throws Exception;

}

 


三. UrlBasedViewResolver

/**
 * ViewResolver的简单实现类。如果你的名字以一种简单的方式匹配你的视图资源的名字,那么就没有必要为每个视图定义一个专用的映射
 *
 * 支持像InternalResourceView和FreeMakerView的AbstractUrlBasedView子类。
 * 所有由这个解析器的生成的视图class可以通过viewClass属性来指定
 *
 * 视图名字可以是资源URL,或者使用一个指定的前缀/后缀。明确支持将一个持有RequesetContext的属性给所有的视图
 *
 * Example: prefix="/WEB-INF/jsp/", suffix=".jsp", viewname="test" ->
 * "/WEB-INF/jsp/test.jsp"
 *
 * 作为一个特点,重定向URL可以使用“redirect:”来指定。
 *
 * 此外,转发URL可以通过“forward:”来指定
 * 转发不应该使用在JSP URL上,去使用合乎逻辑的视图名称
 *
 *
 * 当调用ViewResolvers链时,一个UrlBasedViewResolver会检查{@link AbstractUrlBasedView#checkResource}指定的资源是否真实存在
 * 
 * 然而,使用InternalResourceView,一般不可能去确定目标资源的存在性。在这样的方案中,一个UrlBasedViewResolver总是返回任何给定视图名字的视图对象
 * 因此,它应该被配置成链中的最后的一个ViewResolver
 *
 */
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {

	/**
	 * 特殊的视图名字前缀,指定一个重定向类型的URL
	 * 通常是在一个表单已经被提交并且处理后,交给控制器的。
	 * 这样的视图名字不是在配置的默认的方式被解析,而是使用特殊的捷径。
	 */
	public static final String REDIRECT_URL_PREFIX = "redirect:";

	/**
	 * 特殊的视图名字前缀,指定一个转发类型的URL
	 * 通常是在一个表单已经被提交并且处理后,交给控制器的。
	 * 这样的视图名字不是在配置的默认的方式被解析,而是使用特殊的捷径。
	 */
	public static final String FORWARD_URL_PREFIX = "forward:";


	@Nullable
	private Class<?> viewClass;

	private String prefix = "";

	private String suffix = "";

	@Nullable
	private String contentType;

	private boolean redirectContextRelative = true;

	private boolean redirectHttp10Compatible = true;

	@Nullable
	private String[] redirectHosts;

	@Nullable
	private String requestContextAttribute;

	/** Map of static attributes, keyed by attribute name (String). */
	private final Map<String, Object> staticAttributes = new HashMap<>();

	@Nullable
	private Boolean exposePathVariables;

	@Nullable
	private Boolean exposeContextBeansAsAttributes;

	@Nullable
	private String[] exposedContextBeanNames;

	@Nullable
	private String[] viewNames;

	private int order = Ordered.LOWEST_PRECEDENCE;

	@Override
	protected void initApplicationContext() {
		super.initApplicationContext();
		if (getViewClass() == null) {
			throw new IllegalArgumentException("Property 'viewClass' is required");
		}
	}

	protected View createView(String viewName, Locale locale) throws Exception {
		// 如果这个解析器不支持处理给定的视图,返回null,然后传递给链中的下一个解析器
		if (!canHandle(viewName, locale)) {
			return null;
		}

		// Check for special "redirect:" prefix.
		if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
			// 截掉redirect:
			String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
			RedirectView view = new RedirectView(redirectUrl,
					isRedirectContextRelative(), isRedirectHttp10Compatible());
			String[] hosts = getRedirectHosts();
			if (hosts != null) {
				view.setHosts(hosts);
			}
			return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
		}

		// Check for special "forward:" prefix.
		if (viewName.startsWith(FORWARD_URL_PREFIX)) {
			// 截掉forward:
			String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
			InternalResourceView view = new InternalResourceView(forwardUrl);
			return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
		}

		// 否则回调给子类的loadView
		return super.createView(viewName, locale);
	}

	/**
	 * 表示ViewResolver是否能处理给定的视图名字。如果不能,createView将会返回null。默认的实现类会核对配置的视图名字
	 */
	protected boolean canHandle(String viewName, Locale locale) {
		String[] viewNames = getViewNames();
		// 将给定的视图名字跟已有的view names数组匹配
		return (viewNames == null || PatternMatchUtils.simpleMatch(viewNames, viewName));
	}

	/**
	 * 委派buildView来创建指定视图class的一个新实例。使用以下的Spring生命周期的方法
	 * <ul>
	 * <li>ApplicationContextAware's {@code setApplicationContext}
	 * <li>InitializingBean's {@code afterPropertiesSet}
	 * </ul>
	 */
	@Override
	protected View loadView(String viewName, Locale locale) throws Exception {
		AbstractUrlBasedView view = buildView(viewName);
		View result = applyLifecycleMethods(viewName, view);
		// 检查资源是否存在
		return (view.checkResource(locale) ? result : null);
	}

	protected AbstractUrlBasedView buildView(String viewName) throws Exception {
		Class<?> viewClass = getViewClass();
		Assert.state(viewClass != null, "No view class");

		AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
		// URL = prefix + view_name + suffix
		view.setUrl(getPrefix() + viewName + getSuffix());

		String contentType = getContentType();
		if (contentType != null) {
			view.setContentType(contentType);
		}

		view.setRequestContextAttribute(getRequestContextAttribute());
		view.setAttributesMap(getAttributesMap());

		Boolean exposePathVariables = getExposePathVariables();
		if (exposePathVariables != null) {
			view.setExposePathVariables(exposePathVariables);
		}
		Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
		if (exposeContextBeansAsAttributes != null) {
			view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
		}
		String[] exposedContextBeanNames = getExposedContextBeanNames();
		if (exposedContextBeanNames != null) {
			view.setExposedContextBeanNames(exposedContextBeanNames);
		}

		return view;
	}

	/**
	 * 将包含的ApplicationContext生命周期方法给指定的View实例,如果这样的上下文是可用的话。
	 */
	protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) {
		ApplicationContext context = getApplicationContext();
		if (context != null) {
			Object initialized = context.getAutowireCapableBeanFactory().initializeBean(view, viewName);
			if (initialized instanceof View) {
				return (View) initialized;
			}
		}
		return view;
	}

}

 


四. InternalResourceViewResolver

/**
 * 便捷的UrlBasedViewResolver的子类,支持InternalResourceView和子类JstlView。
 *
 * 由这个解析器生成的所有视图的视图类可以通过setViewClass来指定。默认的是InternalResourceView,如果JSTL API存在的话,则是JstlView
 *
 * 顺便提一句,将JSP文件放到web-inf下,避免直接访问它们,而只有控制器可以访问它们。
 * 当在调用ViewResolver链中,一个InternalResourceViewResolver总是需要在最后,因为它会解析任何视图名字,无论潜在的资源是否存在。
 */
public class InternalResourceViewResolver extends UrlBasedViewResolver {

	private static final boolean jstlPresent = ClassUtils.isPresent(
			"javax.servlet.jsp.jstl.core.Config", InternalResourceViewResolver.class.getClassLoader());

	@Nullable
	private Boolean alwaysInclude;


	/**
	 * Sets the default {@link #setViewClass view class} to {@link #requiredViewClass}:
	 * by default {@link InternalResourceView}, or {@link JstlView} if the JSTL API
	 * is present.
	 */
	public InternalResourceViewResolver() {
		Class<?> viewClass = requiredViewClass();
		if (InternalResourceView.class == viewClass && jstlPresent) {
			viewClass = JstlView.class;
		}
		setViewClass(viewClass);
	}

	public InternalResourceViewResolver(String prefix, String suffix) {
		this();
		setPrefix(prefix);
		setSuffix(suffix);
	}


	/**
	 * This resolver requires {@link InternalResourceView}.
	 */
	@Override
	protected Class<?> requiredViewClass() {
		return InternalResourceView.class;
	}

	/**
	 * 指定是否总是包含视图而不是转发,默认是false
	 */
	public void setAlwaysInclude(boolean alwaysInclude) {
		this.alwaysInclude = alwaysInclude;
	}


	@Override
	protected AbstractUrlBasedView buildView(String viewName) throws Exception {
		InternalResourceView view = (InternalResourceView) super.buildView(viewName);
		if (this.alwaysInclude != null) {
			view.setAlwaysInclude(this.alwaysInclude);
		}
		// 设置是否明确防止调回到当前的处理器路径
		view.setPreventDispatchLoop(true);
		return view;
	}

}

 

发布了88 篇原创文章 · 获赞 14 · 访问量 3万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术工厂 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览