视图解析器的使用
目前web应用都是使用前后端分离的开发方式,在这种方式下,其实不会用到springmvc的视图解析器。
官网上有这么一段话:
An appropriate handler is searched for. If a handler is found, the execution chain associated with the handler (preprocessors, postprocessors, and controllers) is run to prepare a model for rendering. Alternatively, for annotated controllers, the response can be rendered (within the HandlerAdapter) instead of returning a view.
If a model is returned, the view is rendered. If no model is returned (maybe due to a preprocessor or postprocessor intercepting the request, perhaps for security reasons), no view is rendered, because the request could already have been fulfilled.
大致意思是,DispatcherServlet实例会查找一个handler,然后构造一个执行链,然后会执行这个执行链来准备model,以备进行渲染,并且返回一个视图供后续处理。但是如果带注解的controller,响应会在处理器适配器中进行渲染,而不是返回一个view
也就是说,如果我们要研究视图解析器,我们不能使用带注解的controller
1 使用视图解析器的场景
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- <context:component-scan base-package="com.szj"/>--> <!-- <mvc:annotation-driven />--> </beans>
注释掉上面两行
dispatcher-servlet.xml
<bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/jsp/"/><!--设置JSP文件的目录位置--> <property name="suffix" value=".jsp"/> </bean> <bean id="/hello" class="com.szj.controller.JspController" />
JspController.java
public class JspController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { //ModelAndView 模型和视图 ModelAndView mv=new ModelAndView(); //封装对象,放在ModelAndView中 mv.addObject("msg", "Hello!SpringMVC!~~"); //封装要跳转的视图,放在ModelAndView中。 mv.setViewName("hello"); /jsp/hello.jsp return mv; } }
hello.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> ${msg} </body> </html>
然后我们请求/hello这个链接
我们知道,请求会首先到达DispatcherServlet的如下方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; ModelAndView mv = null; Exception dispatchException = null; mappedHandler = getHandler(processedRequest); HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); mappedHandler.applyPreHandle(processedRequest, response) // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); }
首先会获取一个处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
此时的处理器适配器是SimpleControllerHandlerAdapter实例。
接下来,处理器适配器将处理请求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
执行的是SimpleControllerHandlerAdapter的handle方法
public class SimpleControllerHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return (handler instanceof Controller); } @Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception { return ((Controller) handler).handleRequest(request, response); } @Override public long getLastModified(HttpServletRequest request, Object handler) { if (handler instanceof LastModified) { return ((LastModified) handler).getLastModified(request); } return -1L; } }
((Controller) handler).handleRequest(request, response); 这里强制转为对应的Controller类,然后调用的是JspController的如下方法,该方法是Controller接口的重写方法。
public class JspController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { //ModelAndView 模型和视图 ModelAndView mv=new ModelAndView(); //封装对象,放在ModelAndView中 mv.addObject("msg", "Hello!SpringMVC!~~"); //封装要跳转的视图,放在ModelAndView中。 mv.setViewName("hello"); //WEB-INF/jsp/hello.jsp return mv; } }
再回到DispatcherServlet的doDispatch方法,
处理器适配器调用执行链,最终返回一个ModelAndView对象mv,然后执行如下代码
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
其代码为
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { if (mv != null) { render(mv, request, response); } }
如果mv为空,将直接返回,带注解的Controller就属于这种情况。
这里mv不为空,将调用 render(mv, request, response)
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { View view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); view.render(mv.getModelInternal(), request, response); }
这里首先生成一个View对象,然后调用 view.render 方法
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); }
接着调用如下方法
protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine the path for the request dispatcher. String dispatcherPath = prepareForRendering(request, response); // Obtain a RequestDispatcher for the target resource (typically a JSP). RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); rd.forward(request, response); }
这里的RequestDispatcher对象rd是Tomcat中的对象,至此,springmvc处理阶段结束,Tomcat将会继续处理这个请求并且把jsp响应给客户端显式。
注意:ViewResolver负责视图名和视图之间的映射(比如视图名hello对应的视图是/WEB-INF/hello.jsp),View负责准备请求,并调用view.render()将渲染交由特定的视图技术实现。Servlet容器默认支持JSP视图技术,所以这里我们可以看到,调用view.render()后,其实视图渲染是交给Servlet容器进行的,我们看不到具体的渲染过程。如果这里我们使用的是别的Servlet容器默认不支持的视图技术,情况将不同,我们可以看到视图被渲染的全过程。比如FreeMarkerView,执行render方法后,最终调用的是如下方法:
protected void doRender(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose all standard FreeMarker hash models. SimpleHash fmModel = buildTemplateModel(model, request, response); // Grab the locale-specific version of the template. Locale locale = RequestContextUtils.getLocale(request); processTemplate(getTemplate(locale), fmModel, response); }
在这个方法中,我们可以看到它获取模板,获取数据,并渲染。
2 不使用视图解析器的场景
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <context:component-scan base-package="com.szj"/> <mvc:annotation-driven /> </beans>
去掉注释
新增一个Controller
@Controller @RequestMapping("/index") public class MainController { @RequestMapping (value = "/main.json",method = RequestMethod.GET) @ResponseBody public Object main(){ JSONObject json = new JSONObject(); Map map = new HashMap(); map.put("age",28); map.put("sex",1); map.put("name","szj"); json.put("value","key"); json.put("age",12); json.put("list",map); String pretty = JSON.toJSONString(json, SerializerFeature.PrettyFormat, SerializerFeature.WriteDateUseDateFormat); System.out.println(pretty); return pretty; } }
其余都和上例一样
我们访问http://localhost:8080/springmvc_demo_war_exploded/index/main.json
同样请求到达DispatcherServlet的如下方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; ModelAndView mv = null; Exception dispatchException = null; mappedHandler = getHandler(processedRequest); HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); mappedHandler.applyPreHandle(processedRequest, response) // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); }
和上面不同,此时的处理器适配器是RequestMappingHandlerAdapter实例。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 执行的是RequestMappingHandlerAdapter的如下方法
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; mav = invokeHandlerMethod(request, response, handlerMethod); return mav; }
然后调用如下方法
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { invocableMethod.invokeAndHandle(webRequest, mavContainer); return getModelAndView(mavContainer, modelFactory, webRequest); }
其中 invocableMethod.invokeAndHandle(webRequest, mavContainer); 最终调用的是我们写的Controller,且在这个适配器里面直接处理响应,并且设置mavContainer的requestHandled=true表示请求已处理。
然后执行 return getModelAndView(mavContainer, modelFactory, webRequest);
如下是这个方法的定义
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { modelFactory.updateModel(webRequest, mavContainer); if (mavContainer.isRequestHandled()) { return null; } ...省略 }
这里判断 mavContainer.isRequestHandled() 是否已经处理过了,这里为true,返回null
我们再返回DispatcherServlet的doDispatch方法
继续执行
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
这里的mv是null
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { if (mv != null && !mv.wasCleared()) { render(mv, request, response); } }
由于mv是null,将不会执行render方法,直接返回。请求结束,没有使用视图解析器。