SPI (Service Provider interface) mechanism

Keywords: Spring Java JDK jvm

1, Loading mechanism of class

The loader loading of classes in Java comes from files, networks, source files, etc. By default, the class loader of the jvm uses the parent delegation mechanism, Bootstrap ClassLoader, Extension ClassLoader, and attachment classloader (system classloader). Each class loader loads files in the specified location. It can inherit java.lang.classloader custom class loader.        

Bootstrap ClassLoader: responsible for loading the class files in the rt.jar package of JDK, which is the parent class of all classes
Extension ClassLoader: the extension class library responsible for loading Java loads classes from the jre/lib/ect directory or the directory specified by the java.ext.dirs system property. It is the parent class loader of System ClassLoader
System ClassLoader(Application ClassLoader): responsible for loading class file from classpath environment variable

 

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // Verify loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //Parent delegation mechanism
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    //Play by yourself
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
-------------------------------------NB Dividing line-----------------------------------
public Enumeration<URL> getResources(String name) throws IOException {
        @SuppressWarnings("unchecked")
        Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
        //Get resources through classLoader
        if (parent != null) {
            tmp[0] = parent.getResources(name);
        } else {
            tmp[0] = getBootstrapResources(name);
        }
        tmp[1] = findResources(name);

        return new CompoundEnumeration<>(tmp);
    }

The thread context class loader can be obtained through Thread.currentThread().getClassLoader() and Thread.currentThread().getContextClassLoader(). The default thread is AppClassLoader.

Two, SPI

Breaking the parental delegation model, the most basic class of jdk is the user's api call, which is also called by api users, such as spi.

SPI Brief introduction of mechanism SPI The full name is Service Provider Interface,It is mainly used in manufacturers' custom components or plug-ins. stay java.util.ServiceLoader There is a more detailed introduction in the document of. Simple summary java SPI Mechanism idea: the abstract modules in our system often have many different implementation schemes, such as the log module xml Analysis module jdbc Module, etc. In the object-oriented design, we generally recommend the interface programming between modules, and there is no hard coding between modules. Once a specific implementation class is involved in the code, it violates the pluggable principle. If you need to replace an implementation, you need to modify the code. In order to realize the dynamic specification of module assembly, a service discovery mechanism is needed. Java SPI It is to provide a mechanism for finding a service implementation for an interface. A bit similar. IOC The idea is to move the assembly control right out of the program, which is particularly important in modular design.

https://blog.csdn.net/qq_41359684/article/details/99774452

1.JDK implementation

Create a new META-INF/services / configuration file under classpath. The file name is the reference of the interface class, and the content is the reference of the implementation class of the interface.

    @Test
    public void testSpi() throws IOException {
        //Get through ServiceLoader
        ServiceLoader<SpiDemo> loader=ServiceLoader.load(SpiDemo.class);
        for(SpiDemo demo:loader){
            System.out.println(demo.getName());
        }
    }
------------------------------NB Dividing line-----------------------------------------
public final class ServiceLoader<S>
    implements Iterable<S>
{
    //Default specified path META-INF/services/
    private static final String PREFIX = "META-INF/services/";

    ......NB Ellipsis...............
    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);
    }
..................NB Ellipsis.............
    while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                //Read the contents of the configuration file and then use reference to instance it through class.forname().
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
..................NB Ellipsis.............

2. spi in spring

In the process of spring boot auto loading, all jar s under classpath will be loaded to search META-INF/spring.factories. Then load through spring factors loader, and enter the process of loading bean s after loading.

public final class SpringFactoriesLoader {
    //Specified path
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();

..........................NB Ellipsis.............................
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    //Parsing the spring.factories file (in the form of key/value)
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            //Put the reference in the file into the map
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
        Assert.notNull(factoryType, "'factoryType' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
        }

        List<T> result = new ArrayList(factoryImplementationNames.size());
        Iterator var5 = factoryImplementationNames.iterator();

        while(var5.hasNext()) {
            String factoryImplementationName = (String)var5.next();
            result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
        }

        AnnotationAwareOrderComparator.sort(result);
        return result;
    }

It will eventually be loaded in the AutoConfigurationImportSelector class.

/**
 * AutoConfigurationImportSelector Implemented beanclassloaderaware / resourceloaderaware / beanfactorylaware / environmentaware / deferredimportselector
 * These things.
 * DeferredImportSelector
 */
public class AutoConfigurationImportSelector implements , BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

	private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();

	private static final String[] NO_IMPORTS = {};

	private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);

	private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
    //beanFactory can be obtained by implementing BeanFactoryAware
	private ConfigurableListableBeanFactory beanFactory;
	//Environment can be obtained by implementing EnvironmentAware
	private Environment environment;
	//Implement BeanClassLoaderAware to obtain ClassLoader
	private ClassLoader beanClassLoader;
	//ResourceLoaderAware can get ResourceLoader
	private ResourceLoader resourceLoader;

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		//Load "meta-inf /" + "spring autoconfigure metadata. Properties"
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		//Load
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
        
		return new AutoConfigurationEntry(configurations, exclusions);
	}
    ....................NB.................................
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
			Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
					() -> String.format("Only %s implementations are supported, got %s",
							AutoConfigurationImportSelector.class.getSimpleName(),
							deferredImportSelector.getClass().getName()));
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

 

Published 2 original articles, won 1 praise and 266 visitors
Private letter follow

Posted by xXx_Hobbes_xXx on Sat, 01 Feb 2020 09:01:24 -0800