面向的对象的设计里,模块之间是基于接口编程,模块之间不对实现类进行硬编码。 一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。 为了实现在模块装配的时候,不在模块里面写死代码,这就需要一种服务发现机制。 java spi就是提供这样的一个机制: 为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到代码之外。
即SPI机制就是有一个接口,然后有几个实现类,代码运行的时候要确定运行哪个实现类,而这些实现类也是可插拔的。
当服务的提供者(provider),提供了一个接口多种实现时, 一般会在jar包的META-INF/services/目录下,创建该接口的同名文件。 该文件里面的内容就是该服务接口的具体实现类的名称。 而当外部加载这个模块的时候, 就能通过该jar包META-INF/services/里的配置文件得到具体的实现类名,并加载实例化,完成模块的装配。
下面举两个例子:
1,比如加载具体的sql驱动包比如mysql :mysql-connector-java-5.1.35.jar/META-INF/services/java.sql.Driver就是spi扩展点 oracle:ojdbc6-11.2.0.1.0.jar/META-INF/services/java.sql.Driver
2,比如加载Spring-web的ServletContainerInitializer : spring-web-4.2.0.RELEASE.jar!/META-INF/services/javax.servlet.ServletContainerInitializer
1,写一个接口Command
public interface Command { public void execute(); } 复制代码
2,分别写两个接口的实现类 TurnOnCommand和TurnOffCommand
public class TurnOnCommand implements Command { public void execute() { System.out.println("Trun on...."); } } 复制代码
public class TurnOffCommand implements Command { public void execute() { System.out.println("Trun off...."); } } 复制代码
3,在项目的resources/META-ING/services目录下新建一个com.abc.spi.Command的spi配置文件,内容为接口的两个实现类
内容为:
com.abc.spi.TurnOnCommand com.abc.spi.TurnOffCommand复制代码
4,编写测试类Test
public class Test { public static void main(String[] args) { // 加载jdk的spi接口Command.class ServiceLoader<Command> serviceLoader=ServiceLoader.load(Command.class); // 遍历加载的jdk的接口 for(Command command:serviceLoader){ command.execute(); } } }复制代码
下图为测试结果:
1,JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源.
2,JDK的SPI机制没有Ioc和AOP的支持,因此dubbo用了自己的spi机制:增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。
生成的dubbo的jar包下的dubbo-2.5.3.jar/META-INF/dubbo/internal目录下的文件全部是spi的扩展机制
1,spi 文件 存储路径 在 META-INF/dubbo/internal 目录下 并且文件名为接口的全路径名 就是=接口的包名+接口名,
例如:dubbo-2.5.3.jar/META-INF/dubbo/internal/ com.alibaba.dubbo.rpc.Protocol
2,每个spi 文件里面的格式定义为: 扩展名=具体的类名 ,例如 registries=com.alibaba.dubbo.registry.RegistriesPageHandlerpages .
比如自己要扩展dubbo的Protocol,要经过以下步骤:
1,新建一个工程,在src/main/resources/META-INF/dubbo目录下新建一个文件比如org.apache.dubbo.rpc.Protocol,里面内容写一个:xxProtocal=com.abc.XxxProtocol
工程目录如下:
2,然后在这个工程里新建一个XxxProtocol类实现Protocol接口,并实现相关方法
XxxProtocal.java:
package com.xxx; import org.apache.dubbo.rpc.Protocol; public class XxxProtocol implements Protocol { public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { return new XxxExporter(invoker); } public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { return new XxxInvoker(type, url); } }复制代码
XxxExporter.java:
package com.xxx; import org.apache.dubbo.rpc.support.AbstractExporter; public class XxxExporter<T> extends AbstractExporter<T> { public XxxExporter(Invoker<T> invoker) throws RemotingException{ super(invoker); // ... } public void unexport() { super.unexport(); // ... } } 复制代码
XxxInvoker.java:
package com.xxx; import org.apache.dubbo.rpc.support.AbstractInvoker; public class XxxInvoker<T> extends AbstractInvoker<T> { public XxxInvoker(Class<T> type, URL url) throws RemotingException{ super(type, url); } protected abstract Object doInvoke(Invocation invocation) throws Throwable { // ... } } 复制代码
3,可以把这个工程打包成jar包,然后在自己的dubbo provider工程里依赖刚才的那个spi的jar包,并在spring配置文件中配置如下:
<dubbo:protocol name=”XxxProtocol” port=”20000” />复制代码
参考:http://dubbo.apache.org/en-us/index.html