Welcome to pay attention to the public account “JAVA Front” to view more wonderful sharing articles, mainly including source code analysis, practical application, architecture thinking, workplace sharing, product thinking and so on, at the same time, welcome to add my personal wechat “JAVA_front” to communicate and learn together


1. Article Overview

Service Provider Interface (SPI) is a Service discovery mechanism. In essence, the fully qualified name of the Interface implementation class is configured in a file, and the Service loader reads the configuration file to load the implementation class. In this way, the Interface implementation class can be dynamically replaced at runtime.

In this article, we analyze the DUBBO SPI mechanism. Compared with JDK SPI, at least the following functions are extended:

  • Specify specific extension points
  • Specify the default extension point
  • Class level adaptive extension points
  • Method level adaptive extension points
  • Since the implementation IOC
  • Since the implementation of AOP


2 Specify an extension point

2.1 Code Examples

The first step is to define the order interface and the order model

package com.java.front.dubbo.spi.api.order;
import org.apache.dubbo.common.extension.SPI;

@SPI
public interface OrderSpiService {
    public boolean createOrder(OrderModel order);
}

public class OrderModel {
    private String userId;
    private String orderId;
}
Copy the code


The second step is to implement the order service

package com.java.front.dubbo.spi.impl.order;

public class OrderSpiAServiceImpl implements OrderSpiService {

    @Override
    public boolean createOrder(OrderModel order) {
        System.out.println("OrderSpiAService createOrder");
        returnBoolean.TRUE; }}public class OrderSpiBServiceImpl implements OrderSpiService {

    @Override
    public boolean createOrder(OrderModel order) {
        System.out.println("OrderSpiBService createOrder");
        returnBoolean.TRUE; }}Copy the code


Step 3 Add a configuration file

├ ─ SRC │ ├ ─ the main │ │ └ ─ resources │ │ └ ─ meta-inf │ │ ├ ─ services │ │ │ com, Java. Front. The dubbo. Spi. API. Order. OrderSpiServiceCopy the code


Step 4 Add the content of the configuration file

orderSpiAService=com.java.front.dubbo.spi.impl.order.OrderSpiAServiceImpl
orderSpiBService=com.java.front.dubbo.spi.impl.order.OrderSpiBServiceImpl
Copy the code


Step 5 Run the test code

public class OrderSpiServiceTest {

    public static void main(String[] args) {
        test1();
    }

    public static void test1(a) {
        ExtensionLoader<OrderSpiService> extensionLoader = ExtensionLoader.getExtensionLoader(OrderSpiService.class);
        OrderSpiService orderSpiService = extensionLoader.getExtension("orderSpiAService");
        orderSpiService.createOrder(newOrderModel()); }}Copy the code


Step 6 Output test results

OrderSpiAService createOrder
Copy the code


2.2 Source Code Analysis


The loadExtensionClasses method reads the configuration files in the relevant directories and loads the implementation classes

public class ExtensionLoader<T> {

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

    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

    privateMap<String, Class<? >> loadExtensionClasses() {// omit the code
        // KEY indicates the user-defined name, and Value indicates the implementation class
        // orderSpiAService=class com.java.front.dubbo.spi.impl.order.OrderSpiAServiceImpl
        // orderSpiBService=class com.java.front.dubbo.spi.impl.order.OrderSpiBServiceImplMap<String, Class<? >> extensionClasses =newHashMap<String, Class<? > > (); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache"."com.alibaba"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache"."com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache"."com.alibaba"));
        return extensionClasses;
    }

