2.1 SPI简介

SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。 目前有不少框架用它
来做服务的扩展发现,简单来说,它就是一种动态替换发现的机制。使用SPI机制的优势是实现解耦,使得第三方服务模块的装配控制逻辑与调用者的业务代码分离。

2.2 JDK中的SPI

image.png Java中如果想要使用SPI功能,先提供标准服务接口,然后再提供相关接口实现和调用者。这样就可以通过SPI机制中约定好的信息进行查询相应的接口实现。
SPI遵循如下约定:
1、当服务提供者提供了接口的一种具体实现后,在META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
2、接口实现类所在的jar包放在主程序的classpath中;
3、主程序通过java.util.ServiceLoader动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
4、SPI的实现类必须携带一个无参构造方法;

2.3 Dubbo中的SPI

dubbo中大量的使用了SPI来作为扩展点,通过实现同一接口的前提下,可以进行定制自己的实现类。比如比较常见的协议,负载均衡,都可以通过SPI的方式进行定制化,自己扩展。Dubbo中已经存在的所有已经实现好的扩展点。
image.png
下图中则是Dubbo中默认提供的负载均衡策略
image.png

2.4 Dubbo中扩展点使用方式

api项目创建
(1)导入坐标 dubbo
(2)创建接口,在接口上 使用@SPI

2.5 JDK SPI 实现

2.5.1 api接口

  1. package com.hello.service;
  2. /**
  3. * @author zbc
  4. * @date 2020/7/27 22:14
  5. */
  6. public interface SayService {
  7. String sayHello(String name);
  8. }
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>java_spi_demo</artifactId>
  7. <groupId>com.zbc</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <groupId>com.zbc</groupId>
  12. <artifactId>spi_demo_api</artifactId>
  13. </project>

2.5.2 impl 实现

package com.hello.service.impl;

/**
 * @author zbc
 * @date 2020/7/27 22:13
 */
public class SayServiceImpl implements com.hello.service.SayService {
    public String sayHello(String name) {

        return "hello,你好 " + name;
    }
}




package com.hello.service.impl;

import com.hello.service.SayService;

/**
 * @author zbc
 * @date 2020/7/27 22:37
 */
