Spring boot 的 viewResolver 黑科技总结
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
理论上要放在最后。
- 完 -