Spring中的WebApplicationInitializer
1. WebApplicationInitializer
隨着JavaConfig
配置方式逐步替代配置,WebApplicationInitializer
可以看做Web.xml
的替代,此接口在Web容器啓動的時候會記載這個接口的實現類,從而起到Web.xml
的作用,從而我們可以通過其自定義配置Web組件.
首先我們進入這個接口,它僅有一個方法#onStartup
方法,我們並不容易看出其用意何在?而此方法又是何時被調用的?
public interface WebApplicationInitializer {
void onStartup(ServletContext var1) throws ServletException;
}
但是在這個類所屬的包下有另一個類- - -SpringServletContainerInitializer
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList();
Iterator var4;
if(webAppInitializerClasses != null) {
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class<?> waiClass = (Class)var4.next();
if(!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)waiClass.newInstance());
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
}
if(initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
} else {
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
var4 = initializers.iterator();
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
initializer.onStartup(servletContext);
}
}
}
}
在SpringServletContainerInitializer#onStartup
方法中,首先判斷參數WebAppInitializerClasses
這個Set
集合是否爲空.若不爲空並且是WebApplicationInitializer
接口的實現,就將其轉存到另一個集合中,之後對集合進行排序並遍歷調用其#onStartup
方法,從而我們可以知曉之前所說的WebApplicationInitializer
是如何在Web
流程中被調用的.
但是另外兩個問題又了:
1.SpringServletContainerInitializer#onStartup
方法又是如何被調用的呢?
2.其類上註解@HandlesType
又有什麼作用呢?
1.
官方聲明中指出:爲了支持無xml配置化,提供了ServletContainerInitializer
,通過SPI
機制,當啓動Web
容器的時候,會自動到相應的jar
包下找到META-INF/services
下ServletContainerInitializer
的全路徑名的文件,並根據其內容的全路徑進行實例化.而我們可以發現文件中的名稱就是SpringServletContainerInitializer
,所以SpringServletContainerInitializer
作爲ServletContainerInitializer
的實現,通過SPI
機制在Web
容器加載的時候會被自動調用.
2.
- 而對於
@HandlesType
註解,其作用爲:讀取@HandlesType
註解value
的值,根據其值將掃描路徑下所有該值所表示的接口實現,並將其生成一個Set
集合提供給#onStartup
方法使用.- 在
ApplicationContext
內部啓動時會通知ServletContainerInitializer#onStartup
方法,而這個方法的第一個參數Set
集合就是註解Value
值指定的Class
集合,而在這裏即爲WebApplicationInitializer
實現的集合- 在下列代碼中所示,在
Tomcat
所有容器的抽象類中ContainerMBean#addChild
方法中,Tomcat
在爲Host
容器添加Context
子容器時,會爲其分配一個ContextConfig
類,其中通過#processServletContainerInitializer
方法來獲取@HandlesType
註解
public class ContainerMBean extends BaseModelMBean {
public void addChild(String type, String name) throws MBeanException {
//........
try {
if(contained instanceof StandardHost) {
HostConfig config = new HostConfig();
contained.addLifecycleListener(config);
} else if(contained instanceof StandardContext) {
ContextConfig config = new ContextConfig();
contained.addLifecycleListener(config);
}
}
}
}
-------------------------------------------------------------------------------------
public class ContextConfig implements LifecycleListener {
protected void webConfig() {
//.........
if(this.ok) {
this.processAnnotations(orderedFragments, webXml.isMetadataComplete(), javaClassCache);
}
}
protected void processServletContainerInitializers() {
//.........
ServletContainerInitializer sci = (ServletContainerInitializer)var13.next();
this.initializerClassMap.put(sci, new HashSet());
HandlesTypes ht;
try {
ht = (HandlesTypes)sci.getClass().getAnnotation(HandlesTypes.class);
}
}
}
2.AbStractAnnotationConfigDispatcherServletInitializer
- 官方文檔聲明中指出:
Spring MVC
中我們其實是可以創建多個DispatcherServlet
的,只需要創建多個繼承自AbstractAnnotationConfigDispatcherServletInitializer
的子類即可,而每個DispatcherServlet
都有自己的應用上下文(servlet application context)
,這個應用上下文只針對當前的DispatcherServlet
有用,這也就是#getServletConfigClasses
的作用,用來獲取這個DispatcherServlet
的應用上下文的配置類- 而除了每個
DispatcherServlet
配置類的應用上下文,還有一個公共的跟應用上下文(root application context)
,這個應用上下文的作用時爲了在多個DispatcherServlet
之間共享,這也就是#getRootConfigClasses
的作用,用於返回根應用上下文的配置類,Spring
框架的機制會保證如果當前DispatcherServlet
的應用上下文中沒有找到想要的Bean
,就會去根應用上下文中找.
public class SpittrWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
//將DispatcherServlet映射到"/"
@Override
protected String[] getServletMappings(){
return new String[]{"/"};
}
//配置root applicationContext應用上下文的bean
@Override
protected Class<?>[] getRootConfigClasses(){
return new Class<?>[]{RootConfig.class};
}
//配置servlet applicationContext應用上下文的bean
@Override
protected Class<?>[] getServletConfigClasses(){
return new Class<?>[]{Webconfig.class};
}
}
--------------------------------------------------------------------------------------------------------------------------------------------------------
@Configuration
@EnableWebMvc
@ComponentScan("spitter.web")
public class WebConfig implements WebMvcConfigurer{//Spring5中此接口方法均爲默認方法,替代之前適配器類
//配置jsp視圖解析器.否則會使用默認的BeanNameViewResolver
@Bean
public viewResolver viewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF.views");
resolver.setSuffix(".jsp");
return resolver;
}
//配置靜態資源處理
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){
configurer.enable();
}
}
--------------------------------------------------------------------------------------------------------------------------------------------------------
@Configuration
@EnableWebMvc
@ComponentScan(basePackage = {"spitter"},excludeFilters = {
@Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)
})
public class RootConfig{
}
3.總結
AbStractAnnotationConfigDispatcherServletInitializer
就是Spring
給我們通過javaConfig
方式自定義DispatcherServlet
組件的實現方法,如果需要了解其他相關內容:
Spring
容器的相關知識可以參考:SpringMvc
自定義配置化相關知識可以參考: