Implementation principle and difference of SPI in JDK and Spring

Introduction to SPI

service provider interface JDK built-in service discovery mechanism

A Mechanism for Dynamic Replacement Discovery

Enter image description

This picture is drawn by the author (https://www.jianshu.com/p/46aa69643c97)

How to use

Method to realize:

  • Write the interface provided by the service to the outside world
public interface DriverService {
    String getName();
}
  • Concrete implementation, inherit the corresponding interface
public class JavaDriverImpl implements DriverService {
    @Override
    public String getName() {
        return "java implement";
    }
}
  • Write the specific implementation class package name + class name of META-INF/service

Enter image description

com.chengjue.spi.JavaDriverImpl
  • Compile jar packages to provide external services

User:

  • Reference related dependency jar packages
  • Use ServiceLoader to load using
 public static void main(String[] args) {
        ServiceLoader<DriverService> serviceLoader = ServiceLoader.load(DriverService.class);
        for (DriverService driverService: serviceLoader){
            System.out.println(driverService.getName());
        }
    }

Implementation principle

When we use it, we use ServiceLoader to load services. Let's take a look at how ServiceLoader is implemented.

ServiceLoader is a util provided by JDK, under the java.uyil package

We can see his introduction A simple service-provider loading facility. Used to load services

public final class ServiceLoader<S> implements Iterable<S>

We can see that he is a final type and cannot be modified by inheritance. At the same time, it implements the Iterable interface, which is convenient for us to use the iterator to take out all the implementation classes.

Next we can see a familiar constant, this is the path where we defined the implementation class earlier

private static final String PREFIX = "META-INF/services/";

The following is the specific implementation of the load 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<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

  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() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

The above code is posted according to the calling order for easy reading

Through the tracking code, it is found that in the reload method, it is implemented through an inner class LazyIterator. Next, let's look at the constructor of LazyIterator and pass in Class and ClassLoader.

The following is the implementation of the iterator method of ServiceLoader. Here, we will go to a new Iterator. First, there is a provider cache in ServiceLoader. Every time we operate, we will search in the cache first, otherwise we will use LazyIterator to search.

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

        };
    }

The following is the specific processing of LazyIterator

 public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
   private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    //通过PREFIX(META-INF/services/)和类名 获取对应的配置文件,得到具体的实现类
                    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;
        }


        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

         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
        }



Summarize

Through the above code study, I found that it still obtains the instance of the specific implementation class through reflection. We just declare the specific implementation to be exposed for external use in the META-INF/services/ file by means of the SPI definition. Therefore, it is still very important to master reflection. Whether it is spring or some other frameworks, the use of reflection can often be seen

Use in existing frameworks

In fact, I understand the SPI mechanism because I found it when I looked at the SpringBoot code recently. We know that many configurations and implementations in SprngBoot have default implementations. We only need to modify part of the configuration, such as the database configuration, we only need to write in the configuration file. The corresponding url, username, password can be used. In fact, what he uses here is the SPI method.

However, Spring uses the same principle as in the JDK.

  • The tool class used by JDK is ServiceLoader
  • The class used in Spring is SpringFactoriesLoader, in the org.springframework.core.io.support package

the difference:

  • The file path is different and the spring configuration is placed in META-INF/spring.factories

Enter image description

  • The specific implementation steps are different, but the principle is the same, and the reflection mechanism is used.

Implemented in Spring

public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
		Assert.notNull(factoryClass, "'factoryClass' must not be null");
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
		if (logger.isTraceEnabled()) {
			logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
		}
		List<T> result = new ArrayList<>(factoryNames.size());
		for (String factoryName : factoryNames) {
			result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
		}
		AnnotationAwareOrderComparator.sort(result);
		return result;
	}

This is how learning is. If you can understand the principle of SPI in JDK, it will be easier to understand if you look at the related implementation in Spring.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325230670&siteId=291194637