public class DogSayServiceImpl implements SayService {
    public String sayHello(String name) {
        return "dog say hello " + name;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>java_spi_demo</artifactId>
        <groupId>com.zbc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>spi_demo.impl</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.zbc</groupId>
            <artifactId>spi_demo_api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

然后在resource目录下创建一个META-INF.services的目录,创建一个文件名是com.hello.service.SayService的文件,内容是实现类“全限定名” com.hello.service.impl.SayServiceImpl
image.png

2.5.3 main 包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>java_spi_demo</artifactId>
        <groupId>com.zbc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>spi_demo_main</artifactId>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>6</source>
                    <target>6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>com.zbc</groupId>
            <artifactId>spi_demo_api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.zbc</groupId>
            <artifactId>spi_demo.impl</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>
package com.zbc.test;

import com.hello.service.SayService;

import java.util.ServiceLoader;

/**
 * @author zbc
 * @date 2020/7/27 22:18
 */
public class SpiMain {
    public static void main(String[] args) {
        final ServiceLoader<SayService> sayServices = ServiceLoader.load(SayService.class);
        for (SayService sayService : sayServices) {
            System.out.println(sayService.getClass().getName() + ":" + sayService.sayHello("zbc"));
        }
    }
}

2.6 Dubbo SPI 实现

2.6.1 api 实现

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>dubbo_spi_demo</artifactId>
        <groupId>com.zbc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>dubbo_spi_demo_api</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.5</version>
        </dependency>
    </dependencies>

</project>
package com.eat.service;

import org.apache.dubbo.common.extension.SPI;

/**
 * @author zbc
 * @date 2020/7/27 22:46
 */
@SPI
public interface EatService {
    String eat(String food);
}

2.6.2 impl 实现

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>dubbo_spi_demo</artifactId>
        <groupId>com.zbc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>dubbo_spi_demo_impl</artifactId>

    <dependencies>
        <dependency>
            <artifactId>dubbo_spi_demo_api</artifactId>
            <groupId>com.zbc</groupId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>
package com.eat.service.impl;

import com.eat.service.EatService;

/**
 * @author zbc
 * @date 2020/7/27 22:49
 */
public class DogEatServiceImpl implements EatService {

    public String eat(String food) {
        return "hello dog eat " + food;
    }
}



package com.eat.service.impl;

import com.eat.service.EatService;

/**
 * @author zbc
 * @date 2020/7/27 23:02
 */
public class HumanEatServiceImpl implements EatService {
    public String eat(String food) {
        return "human eat " + food;
    }
}

在resource文件下面创建 META-INF/dubbo目录,文件名称是 com.eat.service.EatService,文件内容是实现类的“全限定名”

com.eat.service.impl.DogEatServiceImpl
com.eat.service.impl.HumanEatServiceImpl

2.6.3 main 实现

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>dubbo_spi_demo</artifactId>
        <groupId>com.zbc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>dubbo_spi_demo_main</artifactId>

    <dependencies>
        <dependency>
            <artifactId>dubbo_spi_demo_api</artifactId>
            <groupId>com.zbc</groupId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <artifactId>dubbo_spi_demo_impl</artifactId>
            <groupId>com.zbc</groupId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>
package com.dubbo.test;

import com.eat.service.EatService;
import org.apache.dubbo.common.extension.ExtensionLoader;

import java.util.Set;

/**
 * @author zbc
 * @date 2020/7/27 22:54
 */
public class DubboSpiMain {
    public static void main(String[] args) {
        ExtensionLoader<EatService> extensionLoader = ExtensionLoader.getExtensionLoader(EatService.class);
        //遍历所有的扩展点
        Set<String> extensions = extensionLoader.getSupportedExtensions();
        for (String extension : extensions) {
            System.out.println(extensionLoader.getExtension(extension).eat("面包"));
        }
    }
}

2.7 Dubbo实现自己的SPI目的

  1. JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加 载,会很浪费资源
    2. 如果有扩展点加载失败,则所有扩展点无法使用
    3. 提供了对扩展点包装的功能(Adaptive),并且还支持通过set的方式对其他的扩展点进行注入

    2.8 Dubbo SPI中的Adaptive功能

    Dubbo中的Adaptive功能,主要解决的问题是如何动态的选择具体的扩展点。通过getAdaptiveExtension 统一对指定接口对应的所有扩展点进行封装,通过URL的方式对扩展点来进行动态选择。 (dubbo中所有的注册信息都是通过URL的形式进行处理的。)这里同样采用相同的方式进行实现。功能,主要解决的问题是如何动态的选择具体的扩展点。通过
    (1)创建接口
    api中的 HelloService 扩展如下方法, 与原先类似,在sayHello中增加 Adaptive 注解,并且在参数中提供URL参数.注意这里的URL参数的类为 org.apache.dubbo.common.URL其中@SP可以指定一个字符串参数,用于指明该SPI的默认实现。
    (2)创建实现类
    与上面Service实现类代码相似,只需增加URL形参即可 ``` package com.eat.service;

import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.Adaptive; import org.apache.dubbo.common.extension.SPI;

/**

  • @author zbc
  • @date 2020/7/27 22:46 */ @SPI(“dog”) //指定默认实现,如果url不传时,就运行默认实现 public interface EatService { String eat(String food); @Adaptive String eat(URL url); }
(3)编写DubboAdaptiveMain<br />最后在获取的时候方式有所改变,需要传入URL参数,并且在参数中指定具体的实现类参数如:

package com.dubbo.test;

import com.eat.service.EatService; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.ExtensionLoader;

import java.util.Set;

/**

  • @author zbc
  • @date 2020/7/27 23:20 */ public class DubboAdaptiveMain { public static void main(String[] args) {
     URL url = URL.valueOf("test://localhost/eat/?eat.service=dog");
     ExtensionLoader<EatService> extensionLoader = ExtensionLoader.getExtensionLoader(EatService.class);
     //遍历所有的扩展点
     String eat = extensionLoader.getAdaptiveExtension().eat(url);
     System.out.println(eat);
    
    } }
注意:<br />因为在这里只是临时测试,所以为了保证URL规范,前面的信息均为测试值即可,关键的点在于hello.service 参数,这个参数的值指定的就是具体的实现方式。关于为什么叫eat.service 是因为这个接口的名称,其中后面的大写部分被dubbo自动转码为 . 分割。通过 getAdaptiveExtension 来提供一个统一的类来对所有的扩展点提供支持(底层对所有的扩展点进行封装)。调用时通过参数中增加 URL 对象来实现动态的扩展点使用。如果URL没有提供该参数,则该方法会使用默认在 SPI 注解中声明的实现。
<a name="AtdeE"></a>
# 2.9 SPI Filter 拦截器功能

<?xml version=”1.0” encoding=”UTF-8”?>

dubbo-samples-all org.apache.dubbo 1.0-SNAPSHOT 4.0.0 dubbo-samples-spi-filter

<dependencies>
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo</artifactId>
        <version>2.7.5</version>
    </dependency>
</dependencies>

```
package com.zbc.filter;

import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;

/**
 * @author zbc
 * @date 2020/7/27 23:31
 */
@Activate(group = {CommonConstants.CONSUMER})
public class DubboInvokerFilter implements Filter {
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        long statTime = System.currentTimeMillis();
        try {
           //执行方法
           return invoker.invoke(invocation);
       }finally {
            System.out.println("invoke time:" + (System.currentTimeMillis() - statTime) + "毫秒");
        }
    }
}

在resourcecs文件下创建文件名字是“org.apache.dubbo.rpc.Filter”,内容是 timeFilter=com.zbc.filter.DubboInvokerFilter