    private void loadDirectory(Map
       
        > extensionClasses, String dir, String type)
       ,> {
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls;
            ClassLoader classLoader = findClassLoader();
            if(classLoader ! =null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if(urls ! =null) {
                // Walk through all the files
                while(urls.hasMoreElements()) { java.net.URL resourceURL = urls.nextElement(); loadResource(extensionClasses, classLoader, resourceURL); }}}catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t); }}private void loadResource(Map
       
        > extensionClasses, ClassLoader classLoader, java.net.URL resourceURL)
       ,> {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
            try {
                // Read each line of the file
                String line;
                while((line = reader.readLine()) ! =null) {
                    final int ci = line.indexOf(The '#');
                    if (ci >= 0) {
                        line = line.substring(0, ci);
                    }
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            // the equal sign is used as the separator
                            int i = line.indexOf('=');
                            if (i > 0) {
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                // Load the implementation class
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); }}catch (Throwable t) {
                            IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: "+ t.getMessage(), t); exceptions.put(line, e); }}}}finally{ reader.close(); }}catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " + type + ", class file: " + resourceURL + ") in "+ resourceURL, t); }}}Copy the code


Classes.get (name) gets the extension point based on the input name

public class ExtensionLoader<T> {

    private T createExtension(String name) {
        // Get the corresponding implementation class according to nameClass<? > clazz = getExtensionClasses().get(name);if (clazz == null) {
            throw findException(name);
        }
        try {
            // Get the implementation object
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // omit the code
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + ") could not be instantiated: " + t.getMessage(), t);
        }
        returninstance; }}Copy the code


3 Specify the default extension point

3.1 Code Examples

Step 1 Modify the order interface

package com.java.front.dubbo.spi.api.order;
import org.apache.dubbo.common.extension.SPI;

@SPI("orderSpiBService")
public interface OrderSpiService {
    public boolean createOrder(OrderModel order);
}
Copy the code


The second step runs the test code

public class OrderSpiServiceTest {

    public static void main(String[] args) {
        test2();
    }

    public static void test2(a) {
        ExtensionLoader<OrderSpiService> extensionLoader = ExtensionLoader.getExtensionLoader(OrderSpiService.class);
        OrderSpiService orderSpiService = extensionLoader.getDefaultExtension();
        orderSpiService.createOrder(newOrderModel()); }}Copy the code


The third step is to output the test results

OrderSpiBService createOrder
Copy the code


3.2 Source code Analysis


The getDefaultExtension method gets the default extension point

public class ExtensionLoader<T> {

    public T getDefaultExtension(a) {
        // Load the implementation class and set cachedDefaultName
        getExtensionClasses();
        if (null == cachedDefaultName || cachedDefaultName.length() == 0 || "true".equals(cachedDefaultName)) {
            return null;
        }
        // Get extension points based on the default name
        returngetExtension(cachedDefaultName); }}Copy the code


The loadExtensionClasses method sets the default extension point name

public class ExtensionLoader<T> {

    privateMap<String, Class<? >> loadExtensionClasses() {// An interface can only have one default extension point
        // @spi ("orderSpiBService") sets orderSpiBService as the default extension point
        if(defaultAnnotation ! =null) {
            String value = defaultAnnotation.value();
            if ((value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                if (names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                                                    + ":" + Arrays.toString(names));
                }
                if (names.length == 1) {
                    cachedDefaultName = names[0]; }}}// KEY indicates the user-defined name, and Value indicates the implementation class
        // orderSpiAService=class com.java.front.dubbo.spi.impl.order.OrderSpiAServiceImpl
        // orderSpiBService=class com.java.front.dubbo.spi.impl.order.OrderSpiBServiceImplMap<String, Class<? >> extensionClasses =newHashMap<String, Class<? > > (); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache"."com.alibaba"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache"."com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache"."com.alibaba"));
        returnextensionClasses; }}Copy the code


4 class level adaptive extension points

4.1 Code Examples

The first step is to add an order adaptive implementation class

package com.java.front.dubbo.spi.impl.order;
import org.apache.dubbo.common.extension.Adaptive;

@Adaptive
public class OrderSpiAdaptiveServiceImpl implements OrderSpiService {

    @Override
    public boolean createOrder(OrderModel order) {
        System.out.println("OrderSpiAdaptiveService createOrder");
        returnBoolean.TRUE; }}Copy the code


Step 2 Add a configuration file

orderAdaptiveService=com.java.front.dubbo.spi.impl.order.OrderSpiAdaptiveServiceImpl
Copy the code


Step 3 Run the test code

public class OrderSpiServiceTest {

    public static void main(String[] args) {
        test3();
    }

