Skywalking-12: skywalking SPI mechanism

Switch 2021-10-14 05:38:44

SPI Mechanism

Basic overview

SPI Full name Service Provider Interface , Is a service discovery mechanism . By providing interfaces 、 Predefined loaders ( Loader ) And the commonly known configuration ( Generally in META-INF Under the table of contents ), You can dynamically load service implementation classes .

Class diagram

file

Through the class diagram, we can analyze , ServiceLoader Realized Iterable Interface , Provides the function of iteration .

and ServiceLoader Delegate the implementation of the iteration to LazyIterator .

LazyIterator Provides the ability to delay iterations , When there is a need , To load .

stay Skywalking Use in modules

Interface definition

org.apache.skywalking.oap.server.library.module.ModuleDefine

package org.apache.skywalking.oap.server.library.module;
import java.lang.reflect.Field;
import java.util.Enumeration;
import java.util.Properties;
import java.util.ServiceLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A module definition.
*/
public abstract class ModuleDefine implements ModuleProviderHolder {
private static final Logger LOGGER = LoggerFactory.getLogger(ModuleDefine.class);
private ModuleProvider loadedProvider = null;
private final String name;
public ModuleDefine(String name) {
this.name = name;
}
/**
* @return the module name
*
*/
public final String name() {
return name;
}
/**
* @return the {@link Service} provided by this module.
*/
public abstract Class[] services();
/**
* Run the prepare stage for the module, including finding all potential providers, and asking them to prepare.
*
* @param moduleManager of this module
* @param configuration of this module
* @throws ProviderNotFoundException when even don't find a single one providers.
*/
void prepare(ModuleManager moduleManager, ApplicationConfiguration.ModuleConfiguration configuration,
ServiceLoader<ModuleProvider> moduleProviderLoader) throws ProviderNotFoundException, ServiceNotProvidedException, ModuleConfigException, ModuleStartException {
// etc...
}
// etc...
@Override
public final ModuleProvider provider() throws DuplicateProviderException, ProviderNotFoundException {
if (loadedProvider == null) {
throw new ProviderNotFoundException("There is no module provider in " + this.name() + " module!");
}
return loadedProvider;
}
}

Interface implementation

org.apache.skywalking.oap.server.library.module.BaseModuleA

package org.apache.skywalking.oap.server.library.module;
public class BaseModuleA extends ModuleDefine {
public BaseModuleA() {
super("BaseA");
}
// Need to provide service interface
@Override
public Class<? extends Service>[] services() {
return new Class[] {
ServiceABusiness1.class,
ServiceABusiness2.class
};
}
public interface ServiceABusiness1 extends Service {
void print();
}
public interface ServiceABusiness2 extends Service {
}
}

META-INF Definition

META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine

org.apache.skywalking.oap.server.library.module.BaseModuleA

Usage mode

org.apache.skywalking.oap.server.library.module.ModuleManager#init

 /**
* Init the given modules
*/
public void init(ApplicationConfiguration applicationConfiguration) /* etc... */ {
// SPI Mechanism loading
ServiceLoaderModuleDefine> moduleServiceLoader = ServiceLoader.load(ModuleDefine.class);
// Iterator gets
for (ModuleDefine module : moduleServiceLoader) {
// do something
// etc...
}
// etc...
}

The source code parsing

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.AccessController;
import java.security.AccessControlContext;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
public final class ServiceLoader<S> implements Iterable<S> {
// Directory prefix
private static final String PREFIX = "META-INF/services/";
// Object to be loaded Class object
private final Class<S> service;
// Class loader
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// Load object cache ( Sort by instantiation order )
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// Lazy load iterator currently in use
private LazyIterator lookupIterator;
// heavy load
public void reload() {
// Clear the load object cache
providers.clear();
// Reset lazy load iterator
lookupIterator = new LazyIterator(service, loader);
}
// Direct creation of ServiceLoader object , Only through loadXXX obtain ServiceLoader object
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? 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 line in the configuration file , If there is no comment , Then it is added to the class name list
private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> 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;
}
// Parse configuration file , Returns a list of implementation class names
private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError {
InputStream in = null;
BufferedReader r = null;
ArrayList<String> 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)) >= 0);
} catch (IOException x) {
fail(service, "Error reading configuration file", x);
} finally {
try {
if (r != null) r.close();
if (in != null) in.close();
} catch (IOException y) {
fail(service, "Error closing configuration file", y);
}
}
return names.iterator();
}
// Lazy load iterators , Provides the ability to delay iterations , When there is a need , To load
private class LazyIterator implements Iterator<S> {
// Object to be loaded Class object
Class<S> service;
// Class loader
ClassLoader loader;
// Profile list
Enumeration<URL> configs = null;
// Iterator for the list of class names in the configuration file of the current iteration
Iterator<String> pending = null;
// The name of the next implementation class
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
// Is there a next Service
private boolean hasNextService() {
if (nextName != null) {
return true;
}
// Load all configuration files
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
// When the current class name list is iterated , Load the next configuration file
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
// Get the next class name
nextName = pending.next();
return true;
}
// Get the next one Service
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// Class name -> Class 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 {
// Instantiation
S p = service.cast(c.newInstance());
// Add to cache
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service, "Provider " + cn + " could not be instantiated", x);
}
throw new Error(); // This cannot happen
}
// iterator , Whether there is the next element
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
// Authorized resources
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
// iterator , Get the next element
public S next() {
if (acc == null) {
return nextService();
} else {
// Authorized resources
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
// Deleting... Is not supported
public void remove() {
throw new UnsupportedOperationException();
}
}
// Iterator implementation , If there is a cache, get it from the cache , If not, load... From the lazy load iterator
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
// By class Class Object and class loading , obtain ServiceLoader
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}
// By class Class object , obtain ServiceLoader
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
// By class Class Object and extension class loaders , obtain ServiceLoader
public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
ClassLoader prev = null;
while (cl != null) {
prev = cl;
cl = cl.getParent();
}
return ServiceLoader.load(service, prev);
}
public String toString() {
return "java.util.ServiceLoader[" + service.getName() + "]";
}
}

PS: JDK  Provided SPI  Mechanism , You must use iterators to traverse to get the required implementation , and Dubbo SPI Can pass #getExtension  Gets the specified implementation class .

summary

Through source code analysis , You can see that Skywalking I didn't define my own SPI Mechanism , But read deeply Skywalking After the use scenario , Discovery use JDK Provided SPI There's nothing wrong with the mechanism .

Personally think that , Any technology should be selected according to the scene , What suits is the best , If there were no such complex needs , There's no need to look like dubbo  equally , Define your own SPI  Mechanism .

Reference documents

Please bring the original link to reprint ,thank
Similar articles

2021-10-14

2021-10-14

2021-10-14

2021-10-14

2021-10-14

2021-10-14

2021-10-14