SPI stands for Service Provider Interface, which literally translates to “Service Provider Interface.” It sounds awkward, so I’ll try to translate it as “Service Provider Interface.”

As we all know, an interface can have many implementations. Search, for example, can be a search of the system’s hard disk or a search of a database. The system designer, to reduce coupling, does not want to hardcode the specific search method, but rather wants the service provider to choose which search method to use, which is an option for using the SPI mechanism.

The SPI mechanism is widely used in various open source frameworks, such as:

  1. ExtensionLoader in Dubbo, which everyone is familiar with, can add some custom plug-in functions through these extension points, such as adding filter to achieve whitelist access, interface traffic limiting and other functions; Or it can directly replace its native protocol, transport, etc
  2. When developing the plugin for IDEA Intellij, you need to define a/meta-INF /plugin.xml file. There are many places in this plugin.xml to configure serviceInterface and serviceImplementation, which is also an SPI mechanism through which, Idea allows plugin developers to use both the APIS provided by the underlying SDK and the customized functions, with relatively low coupling. ServiceLoader in JDK was directly used in intellij plug-in development
  3. Spring also makes extensive use of the SPI mechanism, and this article examines part of it.

The SPI in the JDK

SPI is probably familiar to everyone, so let’s go over the SPI mechanism in Java with a very simple example.

  1. Define a Search interface Search
 package com.north.spilat.service;
 import java.util.List;
 public interface Search {
     List<String> search(String keyword);
 }
Copy the code
  1. Implement interfaces to query from the database
    package com.north.spilat.service.impl;
    import com.north.spilat.service.Search;
    import java.util.List;
    /**
     * @author lhh
     */
    public class DatabaseSearch implements Search {
        @Override
        public List<String> search(String keyword) {
            System.out.println("now use database search. keyword:" + keyword);
            returnnull; }}Copy the code
  1. Implement interface to query from file system
 package com.north.spilat.service.impl;
 import com.north.spilat.service.Search;
 import java.util.List;
 /**
  * @author lhh
  */
 public class FileSearch implements Search {
 
     @Override
     public List<String> search(String keyword) {
         System.out.println("now use file system search. keyword:" + keyword);
         returnnull; }}Copy the code
  1. Create a directory in SRC \main\ Resources Meta-inf \ services \ com. North. Spilat. Service. Search, then in com. North. Spilat. Service. The Search below to create two files, in the concrete implementation of the interface above fully qualified name for the file name of a class, that is: Com. North. Spilat. Service. Impl. DatabaseSearch com. North. Spilat. Service. The impl. FileSearch directory the whole project is as follows:

  2. Create a new main method to test it

 package com.north.spilat.main;
 import com.north.spilat.service.Search;
 import java.util.Iterator;
 import java.util.ServiceLoader;
 public class Main {
     public static void main(String[] args) {
         System.out.println("Hello World!");
         ServiceLoader<Search> s = ServiceLoader.load(Search.class);
         Iterator<Search> searchList = s.iterator();
         while (searchList.hasNext()) {
             Search curSearch = searchList.next();
             curSearch.search("test"); }}}Copy the code

Run it and the output looks like this:

Hello World!
now use database search. keyword:test
now use file system search. keyword:test
Copy the code

As you can see, SPI mechanism has defined service loading process framework, you only need to in accordance with the contract, under the meta-inf/services directory, with the fully qualified name of the interface name to create a folder (com) north. Spilat. Service. The Search). Folder put a concrete implementation class of the fully qualified name (. Com. North spilat. Service. Impl. DatabaseSearch), the system can according to these files, load different implementation classes. This is the general process of SPI.

ServiceLoader class analysis

Back to the main method above, there’s nothing special about it except serviceloader.load (search.class);

ServiceLoader class is a utility class, according to the meta-inf/services/xxxInterfaceName the filename below, specific implementation class loading.