    public static void test3(a) {
        ExtensionLoader<OrderSpiService> extensionLoader = ExtensionLoader.getExtensionLoader(OrderSpiService.class);
        OrderSpiService orderSpiService = extensionLoader.getAdaptiveExtension();
        orderSpiService.createOrder(newOrderModel()); }}Copy the code


Step 4 Output test results

OrderSpiAdaptiveService createOrder
Copy the code


4.2 Source Code Analysis


GetAdaptiveExtensionClass method to judge whether there is a class level adaptive extension points, if there is returned directly

public class ExtensionLoader<T> {

    privateClass<? > getAdaptiveExtensionClass() {// Load the extension point and set cachedAdaptiveClass
        getExtensionClasses();

        // Class level adaptive extension points are returned
        if(cachedAdaptiveClass ! =null) {
            return cachedAdaptiveClass;
        }

        // Class level adaptive extension points are created dynamically if they do not exist
        returncachedAdaptiveClass = createAdaptiveExtensionClass(); }}Copy the code


The loadClass method sets cachedAdaptiveClass when it detects the presence of class-level adaptive extension points

public class ExtensionLoader<T> {

    private void loadClass(Map
       
        > extensionClasses, java.net.URL resourceURL, Class
         clazz, String name)
       ,> throws NoSuchMethodException {

        // Clazz is an interface implementation class
        if(! type.isAssignableFrom(clazz)) {throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface.");
        }

        // Set cachedAdaptiveClass if there are class-level adaptive extension points
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            if (cachedAdaptiveClass == null) {
                cachedAdaptiveClass = clazz;
            }
            // An interface can have only one class-level adaptive extension point
            else if(! cachedAdaptiveClass.equals(clazz)) {throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ","+ clazz.getClass().getName()); }}// omit the code}}Copy the code


5 method level adaptive extension points

5.1 Code Examples

The first step is to create an inventory interface and an inventory entity, and the method level adaptive extension point should meet any of the following conditions:

  • Methods must contain URL input parameters
  • Method must contain a GET method and return a URL
package com.java.front.dubbo.spi.api.stock;
import org.apache.dubbo.common.URL;

@SPI("stockA")
public interface StockSpiService {

    @Adaptive("bizType")
    public boolean reduceStock(String skuId, URL url);

    @Adaptive("bizType")
    public boolean reduceStock2(StockReduceModel stockReduceModel);
}

public class StockReduceModel {

    private String skuId;

    private URL url;

    public StockReduceModel(String skuId, URL url) {
        this.skuId = skuId;
        this.url = url;
    }

    public String getSkuId(a) {
        return skuId;
    }

    public void setSkuId(String skuId) {
        this.skuId = skuId;
    }

    public URL getUrl(a) {
        return url;
    }

    public void setUrl(URL url) {
        this.url = url; }}Copy the code


The second step is to implement the inventory service

package com.java.front.dubbo.spi.impl.stock;
import org.apache.dubbo.common.URL;

public class StockSpiAServiceImpl implements StockSpiService {

    @Override
    public boolean reduceStock1(String skuId, URL url) {
        System.out.println("StockSpiAService reduceStock1 skuId=" + skuId);
        return Boolean.TRUE;
    }

    @Override
    public boolean reduceStock2(StockReduceModel stockReduceModel) {
        System.out.println("StockSpiAService reduceStock2 stockReduceModel=" + stockReduceModel);
        returnBoolean.TRUE; }}public class StockSpiBServiceImpl implements StockSpiService {

    @Override
    public boolean reduceStock1(String skuId, URL url) {
        System.out.println("StockSpiBService reduceStock1 skuId=" + skuId);
        return Boolean.TRUE;
    }

    @Override
    public boolean reduceStock2(StockReduceModel stockReduceModel) {
        System.out.println("StockSpiBService reduceStock2 stockReduceModel=" + stockReduceModel);
        returnBoolean.TRUE; }}Copy the code


Step 3 Add a configuration file

