preface

Text has been included to my lot warehouse, welcome Star:https://github.com/bin392328206 plant a tree is the best time ten years ago, followed by now

six-finger-web

The wheels of a Web backend framework roll themselves (easy) from handling Http requests [Netty request-level Web servers] to MVC [interface encapsulating and forwarding], to IOC [dependency injection], to AOP [aspect], to RPC [remote procedure calls] and finally to ORM [database operations].

github

Why the wheel

Actually is such, small six six oneself peacetime? Sometimes like to look at the source code such as Spring, but the level may not how, every time dazed at all, and then feel the inside of the details too difficult, then I can only view the overall thought, then I think if I can according to each elder some thought, rolled out a simple wheel, Is it easier for me to understand the author’s ideas? So six-Finger-Web came out, and it was really a learning process for me, and THEN I opened it up to help those of you who are having trouble learning the source code. In addition, we can exercise our coding ability, because we usually use Java API for CRUD. As time goes by, we are not familiar with many framework API classes, so we take this opportunity to exercise.

The characteristics of

  • Built-in HTTP server written by Netty, without additional dependence on Web services such as Tomcat.
  • Code is simple to understand (small 66 can not write their own framework big guy that kind of high clustering, low coupling code), ability a little bit stronger to see the code can understand, weakness also does not matter, small 66 has a supporting from 0 build tutorial.
  • Support MVC-related annotations to ensure that they are used similarly to SpringMVC
  • Support for Spring IOC and Aop related features
  • Support similar to Mybatis related functions
  • Supports RPC-related functionality similar to Dubbo
  • For data returns, only the Json format is supported

omg

The front is already written chapters, I will give you one by one to go through the construction process

  • Suitable for beginners and intermediate Java programmer training manual to build the entire Web project from 0 (A)
  • Build the whole Web project from 0 (2)
  • Build the whole Web project from 0 (3)
  • Build the whole Web project from 0 (4)
  • Build the whole Web project from 0 (5)
  • [Suitable for junior and intermediate Java programmer training manual to build the whole Web project from 0]

In the last article, I explained RPC in general, because we want to imitate dubbo, and then I went to look at dubbo, I wipe too much source code, and because my own company technology stack is not Dubbo, so? I went to Github to find other projects to imitate Dubbo to learn, and then I found our Guide brother guide-Rpc-framework, and THEN I will follow this project to learn Dubbo! And then with, today we’re going to talk about Dubbo’s SPI first. There’s a lot of applications for this.

SPI(Service Provider Interface)

  • Essentially, the fully qualified name of the interface implementation class is configured in a file, which is read by the service loader to load the implementation class. This lets you dynamically replace the implementation class for the interface at run time.
  • In Java, SPI is designed to be used by service providers as plug-ins. The mechanism of dynamic loading is realized based on policy mode. We define only one interface in the program, the specific implementation of different service providers; When the program starts, the configuration file is read and the configuration determines which implementation to call.
  • SPI mechanism provides extension function for our program. In Dubbo, we can easily extend Dubbo based on SPI. For example, protocol and LoadBalance in Dubbo are extended through THE SPI mechanism.

Java SPI

In fact, the SPI mechanism can be analogous to an inversion of control. Anyway, you are not going to new your implementation class by yourself, but someone else gives it to you. I don’t know how I understand it. Let’s have a look at the SPI mechanism.

  • First, we define a Car interface
package com.xiaoliuliu.spring.a;

/ * ** @author little six six* @ version 1.0 * @date 2020/11/2 10:16 * /public interface Car {  String getBrand(); }  Copy the code
  • Then there are its two implementation classes
package com.xiaoliuliu.six.finger.web.demo.rpc;

/ * ** @author little six six* @ version 1.0 * @date 2020/11/2 10:17 * /public class Benz implements Car {  @Override  public String getBrand() {  System.out.println("benz car");  return "Benz";  } } Copy the code
package com.xiaoliuliu.six.finger.web.demo.rpc;

/ * ** @author little six six* @ version 1.0 * @date 2020/11/2 10:16 * /public class BM implements Car {  @Override  public String getBrand() {  System.out.println("BM car");  return "BM";  } } Copy the code
  • The core part of the

Create a meta-INF /services folder under Resources and create a file. File name is called Car the fully qualified name of the interface package. Com xiaoliuliu. Spring. A.C ar. The content is the fully qualified class name of the interface implementation class.

com.xiaoliuliu.spring.a.Benz
com.xiaoliuliu.spring.a.BM
Copy the code
  • The test class
package com.xiaoliuliu.spring.a;

import java.util.ServiceLoader;

/ * ** @author little six six* @ version 1.0 * @date 2020/11/2 10:17 * /public class Test {  public static void main(String[] args) {  ServiceLoader<Car> serviceLoader = ServiceLoader.load(Car.class);  for (Car car : serviceLoader) {  System.out.println(car.getBrand());  }  } } Copy the code

