Java統治服務器編程領域多年還未有退位趨勢,以IoC(控制反轉)思想爲核心的Spring功不可沒。大多數時候,咱們均可以使用Spring框架來實現咱們的依賴注入,但仍有不少場景,咱們指望本身的代碼有更少的依賴、適應更多的場景,好比跨Android和服務端、跨JVM語言的組件拼裝。javascript
其實從Java6開始已經提供了一套依賴注入標準「Service Provider」和相應的工具」ServiceLoader」來實現咱們本身的控制反轉,且其已經普遍應用在JDK的擴展性設計之中(如:腳本引擎ScriptEngine, 字符集Charset, 文件系統FileSystems, 網絡通信NIO),並愈來愈多地被其它開源組件所使用(如:Web標準Servlet3.0, 通用日誌接口slf4j-api:1.3),Java9進一步對「Service Provider」進行擴展實現了Java的模塊化。因此」Service Provider」機制是Java愈來愈重要的基礎知識之一。html
容我帶領各位,經過JDK文檔和源碼來一步步瞭解」Service Provider」,進而掌握經過Java自帶能力零依賴實現本身的動態依賴注入的方法,或按Java標準來擴展JDK、日誌、Http服務的能力。java
」Service Provider」首先是做爲一個標準被Javase所吸納:python
https://docs.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Service_Providergit
標準中對」Service Provider」的定義能夠總結爲三句話:github
以上是」Service Provider」標準中最重要的三點,請你們務必徹底理解並牢記於心,標準中還有其它一些限制條件,只需瞭解以便問題的快速定位,如:web
JDK中內置了「Service Provider「加載工具類」ServiceLoader」,經過靜態方法」ServiceLoader.load()」方法,便可建立指定類型的」Service Provider」迭代器(ServiceLoader自己),經過遍歷迭代器便可獲得全部Provider的實例。」ServiceLoader」的源代碼能夠在JDK中找到,實現」服務加載」最關鍵的是下面幾段(以JDK8源碼爲例):spring
一、」Service Provider」標準定義的服務發佈文件路徑前綴:apache
二、使用(系統或用戶)ClassLoader找到指定」Service」的」Provider」全部發布文件編程
三、根據發佈文件中的類名加載Provider的Class
四、經過Provider的Class建立Provider實例
咱們看到ServiceLoader經過調用提供者Class的newInstance方法,建立了服務提供者的實例,這也是爲何服務提供者須要有一個無參構造函數的緣由。
出於Web應用安全隔離的須要,Tomcat在實現Servlet3.0標準的」 ServletContainerInitializer」應用自啓動機制中,使用了遵循」Service Provider」標準的另外一套服務查找實現」WebappServiceLoader」,主要區別在於去」WEB-INF/lib」下而不是直接經過類加載器查找」Service Provider」文件,感興趣的同窗能夠去看Tomcat的源碼:org.apache.catalina.startup.WebappServiceLoader
羅馬不是一天建成的,JDK也是。從MessageDigest、Charset、ScriptEngine等誕生於Java不一樣時期的API來看,」Service Provider」標準和」ServiceLoader」工具類的使用從無到有,從選擇之一到惟一選擇,咱們能夠看出」Service Provider」和」ServiceLoader」機制將是擴展JDK已有服務(Charset、ScriptEngine、NIO等)的標準。
讓咱們一塊兒經過腳本引擎ScriptEngine這一與」Service Provider」一塊兒誕生的API,來學習如何經過」Service Provider」擴展JDK服務,以及玩轉可擴展服務的設計。
從Java6開始,JDK內置了一套javascript腳本引擎」NashornScriptEngine」,能夠很方便地在java裏面直接解析、運行js腳本,爲代碼提供動態執行能力,而不用依賴任何第三方庫:
public static void callJavascript() { //建立腳本引擎管理器 ScriptEngineManager m = new ScriptEngineManager(); //經過腳本引擎管理器查找並建立javascript引擎 ScriptEngine jsEngine = m.getEngineByName("javascript"); try { //綁定預約義參數 Bindings bindings = jsEngine.getBindings(ScriptContext.ENGINE_SCOPE); bindings.put("a", 2); bindings.put("b", 3); //調用javascript的pow函數 Object result = jsEngine.eval("Math.pow(a,b)", bindings); //打印返回值」8.0」 System.out.println(result); } catch (ScriptException e) { e.printStackTrace(); } }
變量、函數、與java互調用等更復雜的功能,你們能夠一試,就不在本文的討論範圍以內。
除了自帶的JavaScript引擎,經過」Service Provider」機制,很容易擴展ScriptEngine,支持其它腳本,好比引入」org.python:jython:2.7.0」後,ScriptEngine就具有了執行Python腳本的能力:
package org.ctstudio; import org.python.util.PythonInterpreter; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import java.util.Properties; public class PyScriptEngineDemo { //打招呼函數,供Python腳本調用 public static void sayHello(String name) { System.out.format("Hello %s!\n", name); } public static void callPython() { //Jython引擎使用前須要提早作一些初始化工做 Properties props = new Properties(); props.setProperty("python.import.site", "false"); PythonInterpreter.initialize(System.getProperties(), props, new String[]{}); //建立腳本引擎管理器 ScriptEngineManager m = new ScriptEngineManager(); //經過腳本引擎管理器查找並建立jython引擎 ScriptEngine pyEngine = m.getEngineByName("jython"); try { //執行Python腳本,在其中調用java函數 pyEngine.eval("from org.ctstudio import PyScriptEngineDemo\n" + "PyScriptEngineDemo.sayHello('Jack')"); //Hello Jack! } catch (ScriptException e) { e.printStackTrace(); } } public static void main(String[] args) { callPython(); } }
ScriptEngineManager是如何使用」Service Provider」機制的呢?經過JDK源代碼,咱們能夠看到,ScriptEngineManager正是經過」ServiceLoader.load()」方法來發現全部腳本引擎:
因爲腳本引擎實例的建立是一件開銷比較大的事情,而」ServiceLoader」在迭代過程當中就會直接建立提供者的實例,因此ScriptEngineManager沒有直接返回ScriptEngine,而是使用抽象工廠模式發現並保存ScriptEngineFactory實例,只在真正須要的時候才經過具體工廠建立ScriptEngine的實例:
Servlet3.0的設計則較直接,」ServletContainerInitializer」的提供者被發現並建立實例,隨後全部提供者的」 onStartup」被調用,以觸發用戶自定義的初始化過程,具體可參考Tomcat的源碼:
org.apache.catalina.startup.ContextConfig:
org.apache.catalina.core.StandardContext:
Java最有名的日誌框架之一logback就是利用Servlet3.0的這一機制來初始化:
而Spring框架則利用Servlet3.0的這一機制實現了將Bean與服務提供者集成起來的WebApplicationInitializer,Spring-Boot則進一步在WebApplicationInitializer的基礎之上實現了Web應用拉起工具基類SpringBootServletInitializer,使得咱們能夠在Servlet容器中輕鬆拉起咱們的Spring應用:
最新版本的日誌外觀slf4j2.0(未發佈)的LoggerFactory已改由」Service Provider」標準來加載日誌實現:
Logback1.3(未發佈,其做者也是log4j、slf4j的做者)也改用」Service Provider」機制來實現與日誌外觀的集成:
如此多舉足輕重的開源軟件的重視,足以體現」Service Provider」做爲Java標準的控制反起色制的重要影響,並可預見其將會愈來愈重要。無論你是想要爲開源軟件做貢獻,或是設計本身的可擴展組件,」Service Provider」都是你有必要掌握的知識。