├ ─ SRC │ ├ ─ the main │ │ └ ─ resources │ │ └ ─ meta-inf │ │ ├ ─ services │ │ │ com, Java. Front. The dubbo. Spi. API. Stock. StockSpiServiceCopy the code


Step 4 Add the content of the configuration file

stockA=com.java.front.dubbo.spi.impl.stock.StockSpiAServiceImpl
stockB=com.java.front.dubbo.spi.impl.stock.StockSpiBServiceImpl
Copy the code


Step 5 Run the test code

public class StockSpiServiceTest {

    public static void main(String[] args) {
        test1();
        test2();
    }

    public static void test1(a) {
        ExtensionLoader<StockSpiService> extensionLoader = ExtensionLoader.getExtensionLoader(StockSpiService.class);
        StockSpiService adaptiveService = extensionLoader.getAdaptiveExtension();
        Map<String, String> map = new HashMap<>();
        map.put("bizType"."stockB");
        URL url = new URL(StringUtils.EMPTY, StringUtils.EMPTY, 1, map);
        adaptiveService.reduceStock1("skuId_111", url);
    }

    public static void test2(a) {
        ExtensionLoader<StockSpiService> extensionLoader = ExtensionLoader.getExtensionLoader(StockSpiService.class);
        StockSpiService adaptiveService = extensionLoader.getAdaptiveExtension();
        Map<String, String> map = new HashMap<>();
        map.put("bizType"."stockB");
        URL url = new URL(StringUtils.EMPTY, StringUtils.EMPTY, 1, map);
        StockReduceModel stockReduceModel = new StockReduceModel("skuId_111", url); adaptiveService.reduceStock2(stockReduceModel); }}Copy the code


Step 6 Output test results

StockSpiBService reduceStock1 skuId=skuId_111 StockSpiBService reduceStock2 stockReduceModel=StockReduceModel(skuId=skuId_111, url=? bizType=stockB)Copy the code


5.2 Source Code Analysis


GetAdaptiveExtensionClass method to judge whether there is a class level adaptive extension points, if there is no dynamic creation

public class ExtensionLoader<T> {

    privateClass<? > getAdaptiveExtensionClass() {// Load the extension point and set cachedAdaptiveClass
        getExtensionClasses();

        // Class level adaptive extension points are returned
        if(cachedAdaptiveClass ! =null) {
            return cachedAdaptiveClass;
        }

        // Class level adaptive extension points are created dynamically if they do not exist
        returncachedAdaptiveClass = createAdaptiveExtensionClass(); }}Copy the code


CreateAdaptiveExtensionClass method dynamically generated adaptive extension point code

public class ExtensionLoader<T> {
    privateClass<? > createAdaptiveExtensionClass() {// Dynamically generate adaptive extension point code
        // This method verifies that any of the following conditions are met
        // 1. Methods must contain URL input parameters
        // 2. The method must contain the get method and return the URL
        String code = createAdaptiveExtensionClassCode();

        // Javassist builds the class object
        ClassLoader classLoader = findClassLoader();
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        returncompiler.compile(code, classLoader); }}Copy the code


StockSpiService$Adaptive is a dynamically generated Adaptive extension point code. We can see the function of the URL parameter, which can be likened to the URL as a router. The Adaptive extension point determines which service to route to according to the input parameter

package com.java.front.dubbo.spi.api.stock;
import org.apache.dubbo.common.extension.ExtensionLoader;

