JAVA SPI机制及SPI机制在Tomcat中的应用
SPI 是 JAVA 提供的一種服務提供發現接口,其實就是一種面向接口的編程,為接口去匹配具體服務實現的機制,這一點上與 IOC 的思想類似,都是把裝配的控制權放到了程序之外,下面具體看看什么是 SPI。
一、什么是 SPI
SPI 全稱為 Service Provider Interface,即服務提供發現接口,這里的服務指的不是我們經常聽到的微服務服務發現,這里的一個服務 Service 指的是一個接口或抽象類,服務提供方則是對這個接口或抽象類的實現。SPI 是 ”基于接口的編程 + 策略模式 + 配置文件“ 組合實現的動態加載機制
二、為什么使用 SPI
模塊化設計中,模塊之間基于接口編程,把裝配的控制權放到程序之外,實現系統的解耦
適用于調用方根據實際需求啟用、擴展、替換服務的策略實現。許多開源框架中都使用了 Java 的 SPI 機制,如 JDBC 的 SPI 加載模式、日志框架 SLF4J 加載不同提供商的日志實現、Spring 中也大量適用了 SPI、Dubbo 的擴張機制、ServiceComb Java Chassis (CSE) 的 Filter、異常處理等擴展機制
三、SPI 的實現
- 在類路徑下的 META-INF/services 目錄下,創建以服務接口的”全限定名“命名的文件,文件的內容為接口實現類的全限定名
- 實現類必須在當前程序的 classpath 下
- 使用 bash java.util.ServiceLoader 動態加載實現,會掃描 META-INF/services 下的配置文件加載實現類
并使用maven打包,并安裝在本地maven倉庫
以下這一步很重要:
再次使用maven打包,并安裝在本地maven倉庫
- 可是多寫一個實現類
也要再次打包
pom.xml
四、SPI機制在Tomcat中的應用
- 我們知道:Servlet 2.5 實現 webApp 加載的方式是 web.xml,獲取其中配置的ServletContextListener 的實現類,通過實現類的 contextInitialized 方法來加載 webapp。
具體可參考我另一篇文章
-
而 Servlet 3.0 就無需配置 web.xml, 直接通過實現接口 ServletContainerInitializer 就可以實現 webApp 的加載,就如類名一樣,這個類的作用就是在 servlet 容器初始化過程中加入自定義操作,因為是自定義的,所以可擴展性就非常強,能干很多事情。
-
兩者執行位置的區別
兩個方法的執行位置都是在 Tomcat 啟動過程中,Context 容器啟動時。具體方法是 StandardContext 類的 startInternal() 方法,一個 context 容器就代表了一個 webapp。 -
ServletContainerInitializer源碼
- 那么servlet3.0規范下,是如何不通過web.xml的方式,使用純java代碼的方式加載spring應用的呢
直接看到spring-web:5.3.13的源碼:
沒錯,tomcat啟動后,可以根據servlet3.0的規范,通過SPI機制,將實現了ServletContainerInitializer 接口的類全部進行加載,并排序后,依次調用onstartup方法
在看看javax.servlet.ServletContainerInitializer的內容:
于是,我們直接找到SpringServletContainerInitializer
@HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer {@Overridepublic void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException {List<WebApplicationInitializer> initializers = Collections.emptyList();if (webAppInitializerClasses != null) {initializers = new ArrayList<>(webAppInitializerClasses.size());for (Class<?> waiClass : webAppInitializerClasses) {// Be defensive: Some servlet containers provide us with invalid classes,// no matter what @HandlesTypes says...if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&WebApplicationInitializer.class.isAssignableFrom(waiClass)) {try {initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(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);}} }這里要先知道@HandleTypes這個注解的含義,該注解也是Servlet規范所定義的,作用就是:將注解指定的Class對象(包括其實現類及子類)作為參數傳遞到onStartup(也就是@HandleTypes只能作用在實現了ServletContainerInitializer 的類上)
//含義就是,將WebApplicationInitializer.class的子類獲取實現類, //作為一個Set<Class<?>>參數傳入道到SpringServletContainerInitializer.onstartup方法中 @HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer {}然后,我們就可以在onstartup方法中,進行我們的開發,例如初始化web容器,初始化DispatcherServlet等操作
- AbstractContextLoaderInitializer.onstartup:
- AbstractDispatcherServletInitializer.onstartup:
參考文章
參考文章
總結
以上是生活随笔為你收集整理的JAVA SPI机制及SPI机制在Tomcat中的应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HR不会告诉你的薪资谈判技巧
- 下一篇: SpringBoot Test及注解详解