Architects must know: Java's built-in inversion of control mechanism "Service Provider"

foreword

    Java has ruled the field of server programming for many years and has not abdicated. Spring, with the idea of ​​IoC (inversion of control) as the core, has contributed greatly. Most of the time, we can use the Spring framework to implement our dependency injection, but there are still many scenarios where we expect our code to have fewer dependencies and adapt to more scenarios, such as cross-Android and server-side, cross-JVM languages assembly of components.

    In fact, since Java6, a set of dependency injection standard "Service Provider" and the corresponding tool "ServiceLoader" have been provided to realize our own inversion of control, and it has been widely used in the extensibility design of JDK (such as: script engine) ScriptEngine, character set Charset, file system FileSystems, network communication NIO), and are increasingly used by other open source components (such as: Web standard Servlet3.0, common log interface slf4j-api:1.3), Java9 further " Service Provider" extension realizes the modularity of Java. So the "Service Provider" mechanism is one of the more and more important basic knowledge of Java.

    Let me lead you to understand "Service Provider" step by step through the JDK documentation and source code, and then master the method of implementing your own dynamic dependency injection through Java's own capability with zero dependencies, or extend the JDK, logging, and Http services according to Java standards. ability.

 

"Service Provider" standard

"Service Provider" was first adopted by Javase as a standard:

https://docs.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Service_Provider

The definition of "Service Provider" in the standard can be summarized in three sentences:

  1. A "Service" is a collection of well-known interfaces or (usually abstract) classes, and a "Service Provider" is a specific implementation of a service;
  2. The service provider, through the " META-INF/services / fully-qualified.name.of.service.Interface " file in the jar package contains the fully qualified name of the implementation class, and publishes the implementation class as a provider;
  3. The service lookup mechanism finds and creates an instance of the provider by traversing all the above file contents in the ClassPath.

The above are the three most important points in the "Service Provider" standard. Please be sure to fully understand and keep it in mind. There are other restrictions in the standard, just understand them to quickly locate the problem, such as:

  1. The provider must contain a no-argument constructor so that the service lookup mechanism can create its instance through reflection;
  2. In the provider's release file, you can define a comment line at the beginning of "#";
  3. In the provider release file, multiple implementation classes can be separated by newlines;
  4. Service finder must be invoked in security context...

 

service loading mechanism

The "Service Provider" loading tool class "ServiceLoader" is built into the JDK. Through the static method "ServiceLoader.load()" method, a specified type of "Service Provider" iterator (ServiceLoader itself) can be created, and it can be traversed by traversing the iterator. Get all Provider instances. The source code of "ServiceLoader" can be found in the JDK. The most important parts to realize "Service Loader" are the following paragraphs (take the Oracle JDK8 source code as an example):

1. The service publishing file path prefix defined by the "Service Provider" standard:

2. Use (system or user) ClassLoader to find all published files of the "Provider" of the specified "Service"

3. Load the Provider's Class according to the class name in the release file

4. Create a Provider instance through the Provider's Class

We see that ServiceLoader creates an instance of the service provider by calling the newInstance method of the provider class, which is why the service provider needs to have a no-argument constructor.

    For the need of web application security isolation, Tomcat implements "WebappServiceLoader" using another set of service lookups that follow the "Service Provider" standard in the "ServletContainerInitializer" application self-starting mechanism that implements the Servlet3.0 standard. The main difference is that " WEB-INF/lib" instead of directly looking for the "Service Provider" file through the class loader, interested students can go to the source code of Tomcat: org.apache.catalina.startup.WebappServiceLoader

 

service user

    Rome wasn't built in a day, and neither was the JDK. Judging from the APIs such as MessageDigest, Charset, ScriptEngine, etc., which were born in different periods of Java, the use of the "Service Provider" standard and the "ServiceLoader" tool class has grown from scratch, from one choice to the only choice, we can see that "Service Provider" " and "ServiceLoader" mechanisms will be the standard for extending JDK's existing services (Charset, ScriptEngine, NIO, etc.).

    Let's learn how to extend JDK services through "Service Provider" through the script engine ScriptEngine, an API born with "Service Provider", and play with the design of extensible services.

    Starting from Java6, JDK has built-in a set of javascript script engine "NashornScriptEngine", which can easily parse and run js scripts directly in java, providing dynamic execution capabilities for the code without relying on any third-party libraries:

    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();
        }
    }

More complex functions such as variables, functions, and inter-calling with java, you can try it, and are not within the scope of this article.

    In addition to the built-in JavaScript engine, through the "Service Provider" mechanism, it is easy to extend ScriptEngine to support other scripts. For example, after the introduction of "org.python:jython:2.7.0", ScriptEngine has the ability to execute Python scripts:

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();
    }
}

How does ScriptEngineManager use the "Service Provider" mechanism? Through the JDK source code, we can see that ScriptEngineManager discovers all script engines through the "ServiceLoader.load()" method:

 

    Since the creation of a script engine instance is a relatively expensive thing, and "ServiceLoader" will directly create an instance of the provider during the iteration process, so the ScriptEngineManager does not directly return the ScriptEngine, but uses the abstract factory pattern to discover and save the ScriptEngineFactory instance. , and only create an instance of ScriptEngine through a concrete factory when it is really needed:

 

    The design of Servlet3.0 is relatively straightforward. The provider of "ServletContainerInitializer" is discovered and an instance is created, and then the "onStartup" of all providers is called to trigger the user-defined initialization process. For details, please refer to the source code of Tomcat:

org.apache.catalina.startup.ContextConfig:

org.apache.catalina.core.StandardContext:

 

One of Java's most famous logging frameworks, logback, uses this mechanism of Servlet3.0 to initialize:

 

The Spring framework uses the mechanism of Servlet3.0 to implement the WebApplicationInitializer that integrates beans and service providers. Spring-Boot further implements the Web application pull-up tool base class SpringBootServletInitializer on the basis of the WebApplicationInitializer, so that we can Easily pull up our Spring application in the servlet container:

 

The LoggerFactory of the latest version of the log facade slf4j2.0 (unreleased) has been changed to use the "Service Provider" standard to load the log implementation:

Logback 1.3 (unreleased, whose author is also the author of log4j, slf4j) also switched to the "Service Provider" mechanism for integration with the log facade:

 

Epilogue

    The importance of so many important open source software is enough to reflect the important influence of "Service Provider" as a Java standard inversion of control mechanism, and it is foreseeable that it will become more and more important. Whether you want to contribute to open source software, or design your own extensible components, "Service Provider" is a must-have knowledge for you.

References

  1. Introduction to "Service Provider" in the Jar Package Standard
  2. JDK source code: script engine ScriptEngine, character set Charset, file system FileSystems, network communication NIO
  3. " ContextConfig", "StandardContext" in Tomcat source code
  4. "SpringServletContainerInitializer" in Spring-Web source code
  5. "SpringBootServletInitializer" in Spring-Boot source code
  6. The latest logback source code

 

{{o.name}}
{{m.name}}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=324131673&siteId=291194637