public class StockSpiService$Adaptive implements com.java.front.dubbo.spi.api.stock.StockSpiService {
    public boolean reduceStock1(java.lang.String arg0, org.apache.dubbo.common.URL arg1) {
        if (arg1 == null)
            throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg1;
        String extName = url.getParameter("bizType"."stockA");
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.java.front.dubbo.spi.api.stock.StockSpiService) name from url(" + url.toString() + ") use keys([bizType])");
        com.java.front.dubbo.spi.api.stock.StockSpiService extension = (com.java.front.dubbo.spi.api.stock.StockSpiService) ExtensionLoader.getExtensionLoader(com.java.front.dubbo.spi.api.stock.StockSpiService.class).getExtension(extName);
        return extension.reduceStock1(arg0, arg1);
    }

    public boolean reduceStock2(com.java.front.dubbo.spi.model.StockReduceModel arg0) {
        if (arg0 == null)
            throw new IllegalArgumentException("com.java.front.dubbo.spi.model.StockReduceModel argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.java.front.dubbo.spi.model.StockReduceModel argument getUrl() == null");
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("bizType"."stockA");
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.java.front.dubbo.spi.api.stock.StockSpiService) name from url(" + url.toString() + ") use keys([bizType])");
        com.java.front.dubbo.spi.api.stock.StockSpiService extension = (com.java.front.dubbo.spi.api.stock.StockSpiService) ExtensionLoader.getExtensionLoader(com.java.front.dubbo.spi.api.stock.StockSpiService.class).getExtension(extName);
        returnextension.reduceStock2(arg0); }}Copy the code


6 Self-implementation OF IOC

6.1 Code Examples

Step 1 Add a commodity interface

package com.java.front.dubbo.spi.api.goods;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.SPI;

@SPI
public interface GoodsSpiService {
    public boolean buyGoods(String skuId, URL url);
}
Copy the code


The second step implements the commodity interface

package com.java.front.dubbo.spi.impl.goods;
import org.apache.dubbo.common.URL;
import com.java.front.dubbo.spi.api.goods.GoodsSpiService;
import com.java.front.dubbo.spi.api.stock.StockSpiService;

public class GoodsSpiAServiceImpl implements GoodsSpiService {

    private StockSpiService stockSpiService;

    public void setStockSpiService(StockSpiService stockSpiService) {
        this.stockSpiService = stockSpiService;
    }

    @Override
    public boolean buyGoods(String skuId, URL url) {
        System.out.println("GoodsSpiAService buyGoods skuId=" + skuId);
        stockSpiService.reduceStock1(skuId, url);
        return false; }}Copy the code


Step 3 Add a configuration file

├ ─ SRC │ ├ ─ the main │ │ └ ─ resources │ │ └ ─ meta-inf │ │ ├ ─ services │ │ │ com, Java. Front. The dubbo. Spi. API. Stock. GoodsSpiServiceCopy the code


Step 4 Add the content of the configuration file

goodsA=com.java.front.dubbo.spi.impl.goods.GoodsSpiAServiceImpl
Copy the code


Step 5 Run the test code

public class GoodsServiceTest {

    public static void main(String[] args) {
        test1();
    }

    public static void test1(a) {
        ExtensionLoader<GoodsSpiService> extensionLoader = ExtensionLoader.getExtensionLoader(GoodsSpiService.class);
        GoodsSpiService goodsService = extensionLoader.getExtension("goodsA");
        Map<String, String> map = new HashMap<>();
        map.put("bizType"."stockA");
        URL url = new URL(StringUtils.EMPTY, StringUtils.EMPTY, 1, map);
        goodsService.buyGoods("skuId_111", url); }}Copy the code


Step 6 Output test results

GoodsSpiAService buyGoods skuId=skuId_111
StockSpiAService reduceStock1 skuId=skuId_111
Copy the code


6.2 Source Code Analysis


InjectExtension method self-implements IOC

public class ExtensionLoader<T> {

