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返回的Stringurl当成bean name去环境中找相应的实现了View接口的Bean。ViewResolverComposite。所有通过WebMvConfigurer#configureViewResolvers方法配置的viewResolver都会集中在这个composite里。InternalResourceViewResolver。该viewResolver负责将Controller返回的String解析成为InternalResourceView(servlet、JSP等)。 需要特别注意的是,该ViewResolver会尝试去解析所有view name,无论对应的资源文件(如JSP)是否存在,所以该viewResolver理论上要放在最后。
- 完 -
