SpringMVC源码分析(8)剖析ViewResolver
View视图 其实就是对应MVC中的"V"
1.ViewResolver 结构图
2.BeanNameViewResolver
通过把返回的逻辑视图名称去匹配定义好的视图bean对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Test public void testBeanNameViewResolver() throws ServletException {
StaticWebApplicationContext wac = new StaticWebApplicationContext();
wac.setServletContext( new MockServletContext());
MutablePropertyValues pvs1 = new MutablePropertyValues();
pvs1.addPropertyValue( new PropertyValue( "url" , "/example1.jsp" ));
wac.registerSingleton( "example1" , InternalResourceView. class , pvs1);
BeanNameViewResolver vr = new BeanNameViewResolver();
vr.setApplicationContext(wac);
wac.refresh();
View view = vr.resolveViewName( "example1" , Locale.getDefault());
assertEquals( "Correct view class" , InternalResourceView. class , view.getClass());
assertEquals( "Correct URL" , "/example1.jsp" , ((InternalResourceView) view).getUrl());
} |
3.XmlViewResolver
XmlViewResolver这个视图解析器跟 BeanNameViewResolver 有点类似,也是通过把返回的逻辑视图名称去匹配定义好的视图 bean 对象。
3.1 配置XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<? xml version = "1.0" encoding = "UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> < beans >
< bean id = "example1" class = "org.springframework.web.servlet.view.ViewResolverTests$TestView" >
< property name = "url" >< value >/example1.jsp</ value ></ property >
< property name = "attributesMap" >
< map >
< entry key = "test1" >< value >testvalue1</ value ></ entry >
< entry key = "test2" >< ref bean = "testBean" /></ entry >
</ map >
</ property >
< property name = "location" >< value >test</ value ></ property >
</ bean >
</ beans >
|
3.2 测试用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Test public void testXmlViewResolver() throws Exception {
StaticWebApplicationContext wac = new StaticWebApplicationContext();
wac.registerSingleton( "testBean" , TestBean. class );
wac.setServletContext( new MockServletContext());
wac.refresh();
TestBean testBean = (TestBean) wac.getBean( "testBean" );
XmlViewResolver vr = new XmlViewResolver();
vr.setLocation( new ClassPathResource( "org/springframework/web/servlet/view/views.xml" ));
vr.setApplicationContext(wac);
View view1 = vr.resolveViewName( "example1" , Locale.getDefault());
assertTrue( "Correct view class" , TestView. class .equals(view1.getClass()));
assertTrue( "Correct URL" , "/example1.jsp" .equals(((InternalResourceView) view1).getUrl()));
|
1. BeanNameViewResolver 要求视图 bean 对象都定义在 Spring 的 application context 中,而 XmlViewResolver 是在指定的配置文件中寻找视图 bean 对象,
2. XmlViewResolver是 AbstractCachingViewResolver的子类,支持缓存;
BeanNameViewResolver 不会进行视图缓存。
4. ResourceBundleViewResolver
和 XmlViewResolver 一样它也需要有一个配置文件来定义逻辑视图名称和真正的 View 对象的对应关系,不同的是 ResourceBundleViewResolver 的配置文件是一个属性文件,而且必须是放在 classpath 路径下面的,默认情况下这个配置文件是在 classpath 根目录下的 views.properties 文件,如果不使用默认值的话,则可以通过属性 baseName 或 baseNames 来指定。
4.1 配置文件testviews_fr.properties
1
2
3
|
debugView.(class)= org.springframework.web.servlet.view.InternalResourceView debugView.url=jsp/debug/deboug.jsp debugView.contentType=text/xml;charset=ISO-8859-1 |
4.2 测试用例
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
|
public class ResourceBundleViewResolverTests extends TestCase {
/** Comes from this package */ private static String PROPS_FILE = "org.springframework.web.servlet.view.testviews" ;
private ResourceBundleViewResolver rb;
private StaticWebApplicationContext wac;
protected void setUp() throws Exception {
rb = new ResourceBundleViewResolver();
rb.setBasename(PROPS_FILE);
rb.setCache(getCache());
rb.setDefaultParentView( "testParent" );
wac = new StaticWebApplicationContext();
wac.setServletContext( new MockServletContext());
wac.refresh();
// This will be propagated to views, so we need it.
rb.setApplicationContext(wac);
}
public void testDebugViewFrench() throws Exception {
View v = rb.resolveViewName( "debugView" , Locale.FRENCH);
assertTrue( "French debugView must be of type InternalResourceView" , v instanceof InternalResourceView);
InternalResourceView jv = (InternalResourceView) v;
assertTrue( "French debugView must have correct URL" , "jsp/debug/deboug.jsp" .equals(jv.getUrl()));
assertTrue(
"Correct overridden (XML) content type, not '" + jv.getContentType() + "'" ,
jv.getContentType().equals( "text/xml;charset=ISO-8859-1" ));
} } |
在ResourceBundleViewResolver第一次进行视图解析的时候会先new一个BeanFactory对象,然后把properties文件中定义好的属性按照它自身的规则生成一个个的bean对象注册到该BeanFactory中,之后会把该BeanFactory对象保存起来,所以ResourceBundleViewResolver缓存的是BeanFactory,而不是直接的缓存从BeanFactory中取出的视图bean。然后会从bean工厂中取出名称为逻辑视图名称的视图bean进行返回。接下来就讲讲Spring通过properties文件生成bean的规则。它会把properties文件中定义的属性名称按最后一个点“.”进行分割,把点前面的内容当做是bean名称,点后面的内容当做是bean的属性。这其中有几个特别的属性,Spring把它们用小括号包起来了,这些特殊的属性一般是对应的attribute,但不是bean对象所有的attribute都可以这样用。其中(class)是一个,除了(class)之外,还有(scope)、(parent)、(abstract)、(lazy-init)。而除了这些特殊的属性之外的其他属性,Spring会把它们当做bean对象的一般属性进行处理,就是bean对象对应的property。所以根据上面的属性配置文件将生成如下两个bean对象:
from http://elim.iteye.com/blog/1770554
5.UrlBasedViewResolver
它是对ViewResolver的一种简单实现,而且继承了AbstractCachingViewResolver,主要就是提供的一种拼接URL的方式来解析视图,它可以让我们通过prefix属性指定一个指定的前缀,通过suffix属性指定一个指定的后缀,然后把返回的逻辑视图名称加上指定的前缀和后缀就是指定的视图URL了。
5.2 重要属性
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
|
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
/**
* Prefix for special view names that specify a redirect URL (usually
* to a controller after a form has been submitted and processed).
* Such view names will not be resolved in the configured default
* way but rather be treated as special shortcut.
*/
public static final String REDIRECT_URL_PREFIX = "redirect:" ;
/**
* Prefix for special view names that specify a forward URL (usually
* to a controller after a form has been submitted and processed).
* Such view names will not be resolved in the configured default
* way but rather be treated as special shortcut.
*/
public static final String FORWARD_URL_PREFIX = "forward:" ;
//Set the view class that should be used to create views.
private Class viewClass;
// Set the prefix that gets prepended to view names when building a URL. private String prefix = "" ;
//the suffix that gets appended to view names when building a URL.
private String suffix = "" ;
//view names; such that 'my*', '*Report' and '*Repo*'
private String[] viewNames = null ;
//content type for all views.
private String contentType;
//UrlBasedViewResolver 的 redirectContextRelative 的默认值为 true,
//这意味着,只要重定向的资源以/开头,那么 spring 会帮你添加 contextPath
private boolean redirectContextRelative = true ;
//whether redirects should stay compatible with HTTP 1.0 clients private boolean redirectHttp10Compatible = true ;
//the name of the RequestContext attribute for all views
private String requestContextAttribute;
private int order = Integer.MAX_VALUE;
/** Map of static attributes, keyed by attribute name (String) */
private final Map<String, Object> staticAttributes = new HashMap<String, Object>();
...
}
|
5.3 createView方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public static final String REDIRECT_URL_PREFIX = "redirect:" ;
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());
return new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
}
// 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);
} |
URLBasedViewResolver发现返回的视图名称包含”redirect:”前缀,于是把返回的视图名称前缀”redirect:”去掉,取后面的test.do组成一个RedirectView,RedirectView中将把请求返回的模型属性组合成查询参数的形式组合到redirect的URL后面,然后调用HttpServletResponse对象的sendRedirect方法进行重定向。同样URLBasedViewResolver还支持forword:前缀,对于视图名称中包含forword:前缀的视图名称将会被封装成一个InternalResourceView对象,然后在服务器端利用RequestDispatcher的forword方式跳转到指定的地址。使用UrlBasedViewResolver的时候必须指定属性viewClass,表示解析成哪种视图,一般使用较多的就是InternalResourceView,利用它来展现jsp,但是当我们使用JSTL的时候我们必须使用JstlView。
5.4 一段UrlBasedViewResolver的定义
1
2
3
4
5
6
|
< bean class = "org.springframework.web.servlet.view.UrlBasedViewResolver" >
< property name = "prefix" value = "/WEB-INF/" />
< property name = "suffix" value = ".jsp" />
< property name = "viewClass" value = "org.springframework.web.servlet.view.InternalResourceView" />
</ bean >
|
6.InternalResourceViewResolver 内部资源视图解析器
InternalResourceViewResolver会把返回的视图名称都解析为InternalResourceView对象,InternalResourceView会把Controller处理器方法返回的模型属性都存放到对应的request属性中,然后通过RequestDispatcher在服务器端把请求forword重定向到目标URL。
6.1最熟悉的一段配置
1
2
3
4
|
< bean class = "org.springframework.web.servlet.view.InternalResourceViewResolver" >
< property name = "prefix" value = "/WEB-INF/" />
< property name = "suffix" value = ".jsp" ></ property >
</ bean >
|
总结
ViewResolver解决的事情很单一
-
通过配置,根据不同策略,找出匹配的JSP(也可以是其他)。
-
适当添加缓存处理
-
根据策略不同,返回不同的VIEW,降低耦合度。