原创/朱季谦
在该文章正式开始前,先对 Java SPI是什么做一个简单的介绍。
SPI,是Service Provider Interface的缩写,即服务提供者接口,单从字面上看比较抽象,你可以理解成,该机制就像Spring容器一样,通过IOC将对象的创建交给了Spring容器处理,若需要获取某个类的对象,就从Spring容器里取出使用即可。同理,在SPI机制当中,提供了一个类似Spring容器的角色,叫【服务提供者】,在代码运行过程中,若要使用到实现了某个接口的服务实现类对象,只需要将对应的接口类型交给服务提供者。服务提供者将会动态加载实现了该接口的所有服务实现类对象。
服务提供者的角色用下图来表示。
举一个例子来说明。
假如,假如Maven项目里有这样一个interface接口,接口全名“com.zhu.service.UserService”——
package com.zhu.service;public interface UserService {void getName();
}
创建一个“com.zhu.service.impl.AUserServiceImpl”实现类——
public class AUserServiceImpl implements UserService {@Overridepublic void getName() {System.out.println("这是A用户姓名");}
}
接着在resource资源里,创建一个META-INF.services目录,在该目录里,创建一个文件名与接口com.zhu.service.UserService一致的文件——
该com.zhu.service.UserService文件里写下com.zhu.service.impl.UserServiceImpl类名字——
这时候,就可以基于Java SPI动态加载到接口的实现类并执行了,我们写一个简单的测试类做验证——
public class Test {public static void main(String[] args) {ServiceLoader<UserService> serviceLoader = ServiceLoader.load(UserService.class);Iterator<UserService> serviceIterator = serviceLoader.iterator();while (serviceIterator.hasNext()) {UserService service = serviceIterator.next();service.getName();}
}}
}
执行该代码,ServiceLoader会加载到META-INF.services目录下的配置文件,找到对应接口全名文件,读取文件里的类名,再通过反射就可以进行实现类的实例化。既然能找到实现类的对象,那么不就可以基于父类引用指向子类对象,进而调用到实现类的getName()方法。该方法里执行打印语句 System.out.println("打印用户姓名"),打印结果如下,说明基于接口UserService,在程序动态加载并执行UserService接口实现。
Java SPI的机制玩法,就如上文这一整个过程的实现。
该机制存在一个缺陷,假如该接口对应的文件存在多份实现类,那么,它都会一起执行了。
我们增加多一个实现类BUserServiceImpl——
public class BUserServiceImpl implements UserService {@Overridepublic void getName() {System.out.println("这是B用户姓名");}
}
然后,在resource资源里的META-INF.services目录接口对应com.zhu.service.UserService文件里,将BUserServiceImpl实现类的全名增加到文件里——
其他原有的代码无需改动,直接执行Test的main方法,打印结果如下,可以看到,新增的BUserServiceImpl实现类的getName()也被运行了。
这就说明,Java SPI机制会将文件里配置的所有实现类都动态加载运行,稍微思考了一下,不难发现,若当中某个实现类的getName()出现异常,那么后面还没有执行到的其他实现类就会终止了。
因此,Dubbo框架在设计SPI机制时,只是参考了Java SPI的实现,但没有照搬,相比Java,Dubbo增强了SPI机制,可以针对请求动态得选择需要的接口实现类来运行,更加灵活方便。我在自己的另一边原创博文中,详细介绍过Dubbo SPI的原理,感兴趣的小伙伴可以阅读——《Dubbo2.7的Dubbo SPI实现原理细节》
SPI机制的优点很明显,当我们需要基于已有接口新增一个实现类功能时,只需要新增一个实现类代码,无需在原有代码逻辑上做改动,就可以实现新增类的功能逻辑了。
这种场景比较适合在报表或者处理Excel文档情况下,需针对一个新报表或者Excel做相应定制化处理,只需要基于SPI已有接口新增一个实现类即可。我会在后续文章中,将过去应用到SPI的实践经验做一下总结。