java组件扩展,自定义扩展点
背景
在遇到的有些情况下,我们在引入项目中一些公有模块后,想要对其进行功能扩展。让其功能适用于每个想要依赖的其他模块。通常,我们在公有模块中会提供一些扩展接口。但是具体怎么该扩展呢?
java SPI
1.什么是SPI
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。
2.SPI和API的使用场景
API (Application Programming Interface)在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。 从使用人员上来说,API 直接被应用开发人员使用。
SPI (Service Provider Interface)是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。
3.SPI的简单实现
在jdk中,就有很多为开发者提供的扩展点,比如java.sql.Driver接口,这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。
下文以一个自定义的 java.sql.Driver 服务提供者(也可以自己随意再另外一个模块定义一个接口)为例,展示 SPI 在 java 模块化情况下约定的使用方式。
(ExtendJDBCDriver )自定义的 Driver 服务提供者
//对Driver的扩展
public class ExtendJDBCDriver implements Driver {
@Override
public Connection connect(String url, Properties info) throws SQLException {
return null;
}
@Override
public boolean acceptsURL(String url) throws SQLException {
return false;
}
@Override
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
return new DriverPropertyInfo[0];
}
@Override
public int getMajorVersion() {
return 0;
}
@Override
public int getMinorVersion() {
return 0;
}
@Override
public boolean jdbcCompliant() {
return false;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
META-INF/services
Main(用于调试以及输出结果)
class Client {
public static void main(String[] args) {
//模拟其他模块的业务层调用
//通过接口的class加载具体实现类的迭代器
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
for (Driver item : loader) {
System.out.println("Get class:" + item.getClass().getName());
}
}
}
4.原理图
SPI (Service Provider Interface)是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现,可用于启用框架扩展和可替换组件。 从使用人员上来说,SPI 被框架扩展人员使用。如下图所示,A 模块是接口的定义方和使用方,而 B 模块则是接口实现方。
spring ApplicationContext
上诉的扩展方法中,需要为每个扩展类增加META-INF目录,使用起来比较繁琐。但是就其原理,spi使用serviceLoder管理接口的实现类。我们可以想到其他的类管理器,例如IOC。
IOC容器
org.springframework.beans及org.springframework.context这两个包是Spring IoC容器的基础,其中重要的类有BeanFactory,BeanFactory是IoC容器的核心接口,其职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖关系。
ApplicationContext作为BeanFactory的子类,在Bean管理的功能上得到了很大的增强,也更易于与Spring AOP集成使用。
今天我们要讨论的并不是BeanFactory或者ApplicationContext的实现原理,而是对ApplicationContext的一种实际应用方式。
上诉的问题可以借助ApplicationContext的getBeansOfType来实现我们需要的结果。方法签名如下:
<T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException;
从上面的代码上我们可以看出来这个方法能返回一个接口的全部实现类(前提是所有实现类都必须由Spring IoC容器管理)。
管理多个实现类的例子
有一个公有模块需要被扩展,其他模块去实现它,在其写入扩展接口中写入扩展方法,之后这个公有的模块业务层调用扩展方法。如图所示
代码如下
相比较spi的扩展功能,我们使用spring的特性,让其IOC容器为我们管理扩展实现类,是遇到这样问题良好的解决方案。