SPI mechanism learning in Java and dubbo

 
For the SPI mechanism of java, you can refer to: https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html
 
In order to realize that the module is not dynamically specified in the program when it is assembled, it is necessary to provide a service discovery mechanism. The mechanism to find service implementation for an interface is to transfer the control of the assembly to the outside of the program. In the modular design, this Mechanisms are especially important.
 
The specific convention of Java SPI (Service Provider Interface) is as follows: After the service provider provides an implementation of the service interface, a file named after the service interface is created in the META-INF/services/ directory of the jar package at the same time. The content in the file is the concrete implementation class that implements the service interface.
 
Java provides a tool class for service implementation lookup: java.util.ServiceLoader.
 


 
 
This example is used to demonstrate the definition of the service declaration in the maven environment, but it cannot be debugged and started under the idea...
 
Specify the directory META-INF/services directory under resources (written in the code):
 
public final class ServiceLoader<S
    implements Iterable<S
{
 
    private static final String PREFIX = "META-INF/services/";
 
 
 
Note that the file name of the service declaration needs to be defined as: example.spi.service.Service, which is consistent with the interface name, and the contents include:
 
example.spi.service.PrintServiceImpl
example.spi.service.EchoServiceImpl
  
 
Different implementation classes need to be separated by newlines. In the main method of SpiMain, use ServiceLoader for loading operations:
 
public static void main(String[] args) {
    ServiceLoader<Service> serviceLoader = ServiceLoader.load(Service.class);
 
    for (Service service : serviceLoader) {
        service.printInfo();
    }
}
 
 
In the load method of ServiceLoader, ServiceLoader.LazyIterator will be initialized, which implements the standard iterator interface Iterator (and hasNext, next methods). The hasNextService() method will load PREFIX from the current ClassLoader ("META-INF/services/ ") + service name (class name),
 
 
private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    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);
        }
    }
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
}
 
 
 
In the parse method, the file is parsed line by line. If there is a corresponding implementation in the next step, the method returns true, and then you can access the nextService method to get the next service implementation:
 
private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
        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 {
        S p = service.cast(c.newInstance());
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
            "Provider " + cn + " could not be instantiated",
            x);
    }
    throw new Error();          // This cannot happen
}
 
 
 
Instantiate nextName (the class name implemented in the next line), pay attention to the need to implement the corresponding interface and have a no-argument constructor, and put the implementation in the Map of providers so that it can be used directly next time (unless the reload operation is performed, it will not be update the table, while the reload operation is initialized when the ServiceLoader starts).
 
public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}
 
 
Although ServiceLoader also uses lazy loading, it can only be obtained by traversing all acquisitions, loading and instantiating all the implementation classes of the interface, and it can only be acquired in the form of Iterator, not based on a certain parameter.
 
Reference: https://my.oschina.net/pingpangkuangmo/blog/508963  to analyze the service system of dubbo.
 
This method is widely used in dubbo for service discovery and service registration, and some extensions are implemented to implement the com.alibaba.dubbo.common.extension.ExtensionLoader class, and the directory of the internal registration service is migrated to META-INF/dubbo/ internal type:
 
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
 
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
  
 
In ExtensionLoader, not only will the services in it be initialized, but also other referenced services can be set like IOC. For this part, please refer to the ExtensionLoader.injectExtension(T instance) class. The type of injection must be the start method of set. There is only one parameter, the method is public, and the parameter must be an interface (only the interface ExtensionFactory can search for the service implementation class according to this interface).
 
private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            for (Method method : instance.getClass().getMethods()) {
                if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                    Class<?> pt = method.getParameterTypes()[0];
                    try {
                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("fail to inject via method " + method.getName()
                                + " of interface " + type.getName() + ": " + e.getMessage(), e);
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}
 
 
If there are multiple implementations of the interface, the strategy adopted at this time is not to inject a specific implementer, but to inject a dynamically generated implementer. The logic of the dynamically generated implementer is determined and can be Different parameters are used to implement corresponding methods with different implementers. The class of this dynamically generated implementer is the Class<?> cachedAdaptiveClass of ExtensionLoader
 
When looking for SPI Annotation, use the extension method configured by dubbo for registration. For example, when obtaining AdaptiveExtensionFactory, the constructor used is used to load extension points:
 
public AdaptiveExtensionFactory() {
    ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
    List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
    for (String name : loader.getSupportedExtensions()) {
        list.add(loader.getExtension(name));
    }
    factories = Collections.unmodifiableList(list);
}
  
The getExtension() method can find a specific objectFactory (SPI, Spring) and can find a specific bean from the corresponding file declaration and the spring container.
 
For ExtensionFactory, the extensionClass will be loaded from three places:
 
private Map<String, Class<?>> loadExtensionClasses() {
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if(defaultAnnotation != null) {
        String value = defaultAnnotation.value();
        if(value != null && (value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);
            if(names.length > 1) {
                throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                        + ": " + Arrays.toString(names));
            }
            if(names.length == 1) cachedDefaultName = names[0];
        }
    }
 
    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}
 
 
Classification / META-INF / dubbo / internal, / META-INF / dubbo /, META-INF / services.
 
It can be seen that although the extension mechanism of dubbo is similar to SPI, it adds other functions. For example, services can be obtained according to the interface name. The service declaration file supports the method of A=B. At this time, A is the name and B is the implementation class; support Extending the IOC dependency injection function can inject related services for dependencies between services and ensure singletons.
 
 

Guess you like

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