Spring boot 的 viewResolver 黑科技总结
2017年 3月 27日Spring framework的viewResolver
当一个Controller
返回一串String
类型的view name
之后,Spring是如何找到对应的view
的?
答案就在DispatcherServlet
的resolveViewName
方法中,它循环一个viewResolvers
列表,
然后调用ViewResolver.resolveViewName(...)
方法,当列表中任意一个viewResolver
返回不为null
时,
DispatcherServlet
就认为这个view
解析完毕,并使用解析得到的View
对象继续处理。
那么问题就来了,这个viewResolvers
列表是如何来的呢?
答案在DispatcherServlet
的initViewResolver
方法中。在该方法中,首先根据detectAllViewResolvers
标识来确定viewResolver
的获取方式:
- 如果
detectAllViewResolvers
为true
(默认为true
),则会从当前的ApplicationContext
中获取所有实现ViewResolver
包括其子类的对象,然后还会根据对象的Order
进行排序。 - 如果
detectAllViewResolvers
为false
,则会从当前的ApplicationContext
中获取Bean name为viewResolver
的,类型为ViewResolver
的单个对象,作为viewResolvers
列表中的唯一对象。
然后,如果上述两种情况还没有获取到任何viewResolvers
,DispatcherServlet
会启用默认策略,
根据DispatcherServlet.properties
的配置,获取并初始化键值为
org.springframework.web.servlet.ViewResolver
的class作为viewResolvers
列表成员。
Spring Boot的viewResolver
自动配置
在WebMvcAutoConfiguration
中的WebMvcAutoConfigurationAdapter
中可以看到,它根据条件自动注入了以下几个viewResolver
:
- 如果当前环境里没有
InternalResourceViewResolver
同类型的Bean,则自动注入类型为InternalResourceViewResolver
、 名称为defaultViewResolver
的Bean。InternalResourceViewResolver
为UrlBasedViewResolver
的扩展,Order
默认为Integer.MAX_VALUE
。 - 如果当前环境里有类型为
View
的Bean,并且没有注入过BeanNameViewResolver
同类型的Bean, 则自动注入类型为BeanNameViewResolver
、名称为beanNameViewResolver
的Bean。 并且设置其Order
为Order.LOWEST_PRECEDENCE - 10
,其中Order.LOWEST_PRECEDENCE
的值为Integer.MAX_VALUE
。 - 如果当前环境中已经存在类型为
ViewResolver
的Bean,并且没有类型为ContentNegotiatingViewResolver
、 名称为viewResolver
的Bean,则自动注入同类型同名的Bean。并且设置其Order
为Order.HIGHEST_PRECEDENCE
, 其中Order.HIGHEST_PRECEDENCE
的值为Integer.MIN_VALUE
。
这3个ViewResolver
是WebMvcAutoConfigurationAdapter
为我们默认配置的3个Bean,
但是你以为这样就完事儿了么?其实不然,Spring Boot还会配置一个ViewResolverComposite
的ViewResolver
,
这个ViewResolver
包含了所有使用WebMvcConfigurerAdapter#configureViewResolvers(ViewResolverRegistry)
方法配置的viewResolver
。
并且该ViewResolverComposite
的Order
为Order.LOWEST_PRECEDENCE
。
这个ViewResolverComposite
由EnableWebMvcConfiguration
类提供。EnableWebMvcConfiguration
继承了DelegatingWebMvcConfiguration
,
而DelegatingWebMvcConfiguration
则继承了WebMvcConfigurationSupport
。
在WebMvcConfigurationSupport
中,提供了一个注册ViewResolver
类型Bean的mvcViewResolver
方法。
该方法先初始化了一个ViewResolverRegistry
实例,然后将其传入并调用configureViewResolvers
方法。
最后它根据在configureViewResolvers
方法中配置的ViewResolverRegistry
实例初始化并返回了一个ViewResolverComposite
的实例。
而在WebMvnConfigurationSupport
的子类,也是EnableWebMvcConfiguration
的父类DelegatingWebMvcConfiguration
中,
它首先注入了当前环境中所有的WebMvcConfigurer
(WebMvcConfigurationAdapter
实现了该接口)类型的Bean的列表,
并将该列表包装给WebMvcConfigurerComposite
,最后将所有configure*
和add*
等方法代理给WebMvcConfigurerComposite
。
这样就实现了将所有可配置的方法代理给外部WebMvcConfigurer
的实现类的这样一个功能。
所以总结一下,在Spring boot自动配置的情况下,会有这样几个viewResolver
(按优先度从高到低):
ContentNegotiatingViewResolver
,该ViewResolver
会根据前端传来的请求内容类型 (譬如说由accept
头指定、由format
参数指定或者由url的后缀名指定等,根据ContentNegotiatingManager
中配置的strategies
来确定, 可通过WebMvcConfigurer#configureContentNegotiation
方法来配置)、并且轮询其他各个ViewResolver
来返回相应的View
。BeanNameViewResolver
。如果没有配置其他的ViewResolver
则该viewResolver
仅次于ContentNegotiatingViewResolver
。 自动注入这个viewResolver
的前提是当前环境中已经有类型为View
的Bean,否则该viewResolver
不会被注入到当前环境中。 这个ViewResolver
的职责是将Controller
返回的String
url当成bean name去环境中找相应的实现了View
接口的Bean。ViewResolverComposite
。所有通过WebMvConfigurer#configureViewResolvers
方法配置的viewResolver
都会集中在这个composite里。InternalResourceViewResolver
。该viewResolver
负责将Controller
返回的String
解析成为InternalResourceView
(servlet
、JSP
等)。 需要特别注意的是,该ViewResolver
会尝试去解析所有view name,无论对应的资源文件(如JSP
)是否存在,所以该viewResolver
理论上要放在最后。
- 完 -