Load (search.class), let’s take a look at this class, the following is mainly paste code, analysis is in the code comment.

  1. As you can see, there’s not a lot of logic in there, the main logic is handed over to lazyiterators and the like
/* * entry, Public static <S> ServiceLoader<S> Load (Class<S> service) {ClassLoader cl = Thread.currentThread().getContextClassLoader();returnServiceLoader.load(service, cl); Public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader) {returnnew 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(); } /** * directly instantiates a lazy-loaded iterator */ public voidreload() {
     providers.clear();
     lookupIterator = new LazyIterator(service, loader);
 }
Copy the code
  1. LazyIterator is an iterator that only cares about hasNext() and next(). HasNext () simply calls hasNextService(). Needless to say, nextService() was called only inside next();
 private boolean hasNextService() {
     if(nextName ! = null) {// nextName is not empty, indicating that the service is loaded and not emptyreturn true; } // configs is all resources named PREFIX + service.getName()if(configs == null) {try {// PREFIX is/meta-INF /services // service.getName() is the fully qualified name of the interface String fullName = PREFIX + service.getName(); // loader == null, indicating that it is the bootstrap class loaderif (loader == null)
                 configs = ClassLoader.getSystemResources(fullName);
             else// Load all file resources by name configs = loader.getResources(fullName); } catch (IOException x) { fail(service,"Error locating configuration files", x); }} // Loop through all the resources. Pending is used to store the loaded implementation classwhile((pending == null) || ! pending.hasNext()) {if(! Configs.hasmoreelements ()) {// All files have been traversedreturn false; } // Parse calls parseLine. Parse all files under each PREFIX + service.getName() directory // 2. Pending = parse(service, configs.nextelement ()) pending = parse(service, configs.nextelement ()) NextName = pending.next(); nextName = pending.next();return true;
 }
Copy the code
  1. Take a look at what nextService did
 private S nextService() {// checkif(! hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<? > c = null; C = class.forname (cn,false, loader);
     } catch (ClassNotFoundException x) {
         fail(service,"Provider " + cn + " not found"); } // is a subclass of service, or the same classif(! service.isAssignableFrom(c)) { fail(service,"Provider " + cn  + " not a subtype"); P = service.cast(c.newinstance ()); // 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
 }
Copy the code

As you can see from the above code, lazy loading means waiting until hasNext() is called to look up the service and next() is called to instantiate the service class.

/ meta-INF /services/ XXX, so the ServiceLoader can load different implementations as the service provider wishes, instead of hard coding the logic. Thus achieving the purpose of decoupling.

Of course, you may not see from the simple example above how SPI achieves this decoupling effect. So let’s take a look at how SPI mechanisms are used in open source frameworks to decouple. Experience the charm of SPI.

The SPI springboot

As a programmer, we can study more open source framework, because these open source code is not know how many times every day, so their code from design to implementation, are very good, we can learn a lot from it.

The Spring framework has been the leader of the open source community for many years. The design of its source code is also known for its elegance, ultra-extensibility and ultra-low coupling.

So how does it decouple? The extension point mechanic is one of them

Start with the magic Starter

When I first got into Springboot, I really felt that various spring-XX-starter and XX-spring-starter were very magical. Why does adding a dependency to a POM file introduce a complex plug-in? With this question in mind, I began my journey into science.

Dubbo framework is used by many companies in China, so here, we take Dubo-spring-boot-starter as an example to see how efficient decoupling is in Springboot.

Recall what we would need to do if we were to introduce the Dubbo module in the Springboot project.

  1. Introduce the dubo-spring-boot-starter dependency in poM files.
<dependency> <groupId>com.alibaba.spring.boot</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> The < version > 2.0.0 < / version > < / dependency >Copy the code
  1. Set dubbo parameters in the application.properties file
spring.dubbo.server=true
spring.dubbo.application.name=north-spilat-server

