4.3 视图解析和视图显示
上一节,深入的剖析了作为总控制器的派遣器Servlet如何通过处理器映射查找处理器,并且通过处理器适配器进行适配调用处理器实现的业务逻辑服务,进而返回逻辑视图名和模型数据对象。
这一节,我们将分析作为总控制器的派遣器Servlet如何解析视图和显示视图。这是Spring Web MVC流程中最后一个关键步骤。
由于视图显示技术的多样性,存在很多视图解析器和视图的实现,这一节我们将分析典型的视图解析器和视图显示的实现。他们是基于URL的视图解析器,然后,对于其他更多的视图解析器和视图的实现进行剖析。
4.3.1 基于URL的视图解析器和视图
基于URL的视图解析器是视图解析器接口的简单实现,它不需要显示的映射定义,而是直接的把逻辑视图名解析成为资源URL。它通常使用在基于URL的简单的视图显示技术上,并且逻辑视图名是URL的一部分。如下视图解析器和视图的类图所示,
图表 4‑40
图表 4‑41
抽象缓存视图解析器是视图解析器的直接的抽象实现类。它实现了对视图的内存缓存。如下代码所示,
public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {
// 设置缓存是否开启
private boolean cache = true;
// 缓存视图的映射对象
private final Map<Object, View> viewCache = new HashMap<Object, View>();
public void setCache(boolean cache) {
this.cache = cache;
}
public boolean isCache() {
return this.cache;
}
// 解析视图方法的逻辑实现
public View resolveViewName(String viewName, Locale locale) throws Exception {
// 如果视图缓存关闭,则每次都创建视图
if (!isCache()) {
return createView(viewName, locale);
}
// 如果视图缓存打开
else {
// 首先取得缓存中存储视图的关键字对象
Object cacheKey = getCacheKey(viewName, locale);
// 需要同步存取
synchronized (this.viewCache) {
// 检查缓存中是否已经存在视图
View view = this.viewCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
view = createView(viewName, locale);
// 将创建的视图加入到缓存中
this.viewCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
// 返回从缓存中获得的视图或者新创建的视图
return view;
}
}
}
protected Object getCacheKey(String viewName, Locale locale) {
// 使用逻辑视图名和地域作为视图的关键字
return viewName + "_" + locale;
}
// 提供功能移除缓存的视图
public void removeFromCache(String viewName, Locale locale) {
if (!this.cache) {
logger.warn("View caching is SWITCHED OFF -- removal not necessary");
}
else {
Object cacheKey = getCacheKey(viewName, locale);
Object cachedView;
synchronized (this.viewCache) {
cachedView = this.viewCache.remove(cacheKey);
}
if (cachedView == null) {
// Some debug output might be useful...
if (logger.isDebugEnabled()) {
logger.debug("No cached instance for view '" + cacheKey + "' was found");
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Cache for view " + cacheKey + " has been cleared");
}
}
}
}
// 提供功能清除所有缓存的视图
public void clearCache() {
logger.debug("Clearing entire view cache");
synchronized (this.viewCache) {
this.viewCache.clear();
}
}
protected View createView(String viewName, Locale locale) throws Exception {
return loadView(viewName, locale);
}
// 让子类根据具体实现创建不同的视图
protected abstract View loadView(String viewName, Locale locale) throws Exception;
}
视图解析器实现体系结构中下一个层次是基于URL的视图解析器,它除了根据重定向前缀和转发前缀构造一个重定向视图和转发视图以外,它还为每个通常的URL创建一个抽象的基于URL视图的子类对象并且返回。如下代码注释,
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
// 重定向前缀
public static final String REDIRECT_URL_PREFIX = "redirect:";
// 转发前缀
public static final String FORWARD_URL_PREFIX = "forward:";
// 视图类名,应该是抽象的基于URL视图的子类
private Class viewClass;
// URL前缀
private String prefix = "";
// URL后缀
private String suffix = "";
// 能够处理的视图逻辑名字集合,如果为空,任何视图逻辑名都可以处理
private String[] viewNames = null;
// 支持的内容类型
private String contentType;
// 以/开头的URL是相对于Web Server根还是应用程序根
private boolean redirectContextRelative = true;
// 使用HTTP 1.0兼容还是使用HTTP1.1兼容,不同版本的HTTP协议重定向方法不同
private boolean redirectHttp10Compatible = true;
// 保存请求环境属性的关键字
private String requestContextAttribute;
// 最大顺序
private int order = Integer.MAX_VALUE;
// 静态的属性,应用到所有的解析的视图上
private final Map<String, Object> staticAttributes = new HashMap<String, Object>();
// 设置视图类,需要的视图必须是抽象的基于URL的视图
public void setViewClass(Class viewClass) {
if (viewClass == null || !requiredViewClass().isAssignableFrom(viewClass)) {
throw new IllegalArgumentException(
"Given view class [" + (viewClass != null ? viewClass.getName() : null) +
"] is not of type [" + requiredViewClass().getName() + "]");
}
this.viewClass = viewClass;
}
protected Class requiredViewClass() {
return AbstractUrlBasedView.class;
}
@Override
protected void initApplicationContext() {
super.initApplicationContext();
// 初始化是检查必要的是视图类
if (getViewClass() == null) {
throw new IllegalArgumentException("Property 'viewClass' is required");
}
}
@Override
protected Object getCacheKey(String viewName, Locale locale) {
// 仅仅使用视图逻辑名作为缓存的关键字
return viewName;
}
@Override
protected View createView(String viewName, Locale locale) throws Exception {
// If this resolver is not supposed to handle the given view,
// return null to pass on to the next resolver in the chain.
if (!canHandle(viewName, locale)) {
return null;
}
// Check for special "redirect:" prefix.
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
return new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
}
// Check for special "forward:" prefix.
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// Else fall back to superclass implementation: calling loadView.
return super.createView(viewName, locale);
}
protected boolean canHandle(String viewName, Locale locale) {
// 如果声明了可以处理的视图结合,则进行匹配,如果匹配成功,则能够处理,否则,不能处理当前逻辑视图
String[] viewNames = getViewNames();
return (viewNames == null || PatternMatchUtils.simpleMatch(viewNames, viewName));
}
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
// 创建抽象的基于URL的视图
AbstractUrlBasedView view = buildView(viewName);
// 将创建的视图放入到Web应用程序环境中,并且进行初始化
View result = (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
// 如果视图能处理当前的地域,返回当前视图
return (view.checkResource(locale) ? result : null);
}
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
// 根据配置的视图类名实例化视图对象
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
// URL = 前缀 + 逻辑视图名 + 后缀
view.setUrl(getPrefix() + viewName + getSuffix());
// 设置内容类型
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
// 设置请求环境属性的关键字值
view.setRequestContextAttribute(getRequestContextAttribute());
// 这是静态属性
view.setAttributesMap(getAttributesMap());
return view;
}
}
我们看到,抽象的基于URL的视图解析器返回抽象的基于URL的视图。现在我们分析一下抽象的基于URL的视图的类的实现。实现视图的第一层的类是抽象视图,它合并了派遣器Servlet传进来的模型数据和视图解析器传进来的静态模型数据。将这些数据准备给子类实现视图显示逻辑。如下代码所示,
public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {
// 缺省的内容类型为HTML
public static final String DEFAULT_CONTENT_TYPE = "text/html;charset=ISO-8859-1";
// 每次输出的数据长度
private static final int OUTPUT_BYTE_ARRAY_INITIAL_SIZE = 4096;
// 视图作为Bean的名字
private String beanName;
// 视图的能处理的内容类型
private String contentType = DEFAULT_CONTENT_TYPE;
// 保存请求环境属性的关键字
private String requestContextAttribute;
// 静态属性集合
private final Map<String, Object> staticAttributes = new HashMap<String, Object>();
// 使用CSV格式的字符串设置静态属性
public void setAttributesCSV(String propString) throws IllegalArgumentException {
if (propString != null) {
StringTokenizer st = new StringTokenizer(propString, ",");
while (st.hasMoreTokens()) {
String tok = st.nextToken();
int eqIdx = tok.indexOf("=");
if (eqIdx == -1) {
throw new IllegalArgumentException("Expected = in attributes CSV string '" + propString + "'");
}
if (eqIdx >= tok.length() - 2) {
throw new IllegalArgumentException(
"At least 2 characters ([]) required in attributes CSV string '" + propString + "'");
}
String name = tok.substring(0, eqIdx);
String value = tok.substring(eqIdx + 1);
// Delete first and last characters of value: { and }
value = value.substring(1);
value = value.substring(0, value.length() - 1);
addStaticAttribute(name, value);
}
}
}
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
}
// Consolidate static and dynamic model attributes.
Map<String, Object> mergedModel =
new HashMap<String, Object>(this.staticAttributes.size() + (model != null ? model.size() : 0));
mergedModel.putAll(this.staticAttributes);
if (model != null) {
mergedModel.putAll(model);
}
// Expose RequestContext?
if (this.requestContextAttribute != null) {
mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
}
// 准备响应
prepareResponse(request, response);
// 生成响应
renderMergedOutputModel(mergedModel, request, response);
}
// 如果设置了请求环境属性的关键字值,导出请求环境
protected RequestContext createRequestContext(
HttpServletRequest request, HttpServletResponse response, Map<String, Object> model) {
return new RequestContext(request, response, getServletContext(), model);
}
// 解决IE的一个bug
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
if (generatesDownloadContent()) {
response.setHeader("Pragma", "private");
response.setHeader("Cache-Control", "private, must-revalidate");
}
}
// 默认不产生下载内容
protected boolean generatesDownloadContent() {
return false;
}
// 子类需要实现显示视图的逻辑
protected abstract void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
// 提供功能导出模型数据到请求属性中
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
for (Map.Entry<String, Object> entry : model.entrySet()) {
String modelName = entry.getKey();
Object modelValue = entry.getValue();
if (modelValue != null) {
request.setAttribute(modelName, modelValue);
if (logger.isDebugEnabled()) {
logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
"] to request in view with name '" + getBeanName() + "'");
}
}
else {
request.removeAttribute(modelName);
if (logger.isDebugEnabled()) {
logger.debug("Removed model object '" + modelName +
"' from request in view with name '" + getBeanName() + "'");
}
}
}
}
// 创建临时的内存输出流
protected ByteArrayOutputStream createTemporaryOutputStream() {
return new ByteArrayOutputStream(OUTPUT_BYTE_ARRAY_INITIAL_SIZE);
}
// 将一个内存输出流写入响应中
protected void writeToResponse(HttpServletResponse response, ByteArrayOutputStream baos) throws IOException {
// Write content type and also length (determined via byte array).
response.setContentType(getContentType());
response.setContentLength(baos.size());
// Flush byte array to servlet output stream.
ServletOutputStream out = response.getOutputStream();
baos.writeTo(out);
out.flush();
}
}
视图实现的体系结构中的下一个实现类是抽象的基于URL的视图。它没有实现如何显示URL,而是实现了保存一个URL的值,子类需要根据URL的值来完成视图的显示。如下代码所示,
public abstract class AbstractUrlBasedView extends AbstractView implements InitializingBean {
// 存储一个将要显示的URL
private String url;
protected AbstractUrlBasedView() {
}
protected AbstractUrlBasedView(String url) {
this.url = url;
}
public void afterPropertiesSet() throws Exception {
// 默认URL是必须的
if (isUrlRequired() && getUrl() == null) {
throw new IllegalArgumentException("Property 'url' is required");
}
}
protected boolean isUrlRequired() {
return true;
}
// 占位符方法,子类需要判断是否此视图支持某一个地域信息
public boolean checkResource(Locale locale) throws Exception {
return true;
}
}
抽象的基于URL的视图解析器和抽象的基于URL的视图是体系结构中的基础抽象类,所有其他的实体类都是通过实现他们来实现一个具体视图解析的逻辑。
4.3.1.1 内部资源视图解析器和内部资源视图
内部资源视图解析器和内部资源视图是最常用的视图解析器和视图的实现。他们通过转发请求给JSP, Servlet或者具有JSTL标记的JSP组件进行处理。
内部资源视图解析器通过改写抽象的基于URL的视图解析器的构建视图方法,构建专用的内部资源视图或者JSTL视图。如下代码所示,
public class InternalResourceViewResolver extends UrlBasedViewResolver {
// 判断是否JSTL相关类存在
private static final boolean jstlPresent = ClassUtils.isPresent(
"javax.servlet.jsp.jstl.core.Config", InternalResourceViewResolver.class.getClassLoader());
// 是否总是使用包含派遣HTTP请求
private Boolean alwaysInclude;
// 是否导出环境的Bean作为属性
private Boolean exposeContextBeansAsAttributes;
// 需要导出Web应用程序环境中的Bean的名字
private String[] exposedContextBeanNames;
public InternalResourceViewResolver() {
// 如果JSTL相关类存在,则使用JSTL视图,否则使用内部资源视图
Class viewClass = requiredViewClass();
if (viewClass.equals(InternalResourceView.class) && jstlPresent) {
viewClass = JstlView.class;
}
setViewClass(viewClass);
}
@Override
protected Class requiredViewClass() {
// 默认使用内部资源视图
return InternalResourceView.class;
}
@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
// 使用超类创建内部资源视图或者JSTL视图
InternalResourceView view = (InternalResourceView) super.buildView(viewName);
// 设置是否总是使用包含请求操作
if (this.alwaysInclude != null) {
view.setAlwaysInclude(this.alwaysInclude);
}
// 设置是否导出环境的Bean作为属性
if (this.exposeContextBeansAsAttributes != null) {
view.setExposeContextBeansAsAttributes(this.exposeContextBeansAsAttributes);
}
// 需要导出Web应用程序环境中的Bean的名字
if (this.exposedContextBeanNames != null) {
view.setExposedContextBeanNames(this.exposedContextBeanNames);
}
// 防止派遣死循环
view.setPreventDispatchLoop(true);
return view;
}
}
从上面视图解析器的实现可以看出,它通过检查是否存在JSTL相关类,来创建内部资源视图还是JSTL视图。事实上,JSTL视图是内部资源视图的子类,它不但支持JSP和Servlet服务器断组件,还支持包含JSTL的JSP展示层的组件。如下代码所示,
public class InternalResourceView extends AbstractUrlBasedView {
// 是否总是使用包含派遣请求
private boolean alwaysInclude = false;
// 是否导出转发属性
private volatile Boolean exposeForwardAttributes;
// 是否导出应用程序环境中的Bean作为属性
private boolean exposeContextBeansAsAttributes = false;
// 导出那些Bean作为属性
private Set<String> exposedContextBeanNames;
// 是否阻止派遣死循环
private boolean preventDispatchLoop = false;
@Override
protected void initServletContext(ServletContext sc) {
// 如果早于Servlet 2.5,需要手动导出转发属性
if (this.exposeForwardAttributes == null && sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
this.exposeForwardAttributes = Boolean.TRUE;
}
}
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine which request handle to expose to the RequestDispatcher.
HttpServletRequest requestToExpose = getRequestToExpose(request);
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, requestToExpose);
// Expose helpers as request attributes, if any.
exposeHelpers(requestToExpose);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(requestToExpose, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(requestToExpose, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(requestToExpose, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
exposeForwardRequestAttributes(requestToExpose);
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(requestToExpose, response);
}
}
protected HttpServletRequest getRequestToExpose(HttpServletRequest originalRequest) {
// 如果导出Bean作为属性,则创建一个代理HTTP Servlet请求,这个代理请求包含导出的Bean作为属性
if (this.exposeContextBeansAsAttributes || this.exposedContextBeanNames != null) {
return new ContextExposingHttpServletRequest(
originalRequest, getWebApplicationContext(), this.exposedContextBeanNames);
}
// 否则导出原来的HTTP Servlet请求
return originalRequest;
}
protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response)
throws Exception {
String path = getUrl();
// 如果设置了防止派遣死循环,并且检测到派遣到最初的URL,则阻止死循环,抛出异常,终止处理
if (this.preventDispatchLoop) {
String uri = request.getRequestURI();
if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {
throw new ServletException("Circular view path [" + path + "]: would dispatch back " +
"to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " +
"(Hint: This may be the result of an unspecified view, due to default view name generation.)");
}
}
return path;
}
protected RequestDispatcher getRequestDispatcher(HttpServletRequest request, String path) {
// 使用容器的请求派遣器
return request.getRequestDispatcher(path);
}
protected boolean useInclude(HttpServletRequest request, HttpServletResponse response) {
// 如果设置了总是使用包含,或者请求是包含请求,或者已经发送了响应代码
return (this.alwaysInclude || WebUtils.isIncludeRequest(request) || response.isCommitted());
}
protected void exposeForwardRequestAttributes(HttpServletRequest request) {
if (this.exposeForwardAttributes != null && this.exposeForwardAttributes) {
try {
// 导出转发请求属性FORWARD_REQUEST_URI_ATTRIBUTE, FORWARD_CONTEXT_PATH_ATTRIBUTE, FORWARD_SERVLET_PATH_ATTRIBUTE, FORWARD_PATH_INFO_ATTRIBUTE, FORWARD_QUERY_STRING_ATTRIBUTE
WebUtils.exposeForwardRequestAttributes(request);
}
catch (Exception ex) {
// Servlet container rejected to set internal attributes, e.g. on TriFork.
this.exposeForwardAttributes = Boolean.FALSE;
}
}
}
}
子类JSTL视图能够重用了所有的内部资源视图的逻辑,而且增加了导出本地化环境的实现。如下代码所示,
public class JstlView extends InternalResourceView {
// 设置的消息源
private MessageSource messageSource;
@Override
protected void exposeHelpers(HttpServletRequest request) throws Exception {
// 如果设置了消息源,则导出设置的消息源
if (this.messageSource != null) {
JstlUtils.exposeLocalizationContext(request, this.messageSource);
}
// 否则导出应用程序环境作为消息源,应用程序环境也实现了消息源接口
else {
JstlUtils.exposeLocalizationContext(new RequestContext(request, getServletContext()));
}
}
}
4.3.1.2 瓦块视图解析器和瓦块视图
瓦块视图解析器和瓦块视图把HTTP请求派遣给瓦块容器进行处理,瓦块容器使用不同的瓦块定义来显示一个完整的瓦块页面。
瓦块视图解析器继承自抽象的基于URL的视图解析器。简单的通过返回瓦块视图类来实现的。如下代码所示,
public class TilesViewResolver extends UrlBasedViewResolver {
public TilesViewResolver() {
setViewClass(requiredViewClass());
}
@Override
protected Class requiredViewClass() {
// 简单的返回瓦块视图的定义
return TilesView.class;
}
}
瓦块视图则把当前的URL交给瓦块容器进行响应。如下代码注释,
public class TilesView extends AbstractUrlBasedView {
@Override
public boolean checkResource(final Locale locale) throws Exception {
// 从应用程序对象中取得瓦块容器
TilesContainer container = ServletUtil.getContainer(getServletContext());
// 瓦块容器应该是基本瓦块容器类型,如果不是,则做乐观处理
if (!(container instanceof BasicTilesContainer)) {
// Cannot check properly - let's assume it's there.
return true;
}
// 创建瓦块请求环境
BasicTilesContainer basicContainer = (BasicTilesContainer) container;
TilesApplicationContext appContext = new ServletTilesApplicationContext(getServletContext());
TilesRequestContext requestContext = new ServletTilesRequestContext(appContext, null, null) {
@Override
public Locale getRequestLocale() {
return locale;
}
};
// 检查是否存在瓦块页面定义可以处理当前的URL
return (basicContainer.getDefinitionsFactory().getDefinition(getUrl(), requestContext) != null);
}
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
ServletContext servletContext = getServletContext();
// 从应用程序对象中取得瓦块容器
TilesContainer container = ServletUtil.getContainer(servletContext);
// 如果瓦块容器不存在,则不能处理当前的请求,终止处理
if (container == null) {
throw new ServletException("Tiles container is not initialized. " +
"Have you added a TilesConfigurer to your web application context?");
}
// 导出模型映射数据作为请求属性以供瓦块容器使用
exposeModelAsRequestAttributes(model, request);
// 导出本地化环境
JstlUtils.exposeLocalizationContext(new RequestContext(request, servletContext));
if (!response.isCommitted()) {
// Tiles is going to use a forward, but some web containers (e.g. OC4J 10.1.3)
// do not properly expose the Servlet 2.4 forward request attributes... However,
// must not do this on Servlet 2.5 or above, mainly for GlassFish compatibility.
ServletContext sc = getServletContext();
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
WebUtils.exposeForwardRequestAttributes(request);
}
}
// 把请求交给瓦块容器进行处理
container.render(getUrl(), request, response);
}
}
4.3.1.3 模板视图解析器和模板视图
模板视图解析器和模板视图把HTTP请求传递给模板技术的容器进行处理,它支持Velocity和FreeMaker。
抽象的模板视图解析器继承自基于URL的视图解析器。并且可以对创建的抽象模板视图进行设置,如下代码注释,
public class AbstractTemplateViewResolver extends UrlBasedViewResolver {
private boolean exposeRequestAttributes = false;
private boolean allowRequestOverride = false;
private boolean exposeSessionAttributes = false;
private boolean allowSessionOverride = false;
private boolean exposeSpringMacroHelpers = true;
@Override
protected Class requiredViewClass() {
// 支持抽象模板视图类
return AbstractTemplateView.class;
}
@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
// 对抽象模板视图进行客户化的设置
AbstractTemplateView view = (AbstractTemplateView) super.buildView(viewName);
view.setExposeRequestAttributes(this.exposeRequestAttributes);
view.setAllowRequestOverride(this.allowRequestOverride);
view.setExposeSessionAttributes(this.exposeSessionAttributes);
view.setAllowSessionOverride(this.allowSessionOverride);
view.setExposeSpringMacroHelpers(this.exposeSpringMacroHelpers);
return view;
}
}
抽象的模板视图根据客户化的设置对请求进行一系列的预处理,然后,把HTTP请求传递给子类,子类根据具体的模板实现技术交给模板容器进行处理。
public abstract class AbstractTemplateView extends AbstractUrlBasedView {
public static final String SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE = "springMacroRequestContext";
private boolean exposeRequestAttributes = false;
private boolean allowRequestOverride = false;
private boolean exposeSessionAttributes = false;
private boolean allowSessionOverride = false;
private boolean exposeSpringMacroHelpers = true;
@Override
protected final void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (this.exposeRequestAttributes) {
// 导出请求属性到Spring模型中
for (Enumeration en = request.getAttributeNames(); en.hasMoreElements();) {
String attribute = (String) en.nextElement();
if (model.containsKey(attribute) && !this.allowRequestOverride) {
throw new ServletException("Cannot expose request attribute '" + attribute +
"' because of an existing model object of the same name");
}
Object attributeValue = request.getAttribute(attribute);
if (logger.isDebugEnabled()) {
logger.debug("Exposing request attribute '" + attribute +
"' with value [" + attributeValue + "] to model");
}
model.put(attribute, attributeValue);
}
}
if (this.exposeSessionAttributes) {
// 导出Session属性到Spring模型中
HttpSession session = request.getSession(false);
if (session != null) {
for (Enumeration en = session.getAttributeNames(); en.hasMoreElements();) {
String attribute = (String) en.nextElement();
if (model.containsKey(attribute) && !this.allowSessionOverride) {
throw new ServletException("Cannot expose session attribute '" + attribute +
"' because of an existing model object of the same name");
}
Object attributeValue = session.getAttribute(attribute);
if (logger.isDebugEnabled()) {
logger.debug("Exposing session attribute '" + attribute +
"' with value [" + attributeValue + "] to model");
}
model.put(attribute, attributeValue);
}
}
}
if (this.exposeSpringMacroHelpers) {
if (model.containsKey(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE)) {
throw new ServletException(
"Cannot expose bind macro helper '" + SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE +
"' because of an existing model object of the same name");
}
// Expose RequestContext instance for Spring macros.
model.put(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE,
new RequestContext(request, response, getServletContext(), model));
}
// 应用响应内容类型
applyContentType(response);
// 由子类实现使用模板技术进行处理
renderMergedTemplateModel(model, request, response);
}
protected void applyContentType(HttpServletResponse response) {
if (response.getContentType() == null) {
response.setContentType(getContentType());
}
}
protected abstract void renderMergedTemplateModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
抽象模板视图解析器和抽象模板视图有两个类型的子类,两个类型的子类各自支持Velocity和FreeMaker。
FreeMakerViewResolver的实现非常简单,它仅仅返回FreeMakerView类。而FreeMakerView则将HTTP请求传递给FreeMaker容器来处理并且响应产生展示层的界面。
VelocityViewResolver的实现非常简单,它仅仅返回VelocityView类。而VelocityView则将HTTP请求传递给Velocity容器来处理并且响应产生展示层的界面。
VelocityLayoutViewResolver和VelocityLayoutView是VelocityViewResolver和VelocityView的子类,它增加了对Layout的支持。
既然这些类的实现需要更多的FreeMaker和Velocity的知识,我们这里不做详细的代码分析和注释。
4.3.1.4 Jasper报表视图解析器和Jaspter报表视图
由于本人时间有限,将在本书第二版完成本节的内容。如您愿意提供帮助请发邮件到robertleepeak@gmail.com。
4.3.1.5 XSLT视图解析器和XSLT视图
XSLT视图解析器和XSLT视图使用XSLT技术将XML DOM数据转换成为一种支持的其他数据格式,这包括其他的XML格式,也包括类似HTML的展示层的数据显示格式。
XSLT视图解析器继承自基于URL的视图解析器,并且返回XSLT视图。如下代码注释,
public class XsltViewResolver extends UrlBasedViewResolver {
private String sourceKey;
private URIResolver uriResolver;
private ErrorListener errorListener;
private boolean indent = true;
private Properties outputProperties;
private boolean cacheTemplates = true;
@Override
protected Class requiredViewClass() {
// 返回XSLT视图类,父类负责创建此类实例
return XsltView.class;
}
@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
// 调用父类创建XSLT视图类的实例
XsltView view = (XsltView) super.buildView(viewName);
// 设置数据源的属性关键字
view.setSourceKey(this.sourceKey);
// 设置URI解析器
if (this.uriResolver != null) {
view.setUriResolver(this.uriResolver);
}
// 设置错误监听器
if (this.errorListener != null) {
view.setErrorListener(this.errorListener);
}
// 设置其他属性
view.setIndent(this.indent);
view.setOutputProperties(this.outputProperties);
view.setCacheTemplates(this.cacheTemplates);
return view;
}
}
XSLT视图则使用XSLT技术将一种XML格式的数据源转换成为另外一种,如下代码注释所示,
public class XsltView extends AbstractUrlBasedView {
// 可以配置一个非缺省的转换工厂类
private Class transformerFactoryClass;
private String sourceKey;
private URIResolver uriResolver;
private ErrorListener errorListener = new SimpleTransformErrorListener(logger);
private boolean indent = true;
private Properties outputProperties;
private boolean cacheTemplates = true;
private TransformerFactory transformerFactory;
private Templates cachedTemplates;
@Override
protected void initApplicationContext() throws BeansException {
// 创建转换工厂类的实例并且初始化
this.transformerFactory = newTransformerFactory(this.transformerFactoryClass);
this.transformerFactory.setErrorListener(this.errorListener);
if (this.uriResolver != null) {
this.transformerFactory.setURIResolver(this.uriResolver);
}
// 如果缓存模板,则初始化时加载所有模板
if (this.cacheTemplates) {
this.cachedTemplates = loadTemplates();
}
}
protected TransformerFactory newTransformerFactory(Class transformerFactoryClass) {
// 如果配置了非缺省的转换工厂类,则实例化配置的转换工厂类
if (transformerFactoryClass != null) {
try {
return (TransformerFactory) transformerFactoryClass.newInstance();
}
catch (Exception ex) {
throw new TransformerFactoryConfigurationError(ex, "Could not instantiate TransformerFactory");
}
}
// 否则使用缺省的转换工厂类
else {
return TransformerFactory.newInstance();
}
}
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
// 如果没有缓存XSLT模板,则加载模板
Templates templates = this.cachedTemplates;
if (templates == null) {
templates = loadTemplates();
}
// 根据模板穿件转换对象
Transformer transformer = createTransformer(templates);
// 配置转换对象
configureTransformer(model, response, transformer);
// 配置响应
configureResponse(model, response, transformer);
Source source = null;
try {
// 找到XSLT的数据源
source = locateSource(model);
if (source == null) {
throw new IllegalArgumentException("Unable to locate Source object in model: " + model);
}
// 进行事实的转换
transformer.transform(source, createResult(response));
}
finally {
closeSourceIfNecessary(source);
}
}
protected Result createResult(HttpServletResponse response) throws Exception {
// 使用响应的输出流构造数据结果
return new StreamResult(response.getOutputStream());
}
protected Source locateSource(Map<String, Object> model) throws Exception {
// 如果配置了数据源的关键字,则使用模型属性中以此关键字标记的属性
if (this.sourceKey != null) {
return convertSource(model.get(this.sourceKey));
}
// 否则找到第一个能够作为数据源类型的属性作为数据源
Object source = CollectionUtils.findValueOfType(model.values(), getSourceTypes());
return (source != null ? convertSource(source) : null);
}
protected Class[] getSourceTypes() {
// 这些类型都可以作为输入的数据源
return new Class[] {Source.class, Document.class, Node.class, Reader.class, InputStream.class, Resource.class};
}
protected Source convertSource(Object source) throws Exception {
// 需要把不同类型的数据源转换为XSLT的标准数据源类型
if (source instanceof Source) {
return (Source) source;
}
else if (source instanceof Document) {
return new DOMSource(((Document) source).getDocumentElement());
}
else if (source instanceof Node) {
return new DOMSource((Node) source);
}
else if (source instanceof Reader) {
return new StreamSource((Reader) source);
}
else if (source instanceof InputStream) {
return new StreamSource((InputStream) source);
}
else if (source instanceof Resource) {
Resource resource = (Resource) source;
return new StreamSource(resource.getInputStream(), resource.getURI().toASCIIString());
}
else {
throw new IllegalArgumentException("Value '" + source + "' cannot be converted to XSLT Source");
}
}
protected void configureTransformer(Map<String, Object> model, HttpServletResponse response, Transformer transformer) {
// 把模型参数拷贝到XSLT转换对象中
copyModelParameters(model, transformer);
// 拷贝输出属性
copyOutputProperties(transformer);
// 配置是否缩进
configureIndentation(transformer);
}
protected final void configureIndentation(Transformer transformer) {
// 打开或者关闭缩进
if (this.indent) {
TransformerUtils.enableIndenting(transformer);
}
else {
TransformerUtils.disableIndenting(transformer);
}
}
protected final void copyOutputProperties(Transformer transformer) {
// 拷贝输出属性
if (this.outputProperties != null) {
Enumeration en = this.outputProperties.propertyNames();
while (en.hasMoreElements()) {
String name = (String) en.nextElement();
transformer.setOutputProperty(name, this.outputProperties.getProperty(name));
}
}
}
protected final void copyModelParameters(Map<String, Object> model, Transformer transformer) {
// 把模型参数拷贝到XSLT转换对象中
for (Map.Entry<String, Object> entry : model.entrySet()) {
transformer.setParameter(entry.getKey(), entry.getValue());
}
}
protected void configureResponse(Map<String, Object> model, HttpServletResponse response, Transformer transformer) {
String contentType = getContentType();
String mediaType = transformer.getOutputProperty(OutputKeys.MEDIA_TYPE);
String encoding = transformer.getOutputProperty(OutputKeys.ENCODING);
if (StringUtils.hasText(mediaType)) {
contentType = mediaType;
}
if (StringUtils.hasText(encoding)) {
// Only apply encoding if content type is specified but does not contain charset clause already.
if (contentType != null && !contentType.toLowerCase().contains(WebUtils.CONTENT_TYPE_CHARSET_PREFIX)) {
contentType = contentType + WebUtils.CONTENT_TYPE_CHARSET_PREFIX + encoding;
}
}
// 根据XSLT模板支持的类型设置响应的内容类型
response.setContentType(contentType);
}
private Templates loadTemplates() throws ApplicationContextException {
// 取得模板的数据源
Source stylesheetSource = getStylesheetSource();
try {
// 根据模板的数据源创建模板对象
Templates templates = this.transformerFactory.newTemplates(stylesheetSource);
if (logger.isDebugEnabled()) {
logger.debug("Loading templates '" + templates + "'");
}
return templates;
}
catch (TransformerConfigurationException ex) {
throw new ApplicationContextException("Can't load stylesheet from '" + getUrl() + "'", ex);
}
finally {
closeSourceIfNecessary(stylesheetSource);
}
}
protected Transformer createTransformer(Templates templates) throws TransformerConfigurationException {
// 根据模板创建转换器
Transformer transformer = templates.newTransformer();
if (this.uriResolver != null) {
transformer.setURIResolver(this.uriResolver);
}
return transformer;
}
protected Source getStylesheetSource() {
// 根据请求的URL创建数据源
String url = getUrl();
if (logger.isDebugEnabled()) {
logger.debug("Loading XSLT stylesheet from '" + url + "'");
}
try {
Resource resource = getApplicationContext().getResource(url);
return new StreamSource(resource.getInputStream(), resource.getURI().toASCIIString());
}
catch (IOException ex) {
throw new ApplicationContextException("Can't load XSLT stylesheet from '" + url + "'", ex);
}
}
private void closeSourceIfNecessary(Source source) {
// 关闭一个流类型的数据源
if (source instanceof StreamSource) {
StreamSource streamSource = (StreamSource) source;
if (streamSource.getReader() != null) {
try {
streamSource.getReader().close();
}
catch (IOException ex) {
// ignore
}
}
if (streamSource.getInputStream() != null) {
try {
streamSource.getInputStream().close();
}
catch (IOException ex) {
// ignore
}
}
}
}
}