1. 程式人生 > >常規容器下SpringBootServletInitializer如何實現web.xml作用解析

常規容器下SpringBootServletInitializer如何實現web.xml作用解析

在之前的《使用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通過內建容器啟動的服務無法