#
spring.dubbo.registry.id=defaultRegistry
#Spring. Dubbo. Registry. Address = 127.0.0.1#
spring.dubbo.registry.port=2181
#
spring.dubbo.registry.protocol=zookeeper
#
spring.dubbo.protocol.name=dubbo
#
spring.dubbo.protocol.port=20881
#
spring.dubbo.module.name=north-spilat-server
#
spring.dubbo.consumer.check=false
#
spring.dubbo.provider.timeout=3000
#
spring.dubbo.consumer.retries=0
#
spring.dubbo.consumer.timeout=3000
Copy the code
  1. Annotate the spring-boot boot class
package com.north.spilat.main;

import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

/**
 * @author lhh
 */
@SpringBootApplication
@ComponentScan(basePackages = {"com.north.*"}) @EnableDubboConfiguration public class SpringBootMain { public static void main(String[] args) { SpringApplication.run(SpringBootMain.class, args); }}Copy the code
  1. Define the interface, implement it, and invoke it

interface

package com.north.spilat.service;
/**
 * @author lhh
 */
public interface DubboDemoService {
    String test(String params);
}
Copy the code

Implementing an interface

package com.north.spilat.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.north.spilat.service.DubboDemoService;
import org.springframework.stereotype.Repository;

/**
 * @author lhh
 */
@Service
@Repository("dubboDemoService") 
public class DubboDemoServiceImpl implements DubboDemoService {
    @Override
    public String test(String params) {
        return System.currentTimeMillis() + "-"+ params ; }}Copy the code

Write a controller calling the Dubbo interface

package com.north.spilat.controller;

import com.north.spilat.service.DubboDemoService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author lhh
 */
@RestController
public class HelloWorldController {
    @Resource
    private DubboDemoService dubboDemoService;

    @RequestMapping("/saveTheWorld")
    public String index(String name) {
        returndubboDemoService.test(name); }}Copy the code

After completing the above 4 steps (zooKeeper, etc.), start the SpringBootMain class, and a SpringBoot project with the Dubbo module is set up. It’s really that simple.

However, there is no such thing as peace and quiet in the world. Only someone carries something for you, and that person is dubo-spring-boot-starter.

Dubbo – spring – the boot – the mystery of the starter


dubbo/com.alibaba.dubbo.rpc.InvokerListener

dubbosubscribe=com.alibaba.dubbo.spring.boot.listener.ConsumerSubscribeListener
Copy the code

The file in this directory is only one line long and looks exactly like the JDK SPI above. Yes, this is an extension point. It is an extension point convention in Dubbo, which is the ExtensionLoader we started with

  1. spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.dubbo.spring.boot.DubboAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboProviderAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboConsumerAutoConfiguration

org.springframework.context.ApplicationListener=\
com.alibaba.dubbo.spring.boot.context.event.DubboBannerApplicationListener

Copy the code

Wow, the file is named after Spring, and there are so many Spring classes in the file. I checked my eyes, and I found the right… But don’t worry, there is a spring. Providers file below

  1. spring.providers
provides: dubbo-spring-boot-starter
Copy the code

Providers: Spring. Providers: Spring. Providers: Spring. So let’s focus on spring.Factories.

Physicists like to reason things out before they do an experiment, arrive at a prediction, and then use the results to confirm or disprove the prediction.

The Spring framework must have a ServiceLoader class that loads the implementation of the interface from the meta-INF/Spring.Factories configuration.

Needless to say, this prediction must be accurate, or I would have written all these words in vain. But how do we prove that our predictions are accurate? Let’s do an experiment.

Springboot boot process

The best way to understand springBoot’s startup process is to read its source code.

Springboot’s code is very “human”, and springBoot tells you exactly what its entry point is: the main method. So, it’s kind of nice to read the SpringBoot code, just go through the main method.

This is a springboot startup. The first is two consecutive overloaded static run methods. The static run method instantiates the SpringApplication object by calling the constructor, which initializes it by calling initialiaze(), instantiates it, and then calls a member method run() to launch it.

