以前,我们详细介绍了Spring的Controller技术。Spring的面向接口编程,使Controller的实现多种多样。View技术也一样。今天的分析先从在Controller中的ModelAndView开始。
public class ModelAndView {
private Object view; //View实例或者view的字符串
/** Model Map */
private ModelMap model; //model
/* * Convenient constructor when there is no model data to expose. * Can also be used in conjunction with <code>addObject</code>.
* @param view View object to render
* @see #addObject */
public ModelAndView(View view) {
this.view = view;
}
public ModelAndView(String viewName){
this.view = viewName;
}
可以看到view实例可以指向一个View对象或者字符串。现在先看看View接口:
public interface View {
/**
* Return the content type of the view, if predetermined.
* <p>Can be used to check the content type upfront,
* before the actual rendering process.
* @return the content type String (optionally including a character set),
* or <code>null</code> if not predetermined.
*/
String getContentType();
/**
* 绘制视图
* 绘制视图的第一步是准备请求: 如果是JSP的视图技术
* 首先会把model设为request的属性。
* 第二步则是真正的绘制视图
* @param model Map with name Strings as keys and corresponding model
* objects as values (Map can also be <code>null</code> in case of empty model)
* @param request current HTTP request
* @param response HTTP response we are building
* @throws Exception if rendering failed
*/
void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
在这之后,我们再来看看View的实现继承类图,可以看到Spring支持多种类型视图:
org.springframework.web.servlet.view.AbstractView (implements org.springframework.beans.factory.BeanNameAware, org.springframework.web.servlet.View)
- org.springframework.web.servlet.view.document.AbstractExcelView
- org.springframework.web.servlet.view.document.AbstractJExcelView
- org.springframework.web.servlet.view.document.AbstractPdfView
- org.springframework.web.servlet.view.AbstractUrlBasedView (implements org.springframework.beans.factory.InitializingBean)
- org.springframework.web.servlet.view.jasperreports.AbstractJasperReportsView
- org.springframework.web.servlet.view.jasperreports.AbstractJasperReportsSingleFormatView
- org.springframework.web.servlet.view.jasperreports.ConfigurableJasperReportsView
- org.springframework.web.servlet.view.jasperreports.JasperReportsCsvView
- org.springframework.web.servlet.view.jasperreports.JasperReportsHtmlView
- org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView
- org.springframework.web.servlet.view.jasperreports.JasperReportsXlsView
- org.springframework.web.servlet.view.jasperreports.JasperReportsMultiFormatView
- org.springframework.web.servlet.view.document.AbstractPdfStamperView
- org.springframework.web.servlet.view.AbstractTemplateView
- org.springframework.web.servlet.view.freemarker.FreeMarkerView
- org.springframework.web.servlet.view.velocity.VelocityView
- org.springframework.web.servlet.view.velocity.VelocityToolboxView
- org.springframework.web.servlet.view.velocity.VelocityLayoutView
- org.springframework.web.servlet.view.InternalResourceView
- org.springframework.web.servlet.view.JstlView
- org.springframework.web.servlet.view.tiles.TilesView
- org.springframework.web.servlet.view.tiles.TilesJstlView
- org.springframework.web.servlet.view.RedirectView
- org.springframework.web.servlet.view.tiles2.TilesView
- org.springframework.web.servlet.view.xslt.XsltView
- org.springframework.web.servlet.view.xslt.AbstractXsltView
和Controller一样,View的第一个实现也是AbstractView。所以先让我们看看AbstractView对render函数的实现:
public void render(Map 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 mergedModel = new HashMap(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, mergedModel));
}
prepareResponse(request, response);
renderMergedOutputModel(mergedModel, request, response);
}
第一步,将静态属性和model的属性都添加到mergedModel里面。如果需要请求上下文,则将请求上下文添加到model中。
静态属性是继承AbstractView进行设置的属性。而请求上下文如果设置的名字就会创建一个request上下文。在requestContext中定义了一些包括本地化和主题的处理工具。
第二步,对响应进行预处理。最后调用子类需要实现的函数renderMergedOutputModel。
对PDF和EXCEL格式我们暂且不管,且Spring支持多种视图技术,这里我们主要关注JSTL技术,
接着我们来看AbstractUrlBasedView 类。在AbstractUrlBasedView 只定义了一个url属性。别的没有什么特殊处理。
接着继承AbstractUrlBasedView 的是InternalResourceView。他对renderMergedOutputModel进行实现,实现如下:
/**
* Render the internal resource given the specified model.
* This includes setting the model as request attributes.
*/
protected void renderMergedOutputModel(
Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 获取要暴露的request,一般都是传入的参数request
HttpServletRequest requestToExpose = getRequestToExpose(request);
// 将model的数据添加到request属性中
exposeModelAsRequestAttributes(model, requestToExpose);
// 设置helper,如果存在的话
exposeHelpers(requestToExpose);
// 对绘制进行预处理,从而获得到要分发的url
String dispatcherPath = prepareForRendering(requestToExpose, response);
// 获取请求分发对象
RequestDispatcher rd = requestToExpose.getRequestDispatcher(dispatcherPath);
if (rd == null) {
throw new ServletException(
"Could not get RequestDispatcher for [" + getUrl() + "]: check that this file exists within your WAR");
}
// 决定使用RequestDispatcher的include方法还是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);
}
}
可以看到InternalResourceView对请求进行了转发。转发到url上。最后我们看看JSTLView的实现:
public class JstlView extends InternalResourceView {
private MessageSource messageSource;
public JstlView() {
}
public JstlView(String url) {
super(url);
}
public JstlView(String url, MessageSource messageSource) {
this(url);
this.messageSource = messageSource;
}
protected void initServletContext(ServletContext servletContext) {
if (this.messageSource != null) {
this.messageSource = JstlUtils.getJstlAwareMessageSource(servletContext, this.messageSource);
}
super.initServletContext(servletContext);
}
protected void exposeHelpers(HttpServletRequest request) throws Exception {
if (this.messageSource != null) {
JstlUtils.exposeLocalizationContext(request, this.messageSource);
}
else {
JstlUtils.exposeLocalizationContext(new RequestContext(request, getServletContext()));
}
}
}
在InternalResourceView 中,基本上所有的处理都差不多了。在JSTLView对两个方法进行了覆盖。第一个initServletContext,主要初始化了MessageResource
第二个exposeHelpers将messageSource放在了request里面。
这样view的解析就结束了。接下来容器对jsp进行解析,并进行tag等的处理。然后将生成的页面返回给客户端。