The JAVA SPI decouples the definition of interfaces from the implementation of specific services so that application processes can enable or replace specific components based on the actual service situation.

For example, the Java.sql package of JAVA defines an interface Driver, which is implemented by each service provider. When we need to use a database, we import the corresponding JAR package.

disadvantages

  • Cannot load on demand. When loading extension points, the Java SPI loads all available extension points at once, many of which are not needed and waste system resources.
  • The method of obtaining an implementation class is not flexible. The method can only be obtained in the form of Iterator. The corresponding implementation class cannot be obtained according to a parameter.
  • No SUPPORT for AOP and dependency injection.
  • The JAVA SPI may lose load extension point exceptions, making tracing problems difficult;

dubbo SPI

Dubbo reimplements a more powerful SPI mechanism that supports AOP and dependency injection, and uses caching to improve the performance of loading implementation classes, as well as enabling flexible fetching of implementation classes. The rest of this article describes the application and principles of SPI.

Dubbo’s SPI interfaces are identified by the @SPI annotation, whose main purpose is to indicate that the interface is an SPI interface. The source code is as follows:

@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE})public @interface SPI {    /**
     * default extension name
* Set the default extension class* /    String value() default "";
} Copy the code
  • Let’s start by explaining the use of the Dubbo SPI
    <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
The < version > 2.5.7 < / version >        </dependency>
 Copy the code

Add annotations to the Car interface

/ * ** @author little six six* @ version 1.0 * @date 2020/11/2 10:16
* /@SPI public interface Car {  String getBrand(); }  Copy the code

The configuration file path and file name remain unchanged for the time being. The file content is modified as follows:

benz=com.xiaoliuliu.spring.a.Benz
Copy the code

Modifying test Classes

package com.xiaoliuliu.spring.a;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

import java.util.ServiceLoader;
 / * ** @author little six six* @ version 1.0 * @date 2020/11/2 10:17 * /public class Test {  public static void main(String[] args) { ExtensionLoader<Car> carExtensionLoader = ExtensionLoader.getExtensionLoader(Car.class); // Get the implementation class object on demand Car car = carExtensionLoader.getExtension("benz");  System.out.println(car.getBrand());  } } Copy the code
  • The results of

Take a look at Dubbo’s Spi and how it extends Java!

Dubbo extends the JDK SPI by changing the content of the service provider configuration file from the fully qualified list of provider classes to the KV list. This also makes it difficult to use JDK ServiceLoader directly in Dubbo, so the corresponding, In Dubbo there is ExtensionLoader, which is an extensionpoint loader used to load various configurable components in Dubbo, such as: ProxyFactory, LoadBalance, Protocol, Filter, Container, Cluster, and RegistryFactor Y), etc.

In short, all of Dubbo’s internal components are managed through this SPI for various scenarios, which is why Dubbo needs to design the service provider profile in the form of KV key-value pairs. This K is what we use in Dubbo profiles or annotations. Dubbo gets the implementation classes provided by the service directly from the ExtensionLoader via the service interface (ProxyFactory, LoadBalance, Protocol, Filter, etc., mentioned above) and configured K.

Extended Functions

  • Easy access to extended implementations: the JDK SPI only gets all implementations by interface class name, while ExtensionLoader gets one implementation by interface class name and key value;

  • IOC dependency injection function: Adaptive implementation, which is to generate a proxy class, so that the actual call can be dynamically determined according to some parameters.

Concrete implementation of a simple explanation

I looked at the Dubbo source ExtensionLoader class has about 1000 lines of code and a lot of methods, and THEN I’m not going to take it, let’s take the above we refer to the Javaguide mimics dubbo project to explain, the following is the class code

package github.javaguide.extension;

import github.javaguide.utils.Holder;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap;  import static java.nio.charset.StandardCharsets.UTF_8;  / * * * refer to dubbo spi: https://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html * /@Slf4j public final class ExtensionLoader<T> {   private static final String SERVICE_DIRECTORY = "META-INF/extensions/"; private static final Map<Class<? >, ExtensionLoader<? >> EXTENSION_LOADERS = new ConcurrentHashMap<>();private static final Map<Class<? >, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(); private final Class<? >type;  private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>(); private final Holder<Map<String, Class<? >>> cachedClasses = new Holder<>(); private ExtensionLoader(Class<? >type) {  this.type = type;  }   public static <S> ExtensionLoader<S> getExtensionLoader(Class<S> type) {  if (type == null) {  throw new IllegalArgumentException("Extension type should not be null.");  }  if(! type.isInterface()) { throw new IllegalArgumentException("Extension type must be an interface.");  }  if (type.getAnnotation(SPI.class) == null) {  throw new IllegalArgumentException("Extension type must be annotated by @SPI");  }  // firstly get from cache, if not hit, create one  ExtensionLoader<S> extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);  if (extensionLoader == null) {  EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<S>(type));  extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);  }  return extensionLoader;  }   public T getExtension(String name) {  if (name == null || name.isEmpty()) {  throw new IllegalArgumentException("Extension name should not be null or empty.");  }  // firstly get from cache, if not hit, create one  Holder<Object> holder = cachedInstances.get(name);  if (holder == null) {  cachedInstances.putIfAbsent(name, new Holder<>());  holder = cachedInstances.get(name);  }  // create a singleton if no instance exists  Object instance = holder.get();  if (instance == null) {  synchronized (holder) {  instance = holder.get();  if (instance == null) {  instance = createExtension(name);  holder.set(instance);  }  }  }  return (T) instance;  }   private T createExtension(String name) {  // load all extension classes of type T from file and get specific one by name Class<? > clazz = getExtensionClasses().get(name); if (clazz == null) {  throw new RuntimeException("No such extension of name " + name);  }  T instance = (T) EXTENSION_INSTANCES.get(clazz);  if (instance == null) {  try {  EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());  instance = (T) EXTENSION_INSTANCES.get(clazz);  } catch (Exception e) {  log.error(e.getMessage());  throw new RuntimeException("Fail to create an instance of the extension class " + clazz);  }  }  return instance;  }  private Map<String, Class<? >>getExtensionClasses() {  // get the loaded extension class from the cache Map<String, Class<? >> classes = cachedClasses.get(); // double check  if (classes == null) {  synchronized (cachedClasses) {  classes = cachedClasses.get();  if (classes == null) {  classes = new HashMap<>();  // load all extensions from our extensions directory  loadDirectory(classes);  cachedClasses.set(classes);  }  }  }  return classes;  }  private void loadDirectory(Map<String, Class<? >> extensionClasses) { String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();  try {  Enumeration<URL> urls;  ClassLoader classLoader = ExtensionLoader.class.getClassLoader();  urls = classLoader.getResources(fileName);  if(urls ! = null) { while (urls.hasMoreElements()) {  URL resourceUrl = urls.nextElement();  loadResource(extensionClasses, classLoader, resourceUrl);  }  }  } catch (IOException e) {  log.error(e.getMessage());  }  }  private void loadResource(Map<String, Class<? >> extensionClasses, ClassLoader classLoader, URL resourceUrl) { try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {  String line;  // read every line  while((line = reader.readLine()) ! = null) { // get index of comment  final int ci = line.indexOf(The '#');  if (ci >= 0) {  // string after # is comment so we ignore it  line = line.substring(0, ci);  }  line = line.trim();  if (line.length() > 0) {  try {  final int ei = line.indexOf('=');  String name = line.substring(0, ei).trim();  String clazzName = line.substring(ei + 1).trim();  // our SPI use key-value pair so both of them must not be empty  if (name.length() > 0 && clazzName.length() > 0) { Class<? > clazz = classLoader.loadClass(clazzName); extensionClasses.put(name, clazz);  }  } catch (ClassNotFoundException e) {  log.error(e.getMessage());  }  }   }  } catch (IOException e) {  log.error(e.getMessage());  }  } }  Copy the code

It’s a lot leaner than Dubbo. But although the sparrow, all five organs, through the call to analyze it.

ExtensionLoader<Car> carExtensionLoader = ExtensionLoader.getExtensionLoader(Car.class); // Get the implementation class object on demand
Copy the code

In contrast to Java Dubbo each interface Spi has an ExtensionLoader


In fact, it is equivalent to doing the role of a factory! That’s kind of the idea. But to get the concrete implementation class is the code below

        Car car = carExtensionLoader.getExtension("benz");
Copy the code

And then I’m going to analyze this code


In fact, I circled these places, this is not the classical way we write cache, ha ha, in fact, there is a local cache, basically you can understand it


Obtain the implementation class by scanning the file




Dubbo’s source code is three

By default, Dubbo scans the configuration files in the meta-INF/Dubbo /internal/, meta-INF/Dubbo /, and meta-INF /services/ classpath in sequence.

And then you basically leave it there, essentially decoupling the interface from the implementation. Then Dubbo may also add IOC related things, here is not a one – by – one analysis, interested everyone to have a look.

At the end

Ok, so that’s the end of the second article of RPC. I hope it’s helpful.

Daily for praise

Ok, everybody, that’s all for this article, you can see people here, they are real fans.

Creation is not easy, your support and recognition, is the biggest power of my creation, our next article

Six pulse excalibur | article “original” if there are any errors in this blog, please give criticisms, be obliged!