As you can see, the main logic for the whole startup process is inside the Initialiaze method and the member Run method.

Take a look at initialiaze() ‘s logic. The following is the same as before, mainly Posting the code and analyzing it in the code comments

   @SuppressWarnings({ "unchecked"."rawtypes"}) private void initialize(Object[] sources) {// Sources is a Configuration class or a main classif(sources ! = null && sources.length > 0) { this.sources.addAll(Arrays.asList(sources)); } // Check whether the web environment // classLoader can load to //"javax.servlet.Servlet", / /"org.springframework.web.context.ConfigurableWebApplicationContext"// These two classes are the web environment this.webEnvironment = deduceWebEnvironment(); / / loading initializers and listeners / / getSpringFactoriesInstances, as the name implies, / / interface is loaded a factory instance, / / it looks like we're looking for"ServiceLoader"thesetInitializers((Collection) getSpringFactoriesInstances(
   			ApplicationContextInitializer.class));
   	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); / / find the main method of class enclosing mainApplicationClass = deduceMainApplicationClass (); }Copy the code

Luck is not bad, “suspect” getSpringFactoriesInstances above the water, to see the logic

/ * * * parameterstypeClass */ private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T>type{/ / direct getSpringFactoriesInstances calling overloaded methodsreturn getSpringFactoriesInstances(type, new Class<? > [] {}); } private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T>type, Class<? >[] parameterTypes, Object... Args) {// get the currentThread's classLoader classLoader classLoader = thread.currentthread ().getcontextclassloader (); Use names and ensure unique to protect against duplicates // Note here that we are looking for"ServiceLoader"Finally a / / is SpringFactoriesLoader Set < String > names = new LinkedHashSet < String > (SpringFactoriesLoader. LoadFactoryNames (type, classLoader)); / / by using Java reflection to instantiate the List < T > instances = createSpringFactoriesInstances (type, parameterTypes, classLoader, args, names); / / according to @ Order annotations to row a sequence AnnotationAwareOrderComparator. Sort (instances); // Return all implementation instances of this interfacereturn instances;
	}
Copy the code

We quickly found the SpringFactoriesLoader we were looking for, and it was a very small class with less code than the JDK ServiceLoader. Well, let’s take a closer look at what’s inside him.

  1. FACTORIES_RESOURCE_LOCATION points to the meta-INF/spring.Factories we mentioned above
  2. LoadFactories, which finds and instantiates the specified interface implementation class from meta-INF /spring.factories, where the lookup is done by calling loadFactoryNames
  3. LoadFactoryNames finds the fully qualified name of the implementation class for a particular interface from the specified location
  4. InstantiateFactory instantiation

This class is springboot “ServiceLoader” in it, and it provides a mechanism that allows service providers to specify some kind of interface implementation (multiple), such as the above ApplicationContextInitializer. Class and Application The listener. class interface, if we want to specify our implementation in our module, or if we want to add one of our implementations to existing code, we can specify it in/meta-INF/spring.Factories. I’m going to write a concrete example in a minute, just to make it a little bit more intuitive.

