文章目录
  1. 1. springmvc视图解析与渲染
    1. 1.1. springmvc处理步骤
    2. 1.2. 视图解析与渲染(render方法)
    3. 1.3. 视图解析:通过视图解析器进行视图的解析
    4. 1.4. 视图渲染
      1. 1.4.1. View接口
      2. 1.4.2. View接口的实现类
      3. 1.4.3. 具体渲染的一个过程
      4. 1.4.4. DispatcherServlet做最后处理
    5. 1.5. 总结

springmvc视图解析与渲染

springmvc处理步骤

具体步骤:

第一步:发起请求到前端控制器(DispatcherServlet)

第二步:前端控制器请求HandlerMapping查找 Handler (可以根据xml配置、注解进行查找)

第三步:处理器映射器HandlerMapping向前端控制器返回Handler,HandlerMapping会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象,多个HandlerInterceptor拦截器对象),通过这种策略模式,很容易添加新的映射策略

第四步:前端控制器调用处理器适配器去执行Handler

第五步:处理器适配器HandlerAdapter将会根据适配的结果去执行Handler

第六步:Handler执行完成给适配器返回ModelAndView

第七步:处理器适配器向前端控制器返回ModelAndView (ModelAndView是springmvc框架的一个底层对象,包括 Model和view)

第八步:前端控制器请求视图解析器去进行视图解析 (根据逻辑视图名解析成真正的视图(jsp)),通过这种策略很容易更换其他视图技术,只需要更改视图解析器即可

第九步:视图解析器向前端控制器返回View

第十步:前端控制器进行视图渲染 (视图渲染将模型数据(在ModelAndView对象中)填充到request域)

第十一步:前端控制器向用户响应结果

视图解析与渲染(render方法)

org.springframework.web.servlet.DispatcherServlet#doDispatch方法中

1
2
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//945行返回了 ModelAndView 对象
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);// 959行进行的就是返回值处理问题

org.springframework.web.servlet.DispatcherServlet#processDispatchResult方法中

1
render(mv, request, response); //1012进行视图的渲染(包含视图解析)

org.springframework.web.servlet.DispatcherServlet#render 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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 != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);

View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, 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() + "'");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
}

view = resolveViewName(viewName, mv.getModelInternal(), locale, request);视图解析,返回视图view

view.render(mv.getModelInternal(), request, response); 视图渲染

视图解析:通过视图解析器进行视图的解析

1.解析一个视图名到一个视图对象,具体解析的过程是:在容器中查找所有配置好的视图解析器(List类型),然后进行遍历,

只要有一个视图解析器能解析出视图就返回 View 对象,若遍历完成后都不能解析出视图,那么返回 null。

具体来看:

org.springframework.web.servlet.DispatcherServlet#resolveViewName

1
2
3
4
5
6
7
8
9
10
11
12
13
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {

if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}

解析一个视图名到一个视图对象,具体解析的过程是:在容器中查找所有配置好的视图解析器(List类型),然后进行遍历,只要有一个视图解析器能解析出视图就返回 View 对象,若遍历完成后都不能解析出视图,那么返回 null。

创建视图,以 InternalResourceViewResolver为例,继承UrlBasedViewResolver

org.springframework.web.servlet.view.UrlBasedViewResolver#createView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@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());
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
String[] hosts = getRedirectHosts();
if (hosts != null) {
view.setHosts(hosts);
}
return applyLifecycleMethods(viewName, view);
}
// 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);
}

在创建视图前会检查返回值是否是以:”redirect:” 或 “forward:” 开头的。

如果是重定向:则创建一个重定向视图,返回创建的视图。如果是转发:则返回通过 转发 url 创建的 InternalResourceView 视图。

super.createView(viewName, locale),调用父类创建视图

org.springframework.web.servlet.view.UrlBasedViewResolver#loadView

1
2
3
protected View createView(String viewName, Locale locale) throws Exception {
return loadView(viewName, locale);
}

调用具体的 InternalResourceViewResolver ,然后又调用 父类的 buildView() 方法

org.springframework.web.servlet.view.UrlBasedViewResolver#buildView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
Class<?> viewClass = getViewClass();
Assert.state(viewClass != null, "No view class");

AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
view.setUrl(getPrefix() + viewName + getSuffix());

String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}

view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());

Boolean exposePathVariables = getExposePathVariables();
if (exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
if (exposeContextBeansAsAttributes != null) {
view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
}
String[] exposedContextBeanNames = getExposedContextBeanNames();
if (exposedContextBeanNames != null) {
view.setExposedContextBeanNames(exposedContextBeanNames);
}

return view;
}

