In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-04-01 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article introduces the knowledge of "how to understand the SPI mechanism in Java". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!
The concept of SPI
The full name of SPI in Java is Service Provider Interface, which is a built-in service discovery mechanism of JDK. It is a set of API provided by Java to be implemented or extended by third parties, which can be used to enable framework extensions and replacement components.
JAVA SPI = interface-based programming + policy mode + dynamic loading mechanism for configuration files
Usage scenarios of SPI
Java is an object-oriented language, although Java8 began to support functional programming and Stream, but in general, it is still an object-oriented language. When using Java for object-oriented development, it is generally recommended to use interface-based programming, and the modules and modules of the program will not be hard-coded directly. In the actual development process, there are often multiple implementation classes in an interface, and each implementation class either implements different logic, or uses different ways, and has different implementation technologies. In order to make the caller know clearly which implementation class of the interface he is calling when calling the interface, or in order to realize that it does not need to be dynamically specified in the program when the module is assembled, it needs a service discovery mechanism. The SPI loading mechanism in Java can meet this requirement, and it can automatically find the implementation class of an interface.
A large number of frameworks use Java's SPI technology, as follows:
(1) JDBC loads different types of database drivers. (2) Log facade interface implements class loading. SLF4J loads log implementation classes from different providers. (3) SPI is widely used in Spring.
To the servlet3.0 specification
The realization of ServletContainerInitializer
Automatic type conversion Type Conversion SPI (Converter SPI, Formatter SPI), etc.
(4) there are many components in Dubbo, and each component is abstracted by the formation of interfaces in the framework! There are many kinds of specific implementation, and the implementation of the interface is taken according to the user's configuration when the program is executed.
The use of SPI
When the service provider provides an implementation of the interface, you need to create an interface name (package name) under the META-INF/services/ directory of the Jar package. The file named in the form of the interface name, in which the implementation class of the interface is configured (full package name + class name).
When the external program loads the interface through the java.util.ServiceLoader class, it can find the specific implementation class name through the configuration file in the META/Services/ directory of the Jar package, load the instantiation, and complete the injection. At the same time, the specification of SPI stipulates that the implementation class of the interface must have a no-parameter constructor.
The implementation class to find the interface in SPI is through java.util.ServiceLoader, and there is a line of code in the java.util.ServiceLoader class as follows:
/ / load the prefix of the specific implementation class information, that is, the file named after the interface needs to be placed in the META-INF/services/ directory in the Jar package private static final String PREFIX = "META-INF/services/"
That is, we must write the configuration file of the interface to the META/Services/ directory of the Jar package.
SPI instance
Here, a simple example of using SPI is given to demonstrate how to use SPI to dynamically load the implementation class of the interface in Java programs.
Note: the example is developed based on Java8.
1. Create a Maven project
Create the Maven project spi-demo in IDEA as follows:
two。 Edit pom.xml
Spi-demo io.binghe.spi jar 1.0.0-SNAPSHOT 4.0.0 org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.6.0 1.8 1.8
3. Create a class load utility class
Create a ServiceLoader class in the MyServiceLoader,MyServiceLoader class under the io.binghe.spi.loader package that directly calls JDK to load the Class. The code is as follows.
Package io.binghe.spi.loader; import java.util.ServiceLoader; / * * @ author binghe * @ version 1.0.0 * @ description Class loading tool * / public class MyServiceLoader {/ * use the SPI mechanism to load all Class * / public static ServiceLoader loadAll (final Class clazz) {return ServiceLoader.load (clazz);}}
4. Create an interface
Create the interface MyService under the io.binghe.spi.service package. As a test interface, there is only one method in the interface to print the incoming string information. The code is as follows:
Package io.binghe.spi.service; / * * @ author binghe * @ version 1.0.0 * @ description definition interface * / public interface MyService {/ * * print message * / void print (String info);}
5. Create an implementation class for the interface
(1) create the first implementation class MyServiceA
Create a MyServiceA class under the io.binghe.spi.service.impl package to implement the MyService interface. The code is as follows:
The first implementation of the package io.binghe.spi.service.impl; import io.binghe.spi.service.MyService; / * * @ author binghe * @ version 1.0.0 * @ description interface * / public class MyServiceA implements MyService {@ Override public void print (String info) {System.out.println (MyServiceA.class.getName () + "print" + info);}}
(2) create a second implementation class MyServiceB
Create a MyServiceB class under the io.binghe.spi.service.impl package to implement the MyService interface. The code is as follows:
The second implementation of package io.binghe.spi.service.impl; import io.binghe.spi.service.MyService; / * * @ author binghe * @ version 1.0.0 * @ description interface * / public class MyServiceB implements MyService {@ Override public void print (String info) {System.out.println (MyServiceB.class.getName () + "print" + info);}}
6. Create an interface file
Create the META/Services/ directory under the src/main/resources directory of the project, and create the io.binghe.spi.service.MyService file in the directory. Note: the file must be the full name of the interface MyService, and then configure the classes that implement the MyService interface into the file, as shown below:
Io.binghe.spi.service.impl.MyServiceA io.binghe.spi.service.impl.MyServiceB
7. Create a test class
Create the Main class under the io.binghe.spi.main package of the project, which is the entry class of the test program, provides a main () method, and calls the ServiceLoader class in the main () method to load the implementation class of the MyService interface. And print the result through the Stream of Java8, as shown below:
Package io.binghe.spi.main; import io.binghe.spi.loader.MyServiceLoader; import io.binghe.spi.service.MyService; import java.util.ServiceLoader; import java.util.stream.StreamSupport; / * * @ author binghe * @ version 1.0.0 * @ main method of the description test * / public class Main {public static void main (String [] args) {ServiceLoader loader = MyServiceLoader.loadAll (MyService.class) StreamSupport.stream (loader.spliterator (), false) .forEach (s-> s.print ("Hello World"));}}
8. Test example
Run the main () method in the Main class, and the printed information is as follows:
Io.binghe.spi.service.impl.MyServiceA print Hello World io.binghe.spi.service.impl.MyServiceB print Hello World Process finished with exit code 0
Through the printing information, we can see that the implementation class of the interface is loaded correctly through the Java SPI mechanism, and the implementation method of the interface is called.
Source code parsing
Here, it is mainly the parsing of the source code of java.util.ServiceLoader involved in the loading process of SPI.
Entering the source code of java.util.ServiceLoader, you can see that the ServiceLoader class implements the java.lang.Iterable interface, as shown below.
Public final class ServiceLoader implements Iterable
Indicates that the ServiceLoader class can be iterated through.
The following member variables are defined in the java.util.ServiceLoader class:
/ / load the prefix of the specific implementation class information, that is, the file named after the interface needs to be placed in the META-INF/services/ directory in the Jar package private static final String PREFIX = "META-INF/services/"; / / the interface private final Class service; / / class loader to be loaded is used to load the implementation class private final ClassLoader loader of the interface configured in the file named after the interface. / / the access control context private final AccessControlContext acc; / / used when creating the ServiceLoader is used to cache the loaded interface implementation class, where Key is the complete class name of the interface implementation class and Value is the implementation class object private LinkedHashMap providers = new LinkedHashMap (); / / the iterator private LazyIterator lookupIterator used to delay loading the implementation class
You can see that the load prefix is "META-INF/services/" defined in the ServiceLoader class, so the interface file must be created in the META-INF/services/ directory under the src/main/resources directory of the project.
Call the ServiceLoader.load (clazz) method from the MyServiceLoader class to enter the source code, as shown below:
/ / load the specified class according to the Class object of the class, return the ServiceLoader object public static ServiceLoader load (Class service) {/ / get the current thread's classloader ClassLoader cl = Thread.currentThread (). GetContextClassLoader (); / / dynamically load the specified class and load the class into ServiceLoader return ServiceLoader.load (service, cl);}
The ServiceLoader.load (service, cl) method is called in the method to continue to trace the code, as shown below:
/ / load the Class of the specified class through ClassLoader, and encapsulate the returned result in the ServiceLoader object public static ServiceLoader load (Class service, ClassLoader loader) {return new ServiceLoader (service, loader);}
You can see that in the ServiceLoader.load (service, cl) method, the constructor of the ServiceLoader class is called to follow up the code, as shown below:
/ / construct ServiceLoader object private ServiceLoader (Class svc, ClassLoader cl) {/ / if the passed Class object is null, the null pointer exception service = Objects.requireNonNull (svc, "Service interface cannot be null") is sentenced; / / if the passed ClassLoader is empty, it is obtained through ClassLoader.getSystemClassLoader (), otherwise the passed ClassLoader loader = (cl = = null) is directly used? ClassLoader.getSystemClassLoader (): cl; acc = (System.getSecurityManager ()! = null)? AccessController.getContext (): null; reload ()
Continue with the reload () method, as shown below.
/ / reload public void reload () {/ / clear the LinkedHashMap providers.clear () that saves the loaded implementation class; / / construct a delayed loading iterator lookupIterator = new LazyIterator (service, loader);}
Continue to follow up on the constructor of the lazy load iterator, as shown below.
Private LazyIterator (Class service, ClassLoader loader) {this.service = service; this.loader = loader;}
As you can see, the Class object of the interface that needs to be loaded and the classloader are assigned to the member variables of LazyIterator.
When we iterate to get the object instance in the program, we first look for whether there is a cached instance object in the member variable providers. Return directly if it exists, otherwise call the lookupIterator deferred load iterator to load.
The code for logical judgment by the iterator is as follows:
/ / iterative ServiceLoader method public Iterator iterator () {return new Iterator () {/ / get the iterator Iterator knownProviders = providers.entrySet () .iterator () that holds the LinkedHashMap of the implementation class; / / determine whether there is a next element public boolean hasNext () {/ / if there is an element in knownProviders, directly return true if (knownProviders.hasNext ()) return true / / return whether there is an element return lookupIterator.hasNext () in the delay loader;} / / get the next element public S next () {/ / if there is an element in knownProviders, directly get if (knownProviders.hasNext ()) return knownProviders.next (). GetValue (); / / get the element return lookupIterator.next () in the delay iterator lookupIterator } public void remove () {throw new UnsupportedOperationException ();}};}
The process for LazyIterator to load a class is as follows
/ / determine whether to own the next instance private boolean hasNextService () {/ / if you have the next instance, directly return true if (nextName! = null) {return true;} / / if the full name of the implementation class is null if (configs = = null) {try {/ / get the full-text name, file relative path + file name (package name + interface name) String fullName = PREFIX + service.getName () / / if the classloader is empty, get if (loader = = null) configs = ClassLoader.getSystemResources (fullName) through the ClassLoader.getSystemResources () method; if the else / / classloader is not empty, get configs = loader.getResources (fullName) directly through the classloader;} catch (IOException x) {fail (service, "Error locating configuration files", x) }} while ((pending = = null) | |! pending.hasNext ()) {/ / if there is no more element in the configs, return false if (! configs.hasMoreElements ()) {return false;} / / parse the package structure pending = parse (service, configs.nextElement ());} nextName = pending.next (); return true;} private S nextService () {if (! hasNextService ()) throw new NoSuchElementException () String cn = nextName; nextName = null; Class c = null; try {/ / load class object c = Class.forName (cn, false, loader);} catch (ClassNotFoundException x) {fail (service, "Provider" + cn + "not found");} if (! service.isAssignableFrom (c)) {fail (service, "Provider" + cn + "not a subtype") } try {/ / generate object instance S p = service.cast (c.newInstance ()) through c.newInstance (); / / save the generated object instance to cache (LinkedHashMap) providers.put (cn, p); return p;} catch (Throwable x) {fail (service, "Provider" + cn + "could not be instantiated", x);} throw new Error () / / This cannot happen} public boolean hasNext () {if (acc = = null) {return hasNextService ();} else {PrivilegedAction action = new PrivilegedAction () {public Boolean run () {return hasNextService ();}}; return AccessController.doPrivileged (action, acc);}} public S next () {if (acc = = null) {return nextService () } else {PrivilegedAction action = new PrivilegedAction () {public S run () {return nextService ();}}; return AccessController.doPrivileged (action, acc);}}
Finally, the class for the entire java.util.ServiceLoader is given, as follows:
Package java.util; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedAction Public final class ServiceLoader implements Iterable {/ / load the prefix of the specific implementation class information, that is, the file named by the interface needs to be placed in the META-INF/services/ directory in the Jar package private static final String PREFIX = "META-INF/services/"; / / the interface private final Class service to be loaded / / class loader, which is used to load the implementation class private final ClassLoader loader; of the interface configured in the file named by the interface / / the access control context private final AccessControlContext acc; / used when creating the ServiceLoader is used to cache the loaded interface implementation class, where Key is the complete class name of the interface implementation class and Value is the implementation class object private LinkedHashMap providers = new LinkedHashMap () / / Iterator for delayed loading of implementation class private LazyIterator lookupIterator; / / reload public void reload () {/ / clear the LinkedHashMap providers.clear () of the loaded implementation class; / / construct a delayed loading iterator lookupIterator = new LazyIterator (service, loader) } / / construct ServiceLoader object private ServiceLoader (Class svc, ClassLoader cl) {/ / if the passed Class object is null, the null pointer exception service = Objects.requireNonNull (svc, "Service interface cannot be null") is sentenced; / / if the passed ClassLoader is empty, it is obtained through ClassLoader.getSystemClassLoader (), otherwise the passed ClassLoader loader = (cl = = null) is directly used? ClassLoader.getSystemClassLoader (): cl; acc = (System.getSecurityManager ()! = null)? AccessController.getContext (): null; reload ();} private static void fail (Class service, String msg, Throwable cause) throws ServiceConfigurationError {throw new ServiceConfigurationError (service.getName () + ":" + msg, cause) } private static void fail (Class service, String msg) throws ServiceConfigurationError {throw new ServiceConfigurationError (service.getName () + ":" + msg);} private static void fail (Class service, URL u, int line, String msg) throws ServiceConfigurationError {fail (service, u + ":" + line + ":" + msg) } / / Parse a single line from the given configuration file, adding the name / / on the line to the names list. / private int parseLine (Class service, URL u, BufferedReader r, int lc, List names) throws IOException, ServiceConfigurationError {String ln = r.readLine (); if (ln = = null) {return-1;} int ci = ln.indexOf ('#'); if (ci > = 0) ln = ln.substring (0, ci) Ln = ln.trim (); int n = ln.length (); if (n! = 0) {if ((ln.indexOf ('') > = 0) | | (ln.indexOf ('\ t') > = 0) fail (service, u, lc, "Illegal configuration-file syntax"); int cp = ln.codePointAt (0) If (! Character.isJavaIdentifierStart (cp)) fail (service, u, lc, "Illegal provider-class name:" + ln); for (int I = Character.charCount (cp); I
< n; i += Character.charCount(cp)) { cp = ln.codePointAt(i); if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) fail(service, u, lc, "Illegal provider-class name: " + ln); } if (!providers.containsKey(ln) && !names.contains(ln)) names.add(ln); } return lc + 1; } private Iterator parse(Class service, URL u) throws ServiceConfigurationError { InputStream in = null; BufferedReader r = null; ArrayList names = new ArrayList(); try { in = u.openStream(); r = new BufferedReader(new InputStreamReader(in, "utf-8")); int lc = 1; while ((lc = parseLine(service, u, r, lc, names)) >Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.