Reading source code may not improve my coding skills, but at least I feel comfortable using it.

preface

I recently came across the word Dubbo SPI while browsing Dubbo’s official website. JAVA has an SPI mechanism. Curiosity made me wonder what it was.

JAVA mechanism of SPI

What if we want to load a class dynamically?

  • Call the class.forname (” cn.test.hello “) method
  • Call a classloader.loadClass (” cn.test.hello “) method

The advantage of dynamic loading is that it can be loaded at run time on demand, and whatever classes are needed can be loaded without error at compile time. The advantage of this is that we can dynamically configure what classes are loaded at runtime.

SPI, or Service Provider Interface, is a Service discovery mechanism. It loads classes configured in the meta-INF /services folder in the ClassPath path.

1. How to use it

Define an interface

public interface HellloService {
    public void sayHello(a);
}
Copy the code

Define two implementation classes:

public class ChineseHello implements HellloService {
    @Override
    public void sayHello(a) {
        System.out.println("Say hello in Chinese"); }}public class EnglishHello implements HellloService {
    @Override
    public void sayHello(a) {
        System.out.println("English hello"); }}Copy the code

Next we create a meta-INF /services folder and create a new file with the fully qualified name of the interface

com.service.hi.servicehi.spi.ChineseHello
com.service.hi.servicehi.spi.EnglishHello
Copy the code

The ServiceLoader is then used to dynamically load the implementation class of the interface and call its methods at runtime

public class SpiMain {
    public static void main(String[] args) {
        ServiceLoader<HellloService> services = ServiceLoader.load(HellloService.class);
        for(HellloService hellloService: services){ hellloService.sayHello(); }}}Copy the code
Chinese say hello English helloCopy the code

Thus, SPI is a dynamic loading mechanism implemented by a combination of “interface-based programming + policy pattern + configuration file”. A ServiceLoader is a dynamically loaded utility class.

So:

  • At a large scale, SPI is a service discovery mechanism,
  • At its simplest, SPI is a utility class with configurable dynamically loaded classes.

2. Source code analysis

2.1 ServiceLoader

ServiceLoader#load static method.

public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class service, ClassLoader loader)
{
        return new ServiceLoader<>(service, loader);
}
Copy the code

It can be seen that

  • The ServiceLoader#load static method calls another overloaded load method and passes the current thread’s ClassLoader as an argument by default.
  • As can be seen from the load of the two parameters, we can specify its ClassLoader
  • The load static method ends up with a new ServiceLoader instance.

Let’s look at the constructor of the ServiceLoader.

 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();
}
public void reload(a) {
     providers.clear();
     lookupIterator = new LazyIterator(service, loader);
}
Copy the code

Did you find that the configuration file was not loaded?

In fact, ServiceLoader uses lazy loading, which means that the configuration file is loaded while we are traversing. A LazyIterator is a LazyIterator.

2.2 LazyIterator
public S next(a) {
       if (acc == null) {
          returnnextService(); }}private S nextService(a) {
            if(! hasNextService())// Determine if there is another element
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;// Fully qualified name of the next implementation classClass<? > c =null;
            // Use reflection to get the implementation Class's Class object
            c = Class.forName(cn, false, loader);  
            // Create an object
            S p = service.cast(c.newInstance());
            // Put it in the cacheproviders.put(cn, p); returnreturn p;      
 }
 private boolean hasNextService(a) {
            if(nextName ! =null) {
                return true;
            }
            if (configs == null) {
                try {
                	// Get the file name
                    String fullName = PREFIX + service.getName();
                    // Load the corpus URL
                    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;
                }
                // Parse the file
                pending = parse(service, configs.nextElement());
            }
            // Assign the fully qualified name of the next implementation class
            nextName = pending.next();
            return true;
}
Copy the code

Process:

  1. Concatenate the file location according to the fully qualified interface name, combined with meta-INF /services/. This is a death sentence
  2. Use ClassLoader to load file resources
  3. Resolve which implementation classes of the interface are configured in the corresponding file
  4. Instantiate using reflection based on the fully qualified name of the parsed class
  5. Put it in cache.
  6. return

Again verified:

  • SPI is essentially a dynamically loaded class mechanism
  • ServiceLoader is a utility class that dynamically loads classes
  • The bottom layer uses the usual Class,ClassLoader

3. Familiar but unfamiliar application scenarios have problems

SPI must be familiar to us. Previously we had to load the Driver by hand with class.forname (” com.mysql.jdbc.driver “).

I don’t have to write it now, but I’m using the SPI technique.

4. There are problems

  • When we want to find a class, we have to go through it, not really load on demand,
  • It’s not safe in multiple threads

Spring SPI

SpringFactoriesLoader

In fact, when I saw SPI for the first time, I suddenly felt very familiar, as if I had seen it before in Spring.

Think about it, the most classic is not the SpringFactoriesLoader

SpringFactoriesLoader

Configuration file folder directorypublic static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

// Load all the configurations in meta-INF/spring.Factories.
public static List<String> loadFactoryNames(Class
        factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
	}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if(result ! =null) {
			return result;
		}

		try {
			// Load the file resource URLEnumeration<URL> urls = (classLoader ! =null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					List<String> factoryClassNames = Arrays.asList(
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
					result.addAll((String) entry.getKey(), factoryClassNames);
				}
			}
			// Put it in the cache
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex); }}Copy the code

spring.factories

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
Copy the code

Unlike the JAVA SPI,

  • Spring Spi gets fixedMETA-INF/spring.factoriesConfiguration in the file. The JAVA SPI is a file named the fully qualified path to an interface that needs to be defined by the developer
  • Spring Spi is configured in the k-V format,
  • When the Spring SPI first loads the configuration file, it parses all the configurations from the Spring. factories configuration file into the cache, and fetches values from the cache by Key

Spring SPI is better designed than JAVA SPI. It is essentially an advanced encapsulation of the Class and ClassLoader.

Dubbo SPI

After looking at JAVA SPI and thinking about Sprng SPI, I seem to know what Dubbo SPI looks like.

ExtensionLoader

  • Dubbo uses ExtensionLoader as a tool for dynamically loading configurations.

  • The Dubbo configuration file is placed in the “meta-INF/Dubbo /” directory and named after the full name of the specific extension interface, similar to the Java SPI

  • Dubbo SPI also uses a K-V configuration, similar to Spring SPI

  • ExtensionLoader provides more methods and rich fetching capabilities

  • Dubbo SPI also adds features such as IOC and AOP

  • It is essentially an advanced encapsulation of the Class and ClassLoader.

Dubbo is similar to both JAVA SPI and Spring SPI. Maybe Dubbo was designed with JAVA SPI and Spring SPI in mind

This article does not cover more content of Dubbo SPI, just to give my understanding of SPI, for the future to read Dubbo source.

conclusion

Whether it’s JAVA SPI,Spring SPI, or Dubbo SPI. It’s all about high-level encapsulation of reflection, with Class and ClassLoader at the core.


Recommended reading:

SpringCloud source code read 0-SpringCloud essential knowledge

SpringCloud source code read 1-EurekaServer source code secrets

SpringCloud source code read 2-Eureka client secrets

3 Ribbon Load Balancing

4 Ribbon Load Balancing

Sending AN HTTP request (1): There are several ways to send an HTTP request

Sending an HTTP request (2): The RestTemplate sends an HTTP request

The @loadBalanced annotation RestTemplate has load balancing capabilities

Welcome everyone to pay attention to my public number [source code action], the latest personal understanding timely.