    private T createExtension(String name) { Class<? > clazz = getExtensionClasses().get(name);if (clazz == null) {
            throw findException(name);
        }
        try {
            // instance = com.java.front.dubbo.spi.impl.goods.GoodsSpiAServiceImpl
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }

            // self-implement IOC
            injectExtension(instance);

            // omit the code
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + ") could not be instantiated: "+ t.getMessage(), t); }}private T injectExtension(T instance) {
        try {
            if(objectFactory ! =null) {

                // iterate over all setxxx methods of instance
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        if(method.getAnnotation(DisableInject.class) ! =null) {
                            continue;
                        }

                        // method = public void com.java.front.dubbo.spi.impl.goods.GoodsSpiAServiceImpl.setStockSpiService(com.java.front.dubbo.spi.api.stock.StockSpiS ervice)
                        // pt = com.java.front.dubbo.spi.api.stock.StockSpiServiceClass<? > pt = method.getParameterTypes()[0];
                        if (ReflectUtils.isPrimitives(pt)) {
                            continue;
                        }
                        try {
                            // property = stockSpiService
                            String property = method.getName().length() > 3 ? method.getName().substring(3.4).toLowerCase() + method.getName().substring(4) : StringUtils.EMPTY;

                            // objectFactory = AdaptiveExtensionFactory
                            . / / AdaptiveExtensionFactory getExtension executed in sequence SpiExtensionFactory, SpringExtensionFactory until get StockSpiService object is assigned to the object
                            Object object = objectFactory.getExtension(pt, property);
                            if(object ! =null) {
                            
                                // instance = com.java.front.dubbo.spi.impl.goods.GoodsSpiAServiceImpl
                                // method = GoodsSpiAServiceImpl.setStockSpiService
                                // object = StockSpiService$Adaptivemethod.invoke(instance, object); }}catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName() + " of interface " + type.getName() + ":" + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        returninstance; }}public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if(! loader.getSupportedExtensions().isEmpty()) {returnloader.getAdaptiveExtension(); }}return null; }}Copy the code


Self-implementation of AOP

7.1 Decorator mode

The decorator pattern dynamically attaches responsibility to objects, enhances primitive class functionality without changing the primitive class interface, and supports nested use of multiple decorators. Implementing the decorator pattern requires the following components:


(1) Component

Abstract artifacts: Core business abstractions, which can use interfaces or abstract classes

public abstract class Component {
    public abstract void playFootBall(a);
}
Copy the code


(2) ConcreteComponent

Concrete artifacts: core business code

public class ConcreteComponent extends Component {

    @Override
    public void playFootBall(a) {
        System.out.println("Play football"); }}Copy the code


(3) Decorator

Abstract decorator: Implements Components and combines them with constructors

public abstract class Decorator extends Component {
    private Component component = null;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void playFootBall(a) {
        this.component.playFootBall(); }}Copy the code


(4) ConcreteDecorator

Concrete decorator: Concrete decorator code

// Socks decorator
public class ConcreteDecoratorA extends Decorator {

    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    private void decorateMethod(a) {
        System.out.println("Change your socks.");
    }

    @Override
    public void playFootBall(a) {
        this.decorateMethod();
        super.playFootBall(); }}// Sneaker decorator
public class ConcreteDecoratorB extends Decorator {

    public ConcreteDecoratorB(Component component) {
        super(component);
    }

    private void decorateMethod(a) {
        System.out.println("Change your sneakers.");
    }

    @Override
    public void playFootBall(a) {
        this.decorateMethod();
        super.playFootBall(); }}Copy the code


(5) Test code

public class TestDecoratorDemo {
    public static void main(String[] args) {
        Component component = new ConcreteComponent();
        component = new ConcreteDecoratorA(component);
        component = newConcreteDecoratorB(component); component.playFootBall(); }}Copy the code


(6) Output results

Change sneakers change socks play soccerCopy the code


7.2 Code Examples

This chapter is expanded on the basis of the order service in chapter 2. In the first step, two new sections are added, one log section and one transaction section. We can think of a section as a decorator: The section implements the order service and combines the order service through a constructor.

package com.java.front.dubbo.spi.impl.order;

// Log section
public class OrderSpiLogWrapper implements OrderSpiService {

    private OrderSpiService orderSpiService;

    public OrderSpiLogWrapper(OrderSpiService orderSpiService) {
        this.orderSpiService = orderSpiService;
    }

    @Override
    public boolean createOrder(OrderModel order) {
        System.out.println("OrderSpiLogWrapper log start");
        boolean result = orderSpiService.createOrder(order);
        System.out.println("OrderSpiLogWrapper log end");
        returnresult; }}// Transaction facets
public class OrderSpiTransactionWrapper implements OrderSpiService {

    private OrderSpiService orderSpiService;

    public OrderSpiTransactionWrapper(OrderSpiService orderSpiService) {
        this.orderSpiService = orderSpiService;
    }

    @Override
    public boolean createOrder(OrderModel order) {
        System.out.println("OrderSpiTransactionWrapper begin");
        boolean result = orderSpiService.createOrder(order);
        System.out.println("OrderSpiTransactionWrapper commit");
        returnresult; }}Copy the code


Step 2 Add the content of the configuration file

orderSpiLogWrapper=com.java.front.dubbo.spi.impl.order.OrderSpiLogWrapper
orderSpiTransactionWrapper=com.java.front.dubbo.spi.impl.order.OrderSpiTransactionWrapper
Copy the code


Step 3 Run the test code

public class OrderSpiServiceTest {

    public static void main(String[] args) {
        test1();
    }

    public static void test1(a) {
        ExtensionLoader<OrderSpiService> extensionLoader = ExtensionLoader.getExtensionLoader(OrderSpiService.class);
        OrderSpiService orderSpiService = extensionLoader.getExtension("orderSpiAService");
        orderSpiService.createOrder(newOrderModel()); }}Copy the code


Step 4 Output test results

OrderSpiLogWrapper log start
OrderSpiTransactionWrapper begin
OrderSpiAService createOrder
OrderSpiTransactionWrapper commit
OrderSpiLogWrapper log end
Copy the code


7.3 Source Code Analysis


The createExtension method can see that the aspect decorates the order service through the constructor

public class ExtensionLoader<T> {

    private T createExtension(String name) { Class<? > clazz = getExtensionClasses().get(name);if (clazz == null) {
            throw findException(name);
        }
        try {
            // instance = com.java.front.dubbo.spi.impl.order.OrderSpiAServiceImpl
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }

            // self-implement IOC
            injectExtension(instance);

            // All facets
            // wrapperClasses = [com.java.front.dubbo.spi.impl.order.OrderSpiLogWrapper,com.java.front.dubbo.spi.impl.order.OrderSpiTransactionWrapper]Set<Class<? >> wrapperClasses = cachedWrapperClasses;// self-implementing AOP
            // instance=(OrderSpiLogWrapper(OrderSpiTransactionWrapper(orderSpiAService)))
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for(Class<? > wrapperClass : wrapperClasses) {// 1. Decorate with the section constructor
                    // 2. The section may need to inject properties through set, so execute injectExtensioninstance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); }}return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + ") could not be instantiated: "+ t.getMessage(), t); }}}Copy the code


The loadClass method sets up cachedWrapperClasses

public class ExtensionLoader<T> {
    private void loadClass(Map
       
        > extensionClasses, java.net.URL resourceURL, Class
         clazz, String name)
       ,> throws NoSuchMethodException {

        // clazz implements type
        // type = com.java.front.dubbo.spi.api.order.OrderSpiService
        // clazz = com.java.front.dubbo.spi.impl.order.OrderSpiLogWrapper
        // clazz = com.java.front.dubbo.spi.impl.order.OrderSpiTransactionWrapper
        if(! type.isAssignableFrom(clazz)) {throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface.");
        }

        // omit the code

        // clazz is a wrapper
        else if(isWrapperClass(clazz)) { Set<Class<? >> wrappers = cachedWrapperClasses;if (wrappers == null) {
                cachedWrapperClasses = newConcurrentHashSet<Class<? > > (); wrappers = cachedWrapperClasses; }// add cachedWrapperClasses
            wrappers.add(clazz);
        }

        // omit the code
    }

    // Whether clazz combines type with a constructor
    private boolean isWrapperClass(Class
        clazz) {
        try {
            clazz.getConstructor(type);
            return true;
        } catch (NoSuchMethodException e) {
            return false; }}}Copy the code


8 Article Summary

In this paper, through code examples and source code analysis two ways, a detailed analysis of DUBBO SPI six features: specified specific extension point, specified default extension point, class level adaptive extension point, method level adaptive extension point, self-implementation OF IOC, self-implementation of AOP, I hope this article will be helpful to you.


Welcome to pay attention to the public account “JAVA Front” to view more wonderful sharing articles, mainly including source code analysis, practical application, architecture thinking, workplace sharing, product thinking and so on, at the same time, welcome to add my personal wechat “JAVA_front” to communicate and learn together