Spring MVC视图的呈现

来源:互联网 发布:荧光作图软件下载 编辑:程序博客网 时间:2024/05/16 17:38
  之前面试被问到两次的问题,其中一次还是网龙面试时被问到的。对于这个问题,其实答案很简单,往下看前可以先思考一下。本文会先给出简洁明了的答案,具体的分析根据兴趣选择阅读。     
先简单的描述一下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。



0 0
原创粉丝点击