在之前的《使用jsp作為視圖模板&常規部署》章節有過一個實踐,需要啟動類繼承自SpringBootServletInitializer方可正常部署至常規tomcat下,其主要能夠起到web.xml的作用。下面通過源碼簡單解析為何其能夠替代web.xml。
本章概要
1、源碼分析如何實現SpringBootServletInitializer整個加載過程;
2、實現自定義WebApplicationInitializer配置加載;
3、實現自定義ServletContainerInitializer 配置加載;
示例代碼如下
1、首先web.xml主要配置各種servlet,filter,listener等,如常見的Log4jConfigListener、OpenSessionInViewFilter、CharacterEncodingFilter、DispatcherServlet等,此部分信息均是容器啟動時加載。
2、在springboot中我們從SpringBootServletInitializer源碼入手:
-
public
abstract
class SpringBootServletInitializer implements WebApplicationInitializer{
-
..................
-
public void onStartup(ServletContext servletContext) throws ServletException {
-
this.logger = LogFactory.getLog(
super.getClass());
-
WebApplicationContext rootAppContext = createRootApplicationContext(servletContext);
-
-
if (rootAppContext !=
null) {
-
servletContext.addListener(
new ContextLoaderListener(rootAppContext) {
-
public void contextInitialized(ServletContextEvent event) {
-
}
-
});
-
}
else
-
this.logger.debug(
-
"No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
-
}
-
....................
-
}
可以先關注此類實現了
WebApplicationInitializer,那么實現了此接口又如何呢?
3、下面繼續關注一個spring源碼:
- <code class="language-java"><span style="font-size:14px;">@HandlesTypes({ WebApplicationInitializer.class })
- public class SpringServletContainerInitializer implements <span style="background-color:rgb(255,255,255);">ServletContainerInitializer </span>{
- public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
- throws ServletException {
- List initializers = new LinkedList();
- if (webAppInitializerClasses != null) {
- for (Class waiClass : webAppInitializerClasses) {
- if ((!(waiClass.isInterface())) && (!(Modifier.isAbstract(waiClass.getModifiers())))
- && (WebApplicationInitializer.class.isAssignableFrom(waiClass))) {
- try {
- initializers.add((WebApplicationInitializer) waiClass.newInstance());
- } catch (Throwable ex) {
- throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
- }
- }
- }
- }
- if (initializers.isEmpty()) {
- servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
- return;
- }
- servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
- AnnotationAwareOrderComparator.sort(initializers);
- for (WebApplicationInitializer initializer : initializers)
- initializer.onStartup(servletContext);
- }
- }</span></code>
4、繼續關注3中紅色標示部分,此時我們先來看
ServletContainerInitializer的作用,其主要就是在啟動容器時負責加載相關配置:
public abstract interface ServletContainerInitializer {
public abstract void onStartup(Set<Class<?>> paramSet, ServletContext paramServletContext) throws ServletException;
}
容器啟動時會
自動掃描當前服務中ServletContainerInitializer的實現類,並調用其onStartup方法,其參數Set<Class<?>> c,可通過在實現類上聲明注解javax.servlet.annotation.HandlesTypes(
WebApplicationInitializer.class)注解自動注入,@HandlesTypes會自動掃描項目中所有的
WebApplicationInitializer.class的實現類,並將其全部注入Set。
5、通過4中的說明可以很清楚的理解其服務啟動容器加載過程配置的裝載過程,在
SpringServletContainerInitializer中可以發現所有
WebApplicationInitializer實現類在執行onStartup方法前需要根據其注解@order值排序,下面自定義一個
WebApplicationInitializer實現類:
-
package com.shf.springboot.config;
-
-
import javax.servlet.ServletContext;
-
import javax.servlet.ServletException;
-
import org.slf4j.Logger;
-
import org.slf4j.LoggerFactory;
-
import org.springframework.core.annotation.Order;
-
import org.springframework.web.WebApplicationInitializer;
-
import com.shf.springboot.runner.MyStartupRunner1;
-
@Order(
1)
-
public
class MyWebApplicationInitializer implements WebApplicationInitializer {
-
private Logger logger=LoggerFactory.getLogger(MyStartupRunner1.class);
-
-
@Override
-
public void onStartup(ServletContext paramServletContext) throws ServletException {
-
logger.info(
"啟動加載自定義的MyWebApplicationInitializer");
-
System.out.println(
"啟動加載自定義的MyWebApplicationInitializer");
-
}
-
-
}
打成WAR包部署至常規tomcat下啟動服務驗證:


注:之前有專門講解如何裝載servlet、filter、listener的注解,且可以通過兩種不同的方式。那么第三種方式可以通過WebApplicationInitializer的實現類來進行裝載配置。但此方式
僅限部署至常規容器下生效
,采用
jar方式應用內置容器啟動服務不加載
。
6、既然可以通過自定義的WebApplicationInitializer來實現常規容器啟動加載,那么我們是否可以直接自定義ServletContainerInitializer來實現啟動加載配置呢:
6.1、首先編寫一個待注冊的servlet:
-
package com.shf.springboot.servlet;
-
-
import java.io.IOException;
-
import javax.servlet.ServletContext;
-
import javax.servlet.ServletException;
-
import javax.servlet.annotation.WebServlet;
-
import javax.servlet.http.HttpServlet;
-
import javax.servlet.http.HttpServletRequest;
-
import javax.servlet.http.HttpServletResponse;
-
import org.springframework.boot.web.servlet.ServletContextInitializer;
-
-
public
class Servlet4 extends HttpServlet {
-
private
static
final
long serialVersionUID = -
4186518845701003231L;
-
-
@Override
-
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-
System.out.println(
"Servlet4");
-
resp.setContentType(
"text/html");
-
resp.getWriter().write(
"Servlet4");
-
}
-
-
@Override
-
public void init() throws ServletException {
-
super.init();
-
System.out.println(
"Servlet4 loadOnStart");
-
}
-
-
}
6.2、編寫實現ServletContainerInitializer的自定義實現類:
-
package com.shf.springboot.config;
-
-
import java.util.Set;
-
import javax.servlet.ServletContainerInitializer;
-
import javax.servlet.ServletContext;
-
import javax.servlet.ServletException;
-
import javax.servlet.ServletRegistration;
-
import org.slf4j.Logger;
-
import org.slf4j.LoggerFactory;
-
-
public
class MyServletContainerInitializer implements ServletContainerInitializer {
-
private Logger logger=LoggerFactory.getLogger(MyServletContainerInitializer.class);
-
-
@Override
-
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
-
logger.info(
"啟動加載自定義的MyServletContainerInitializer");
-
System.out.println(
"啟動加載自定義的MyServletContainerInitializer");
-
ServletRegistration.Dynamic testServlet=servletContext.addServlet(
"servlet4",
"com.shf.springboot.servlet.Servlet4");
-
testServlet.setLoadOnStartup(
1);
-
testServlet.addMapping(
"/servlet4");
-
}
-
}
6.3、對
新增的servlet設置其請求路徑
,同時打成WAR包部署至tomcat啟動服務,但請求
http://localhost:8080/SpringBoot1/servlet4
卻失敗,此時發現需要了解servlet3對於ServletContainerInitializer 的加載機制是如何的,在官方有類似這樣的描述“該接口的實現必須聲明一個JAR資源放到程序中的META-INF/services下,並且記有該接口實現類的全路徑,才會被運行時(server)的查找機制或是其它特定機制找到”。那么我們先參考spring-web-4.3.2.RELEASE.jar中


我們可以將自定義的類配置到一個jar包下部署至WEB-INF\lib目錄下,
6.3.1、首先新建如下目錄結構的文件並填寫內容如下:


6.3.2、然后通過如下命令生成jar包


6.3.3、將myTest.jar放置WEB-INF\lib目錄下重啟服務,再次請求:


注:與5中實現
WebApplicationInitializer一樣,該方式僅限於部署常規容器生效。故jar通過內置容器啟動的服務無法加載servlet4配置。