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:
- Concatenate the file location according to the fully qualified interface name, combined with meta-INF /services/. This is a death sentence
- Use ClassLoader to load file resources
- Resolve which implementation classes of the interface are configured in the corresponding file
- Instantiate using reflection based on the fully qualified name of the parsed class
- Put it in cache.
- 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 fixed
META-INF/spring.factories
Configuration 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.