/** * omit import **/ public abstract class SpringFactoriesLoader {private static final Log Logger = LogFactory.getLog(SpringFactoriesLoader.class); /** * The location to lookforFactories. * Find the location of the factory implementation class * <p>Can be presentinUse meta-INF /spring.factories to open a public static final String FACTORIES_RESOURCE_LOCATION ="META-INF/spring.factories"; Public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) { Assert.notNull(factoryClass,"'factoryClass' must not be null");
		ClassLoader classLoaderToUse = classLoader;
		if(classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } // loadFactoryNames List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);if (logger.isTraceEnabled()) {
			logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
		}
		List<T> result = new ArrayList<T>(factoryNames.size());
		for(String factoryName : FactoryNames) {// Instantiate result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse)); } / / sorting AnnotationAwareOrderComparator. Sort (result);returnresult; } public static List<String> loadFactoryNames(Class<? String factoryClassName = factoryClass.getName(); Try {// Load all the meta-INF /spring.factories resources Enumeration<URL> urls = (classLoader! = null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); List<String> result = new ArrayList<String>();while(urls.hasmoreelements ()) {// A url represents a spring.factories file URL = urls.nextelement (); // Load all attributes, General is XXX interface = impl1, impl2 this form of Properties Properties = PropertiesLoaderUtils. LoadProperties (new UrlResource (url)); // Similar according to the interface name"impl1,impl2"The String String factoryClassNames = properties.getProperty(factoryClassName) // is comma-separated and converted to a list result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); } // Returns a list of implementation class namesreturn result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
					"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); }} /** * Instantiate from the fully qualified name of the class name */ @suppressWarnings ("unchecked") private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader ClassLoader) {try {// Find Class<? > instanceClass = ClassUtils.forName(instanceClassName, classLoader); // Verify whether the interface class or the implementation class of the interface classif(! factoryClass.isAssignableFrom(instanceClass)) { throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]"); } Constructor<? > constructor = instanceClass.getDeclaredConstructor(); ReflectionUtils.makeAccessible(constructor); // Reflection instantiationreturn (T) constructor.newInstance();
		}
		catch (Throwable ex) {
			throw new IllegalArgumentException("Unable to instantiate factory class: "+ factoryClass.getName(), ex); }}}Copy the code

After looking at the SpringFactoriesLoader class, the logic of the Initialize () method is complete. Let’s look at another important method run(String… args)

/** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. * @param args the application arguments (usually passed from  a Java main method) * @returna running {@link ApplicationContext} */ public ConfigurableApplicationContext run(String... StopWatch = new StopWatch(); stopWatch.start(); / / the context springboot ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null; / / configuration configureHeadlessProperty headless mode (); / / start the listener, you can configure to spring. The factories to SpringApplicationRunListeners listeners = getRunListeners (args); listeners.starting(); Try {/ / encapsulation parameters ApplicationArguments ApplicationArguments = new DefaultApplicationArguments (args); // Set environment ConfigurableEnvironment = prepareEnvironment(Listeners, applicationArguments); // Print banner banner printedBanner =printBanner(environment); // Create context context = createApplicationContext(); analyzers = new FailureAnalyzers(context); PrepareContext (context, environment, Listeners, applicationArguments, printedBanner); Refresh Context(context); refresh context (context); refresh context (context); afterRefresh(context, applicationArguments); listeners.finished(context, null); stopWatch.stop();if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			returncontext; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); }}Copy the code

This method is the main logic of springboot boot, a lot of content, if you want to make it clear, I’m afraid to write a few times the article also said not over (give people springboot a little respect for the least good, want to understand an article people thoroughly the whole framework, people don’t face ah). So I won’t go too far here. For this article, just know that the run() method is the main logic to start, and remember context = createApplicationContext(); refreshContext(context); These two lines of code, we’ll see that again in a second.

The principle of dubbo – spring – the boot – the starter

Much has been said, but why does SpringBoot introduce a starter dependency introduce a complex module? Dubo-spring-boot-starter is used here.

Spring-boots-starter: Spring-factories. EnableAutoConfiguration and ApplicationListener are configured.

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.dubbo.spring.boot.DubboAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboProviderAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboConsumerAutoConfiguration

org.springframework.context.ApplicationListener=\
com.alibaba.dubbo.spring.boot.context.event.DubboBannerApplicationListener
Copy the code

The listener knows from the name that it is used to print the banner at startup, so let’s look at where EnableAutoConfiguration is used.

The debug through the main method, and finally in the AutoConfigurationImportSelector class found a line of code: SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())

Which getSpringFactoriesLoaderFactoryClass () is dead write back EnableAutoConfiguration. Class

 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
 		AnnotationAttributes attributes) {
 	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
 			getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
 	Assert.notEmpty(configurations,
 			"No auto configuration classes found in META-INF/spring.factories. If you "
 					+ "are using a custom packaging, make sure that file is correct.");
 	return configurations;
 }

 /**
  * Return the class used by {@link SpringFactoriesLoader} to load configuration
  * candidates.
  * @returnthe factory class */ protected Class<? >getSpringFactoriesLoaderFactoryClass() {
 	return EnableAutoConfiguration.class;
 }
