Spring MVC视图的呈现

1 篇文章 0 订阅
  之前面试被问到两次的问题,其中一次还是网龙面试时被问到的。对于这个问题,其实答案很简单,往下看前可以先思考一下。本文会先给出简洁明了的答案,具体的分析根据兴趣选择阅读。      
先简单的描述一下spring mvc的工作过程:客户端请求发送到前端控制器(DispatchServlet),由前端控制器通过用户配置HandlerMapping将请求映射到业务处理器(Controller)处理业务,并且返回ModelAndView对象,再由ViewResolver解析视图名称为具体的View,由View对传进来的Model进行渲染,返回信息到客户端。

     面试题:前端控制器如何渲染JSP返回给客户端?

     答案分割线---------------------------------------------
     众所周知的是JSP会被编译成Servlet运行,这个是解这道题的基础。前端控制器得到View对象和Model对象后,调用View对象的render方法进行渲染,将Model(业务处理器处理的结果信息)设值到Request对象上,调用请求分发器(RequestDispatch)进行分发(forward)到JSP。

     具体分析分割线----------------------------------------

     DispatchServlet根据ViewResolver将视图名称解析为具体的视图对象View。DispatchServlet.render()方法代码:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
		// Determine locale for request and apply it to the response.
		Locale locale = this.localeResolver.resolveLocale(request);
		response.setLocale(locale);

		View view;
		if (mv.isReference()) {
			// We need to resolve the view name.
			view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
			if (view == null) {
				throw new ServletException(
						"Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" +
								getServletName() + "'");
			}
		}
		else {
			// 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.isDebugEnabled()) {
			logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
		}
		view.render(mv.getModelInternal(), request, response);
	}

 
 
 
 以上过程是DispatchServlet通过解析得到view对象,并通过调用view对象的render方法来完成数据的显示过程。接下来来了解一下如何通过解析视图的逻辑名得到视图对象的。 
 
	protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
			HttpServletRequest request) throws Exception {

		for (ViewResolver viewResolver : this.viewResolvers) {
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				return view;
			}
		}
		return null;
	}
 
解析过程就是调用viewResolver进行解析,看一下常见的BeanNameViewResolver:
public View resolveViewName(String viewName, Locale locale) throws BeansException {
		ApplicationContext context = getApplicationContext();
		if (!context.containsBean(viewName)) {
			// Allow for ViewResolver chaining.
			return null;
		}
		return context.getBean(viewName, View.class);
	}
首先取得当前IOC容器,然后判断在IOC容器是否包含有指导名称的Bean,如果有则通过getBean获取。
ViewResolver还有其他的实现,比如AbstractCachingViewResolver,处理过程会对view对象进行缓存。
	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 = UNRESOLVED_VIEW;
						}
						if (view != null) {
							this.viewAccessCache.put(cacheKey, view);
							this.viewCreationCache.put(cacheKey, view);
							if (logger.isTraceEnabled()) {
								logger.trace("Cached view [" + cacheKey + "]");
							}
						}
					}
				}
			}
			return (view != UNRESOLVED_VIEW ? view : null);
		}
	}
createView是一个模板方法,具体的实现有子类完成。
回到主题JSP视图的实现,Spring mvc中使用JstlView作为view对象。render方法在其基类AbstractView中实现。
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);
		}

		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);

		prepareResponse(request, response);
		renderMergedOutputModel(mergedModel, request, response);
	}
将信息都放入到mergedModel中,如何在prepareResponse方法中设置Response的一些基本参数。最后调用renderMergedOutputModel方法,这个方法在InternalResourceView中找到。

protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		HttpServletRequest requestToExpose = getRequestToExpose(request);

		exposeModelAsRequestAttributes(model, requestToExpose);

		exposeHelpers(requestToExpose);

		String dispatcherPath = prepareForRendering(requestToExpose, response);

		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 (useInclude(requestToExpose, response)) {
			response.setContentType(getContentType());
			if (logger.isDebugEnabled()) {
				logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
			}
			rd.include(requestToExpose, response);
		}

		else {
			exposeForwardRequestAttributes(requestToExpose);
			if (logger.isDebugEnabled()) {
				logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
			}
			rd.forward(requestToExpose, response);
		}
	}

这个方法做了一些前置处理后,获得请求分发器,然后判断是否是使用include,如果不是则使用forward跳转到JSP。



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值