可以看出:是通过 BeanUtils.instantiateClass(getViewClass()) 来创建 View 对象的。这个例子与其说是 InternalResourceViewResolver ,倒不如说是 UrlBasedViewResolver 类型的例子。

从这里也可以看出:该类型最终要到的目标URL为:getPrefix() + viewName + getSuffix()

视图渲染

View接口

官方文档:

1
`* MVC View for a web interaction. Implementations are responsible for rendering* content, and exposing the model. A single view exposes multiple model attributes.** <p>This class and the MVC approach associated with it is discussed in Chapter 12 of* <a href="http://www.amazon.com/exec/obidos/tg/detail/-/0764543857/">Expert One-On-One J2EE Design and Development</a>* by Rod Johnson (Wrox, 2002).** <p>View implementations may differ widely. An obvious implementation would be* JSP-based. Other implementations might be XSLT-based, or use an HTML generation library.* This interface is designed to avoid restricting the range of possible implementations.** <p>Views should be beans. They are likely to be instantiated as beans by a ViewResolver.* As this interface is stateless, view implementations should be thread-safe.`

说明:

SpringMVC 对一个 web 来说是相互作用的(不太明白)。View 的实现类是负责呈现内容的,并且 exposes(暴露、揭露、揭发的意思,这里就按暴露解释吧,想不出合适的词语) 模型的。

一个单一的视图可以包含多个模型。

View 的实现可能有很大的不同。一个明显的实现是基于 JSP 的。其他的实现可能是基于 XSLT 的,或者是一个 HTML 生成库。

设计这个接口是为了避免约束可能实现的范围(这里是不是说,我们可以通过实现该接口来自定义扩展自定义视图?)。

所有的视图都应该是一个 Bean 类。他们可能被 ViewResolver 当做一个 bean 进行实例化。

由于这个接口是无状态的,View 的所有实现类应该是线程安全的。

View接口的实现类

IDEA中crtl+H可查看View接口所有的实现类

具体渲染的一个过程

​ 举例:View类型为JstlView

继承关系:

public class JstlView extends InternalResourceView

​ public class InternalResourceView extends AbstractUrlBasedView

​ public abstract class AbstractUrlBasedView extends AbstractView implements InitializingBean

​ public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware

通过org.springframework.web.servlet.DispatcherServlet#render 方法中的渲染view.render(mv.getModelInternal(), request, response);

进入到org.springframework.web.servlet.view.AbstractView#render

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void render(@Nullable 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, getRequestToExpose(request), response);
}

renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);

这里只看 org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel 这个方法

renderMergedOutputModel(渲染合并输出模型)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);

// Expose helpers as request attributes, if any.
exposeHelpers(request);

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

else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(request, response);
}
}

RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);//创建RequestDispatcher

rd.forward(request, response); //转发请求

可以看到前面的几个步骤都是为 RequestDispatch 做准备,装填数据。最后,到目标页面是通过转发。

DispatcherServlet做最后处理

DispatcherServlet继承FrameworkServlet

FrameworkServlet重写service()方法,(也重写了doGet,doPost方法等)

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}

1)service()方法或者super.service()方法,以及super.service()方法调用的doGet(),doPost()等等都调用processRequest()方法

2)processRequest()方法,processRequest()调用doService()方法

3)doService()调用doDispatch(request, response),

4)doDispatch(request, response)调用processDispatchResult(),

5)processDispatchResult方法调用render()方法,虽然在render()方法中请求已经转发,但是后面还做了一些异常处理等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {

boolean errorView = false;

if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}

// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}

if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}

if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}

总结

介绍了 SpringMVC 视图解析和视图渲染问题是如何解决的。SpringMVC 为逻辑视图提供了多种视图解析策略,可以在配置文件中配置多个视图的解析策略。并制定其先后顺序。

这里所说的视图解析策略,就是指视图解析器。视图解析器会将逻辑视图名解析为一个具体的视图对象。再说视图渲染的过程,视图对模型进行了渲染,最终将模型的数据以某种形式呈现给用户。

参考:https://www.cnblogs.com/solverpeng/p/5743609.html

文章目录
  1. 1. springmvc视图解析与渲染
    1. 1.1. springmvc处理步骤
    2. 1.2. 视图解析与渲染(render方法)
    3. 1.3. 视图解析:通过视图解析器进行视图的解析
    4. 1.4. 视图渲染
      1. 1.4.1. View接口
      2. 1.4.2. View接口的实现类
      3. 1.4.3. 具体渲染的一个过程
      4. 1.4.4. DispatcherServlet做最后处理
    5. 1.5. 总结