Copy the code

Below can be found that EnableAutoConfiguration. There will be a lot of class, as long as you are in the spring. The fatories configuration, it will give you loading in

  1. this.reader.loadBeanDefinitions(configClasses); ConfigClasses are all the implementation classes that are read in for parsing
  2. RegisterBeanDefinition is registered with beanDefinitionNames
  3. Spring’s refresh () operation, the last step is finishBeanFactoryInitialization (the beanFactory), this step will initialize all singleton, Finally, all BeanDefinitions are read from beanDefinitionNames, including all EnableAutoConfiguration implementations above, and then instantiated
  4. Instantiation EnableAutoConfiguration concrete implementation, will perform these specific implementation class logic, in the case of Dubbo, will initialize the com. Alibaba. The Dubbo. Spring. The boot. DubboAutoConfiguration, com.alibaba.dubbo.spring.boot.DubboProviderAutoConfiguration, Com. Alibaba. Dubbo. Spring. The boot. DubboConsumerAutoConfiguration these three implementation class, took the dubbo start and register to the spring container.

Implement a spring-boot-starter

Once you understand the principles, it’s easy to implement your own starter.

Let’s say I have a component, which is really cool, that has the ability to save the world, and your system comes in, and it has the ability to save the world. How do you get your Spring-boot system to quickly access this awesome component? I implement a starter, and you rely on me as a starter

Start by defining an interface to save the world

package com.north.lat.service; /** * @author LHH */ public interface SaveTheWorldService {/** * save the world * @param name leave name * @return
  */
 String saveTheWorld(String name);
}
Copy the code

An abstract class

package com.north.lat.service;

import lombok.extern.log4j.Log4j;

import java.util.Random;

/**
 * @author lhh
 */
@Log4j
public abstract  class AbstractSaveTheWorldService implements SaveTheWorldService {
    private final static Random RANDOM = new Random();
    private final static String SUCCESS_MSG = "WAOOOOOOO! The hero";
    private final static String FAIL_MSG = "Saving the world is a risky business.";

    @Override
    public String saveTheWorld(String name) {
        int randomInt = RANDOM.nextInt(100);
        String msg;
        if((randomInt +  1) > getDieRate()){
            msg = SUCCESS_MSG +"," + name + "Saved the world!";
        }else{
            msg = FAIL_MSG + "," + name + "You failed. Try again in the next life.";

        }
        log.info(msg);
        returnmsg; } /** * Specifies the mortality rate * @return
     */
    public abstract int getDieRate();
}
Copy the code

The average person trying to save the world has a 99% failure rate

package com.north.lat.service.impl; import com.north.lat.service.AbstractSaveTheWorldService; / ordinary people to save the world * * * * @ author LHH * / public class CommonSaveTheWorldServiceImpl extends AbstractSaveTheWorldService {private final static int DIE_RATE = 99; @Override public intgetDieRate() {
        returnDIE_RATE; }}Copy the code

Saving the world as a hero has a 99% success rate

package com.north.lat.service.impl; import com.north.lat.service.AbstractSaveTheWorldService; / * * * a hero to save the world * @ author LHH * / public class HeroSaveTheWorldImpl extends AbstractSaveTheWorldService {private final static int DIE_RATE = 1; @Override public intgetDieRate() {
        returnDIE_RATE; }}Copy the code

NbAutoConfiguration NbAutoConfiguration NbAutoConfiguration NbAutoConfiguration NbAutoConfiguration NbAutoConfiguration NbAutoConfiguration NbAutoConfiguration NbAutoConfiguration

