之前開阿里的HSF框架,里面用到了Java的SPI機制,今天閑暇的時候去了解了一下,通過寫博客來記錄一下
SPI的全名為Service Provider Interface,我對於該機制的理解是為接口尋找服務實現類。現在公司的系統都是進行了模塊的划分,系統抽象為多個模塊,往往有很多不同的實現方案,比如日志模塊的方案,xml解析模塊、jdbc模塊的方案等。面向的對象的設計里,我們一般推薦模塊之間基於接口編程,模塊之間不對實現類進行硬編碼。一旦代碼里涉及具體的實現類,就違反了可拔插的原則,如果需要替換一種實現,就需要修改代碼。於是就有了SPI這種服務發現機制。
java spi的具體使用如下 :
當服務的提供者,提供了服務接口的一種實現之后,在jar包的META-INF/services/目錄里同時創建一個以服務接口命名的文件。該文件里就是實現該服務接口的具體實現類。而當外部程序裝配這個模塊的時候,就能通過該jar包META-INF/services/里的配置文件找到具體的實現類名,並裝載實例化,完成模塊的注入。
基於這樣一個約定就能很好的找到服務接口的實現類,而不需要再代碼里制定。
jdk提供服務實現查找的一個工具類:java.util.ServiceLoader
參考案例:
項目文件結構:

參考代碼:
Developer.java package cn.edu.knowledge.spi; public interface Developer { public String getPrograme(); }
JavaDeveloper.java package cn.edu.knowledge.spi; public class JavaDeveloper implements Developer { @Override public String getPrograme() { return "Java"; } }
META-INF\services文件下的cn.edu.knowledge.spi.Developer文件內容是服務類的全限命名:
cn.edu.knowledge.spi.JavaDeveloper
將文件導出為jar包,新建一個項目,在項目中導入該jar,下面的測試類的代碼
Test.java import java.util.ServiceLoader; import cn.edu.knowledge.spi.Developer; public class Test { public ServiceLoader<Developer> serviceloader = ServiceLoader.load(Developer.class); public static void main(String[] arg) { Test devClient = new Test(); Developer dev = devClient.getDeveloper(); System.out.println(dev.getPrograme()); } private Developer getDeveloper() { Developer lastdev = null; for (Developer dev : serviceloader) { System.out.println("out." + dev.getPrograme()); lastdev = dev; } if(lastdev==null) System.out.println("why..."); return lastdev; } }
我們在開發中都有用到SPI機制,但是我們沒有意識到比如:
1.common-logging
apache最早提供的日志的門面接口。只有接口,沒有實現。具體方案由各提供商實現,發現日志提供商是通過掃描 META-INF/services/org.apache.commons.logging.LogFactory 配置文件,通過讀取該文件的內容找到日志提工商實現類。只要我們的日志實現里包含了這個文件,並在文件里制定 LogFactory工廠接口的實現類即可。
2.jdbc
jdbc4.0以前,開發人員還需要基於Class.forName("xxx")的方式來裝載驅動,jdbc4也基於spi的機制來發現驅動提供商了,可以通過META-INF/services/java.sql.Driver文件里指定實現類的方式來暴露驅動提供者。
學習到的知識:面向接口編程可以實現接口和實現的分離,這樣做的最大好處就是能夠在客戶端未知的情況下修改實現代碼。那么什么時候應該抽象出Java接口呢?一種是用在層和層之問的調用。層和層之間是最忌諱耦合度過高或是改變過於頻繁。設計優秀的接口能夠解決這個問題。另一種是用在那些不穩定的部分上。如果某些需求的變化性很大,那么定義接口也是一種解決之道。設計良好的接口就像是我們日常使用的萬用插座一樣,不論插頭如何變化,都可以使用。