package com.north.lat; import com.north.lat.service.SaveTheWorldService; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.core.io.support.SpringFactoriesLoader; import java.util.List; /** * @author LHH * Inject the Environment and applicationContext for subsequent operations */ @configuration @ConditionalOnClass(SaveTheWorldService.class) public class NbAutoConfiguration implements EnvironmentAware,ApplicationContextAware,BeanDefinitionRegistryPostProcessor { private Environment environment; private ApplicationContext applicationContext; @Override public voidsetEnvironment(Environment environment) {
            this.environment = environment;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { // Here I loaded all the implementations of SaveTheWorldService from Spring. factories, List<SaveTheWorldService> saveTheWorldServices = SpringFactoriesLoader.loadFactories(SaveTheWorldService.class, this.getClass().getClassLoader()); / / then use BeanDefinitionRegistry registered to BeanDefinitions saveTheWorldServices. ForEach (saveTheWorldService - > { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(saveTheWorldService.getClass()); beanDefinition.setLazyInit(false);
            beanDefinition.setAbstract(false);
            beanDefinition.setAutowireCandidate(true);
            beanDefinition.setScope("singleton");
            registry.registerBeanDefinition(saveTheWorldService.getClass().getSimpleName(), beanDefinition);
        });
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}
Copy the code

The spring.factories from niubility- spring-spring-starter – 1.0-snapshot. jar look like this

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.north.lat.NbAutoConfiguration
com.north.lat.service.SaveTheWorldService=\
com.north.lat.service.impl.CommonSaveTheWorldServiceImpl
Copy the code

The spring-.Factories of Niubility – spring-starter-2.0-snapshot. jar look like this

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.north.lat.NbAutoConfiguration
com.north.lat.service.SaveTheWorldService=\
com.north.lat.service.impl.HeroSaveTheWorldImpl
Copy the code

This completes the project structure as shown below:

So how do we access it? Let’s try this with spilat project:

Rely on JAR packages, which at this time are connected to version 1.0; And that’s it

<dependency> <groupId>com.north.lat</groupId> <artifactId>niubility-spring-starter</artifactId> < version > 1.0 - the SNAPSHOT < / version > < / dependency >Copy the code

Complete access to the so-called refers to, in the spring has registered its SaveTheWorldService all implementation, namely CommonSaveTheWorldServiceImpl (version 1.0) or HeroSaveTheWorldImpl (version 2.0).

Let’s inject a call into the Controller

package com.north.spilat.controller;

import com.north.lat.service.SaveTheWorldService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author lhh
 */
@RestController
public class HelloWorldController {
    @Resource
    private SaveTheWorldService saveTheWorldService;


    @RequestMapping("/saveTheWorld")
    public String index(String name) {
        returnsaveTheWorldService.saveTheWorld(name); }}Copy the code

With version 1.0, the failure rate was 99%, and the results were as follows:

Copy the code
<dependency> <groupId>com.north.lat</groupId> <artifactId>niubility-spring-starter</artifactId> < version > 2.0 - the SNAPSHOT < / version > < / dependency >Copy the code
And if you look at the result, it's perfectCopy the code

In the example above, we simply rely on the JAR package to access or upgrade components, which is truly pluggable and low-coupling. Of course, in actual application scenarios, we may need to add a little configuration, such as the above spring-boot-starter-dubbo, Druid-spring-boot-starter spring-boot-starter disconf druid-spring-boot-starter

conclusion

Decoupling is something that generations of programmers have spent their entire lives pursuing, and SPI is the culmination of countless tools and ideas that have been proposed and implemented over the years.

The SPI mechanism is very common in various open source frameworks, and the SPI mechanism varies from framework to framework, more or less evolving; But the principle behind it is pretty much the same.

Therefore, understanding these mechanisms, on the one hand, can help us better understand how open source frameworks work and avoid detours; On the other hand, it can also serve as a reference for our daily code writing and system design, so as to write more elegant code.