SPI 是 JAVA 提供的一种服务提供发现接口,其实就是一种面向接口的编程,为接口去匹配具体服务实现的机制,这一点上与 IOC 的思想类似,都是把装配的控制权放到了程序之外,下面具体看看什么是 SPI。
一、什么是 SPISPI 全称为 Service Provider Interface,即服务提供发现接口,这里的服务指的不是我们经常听到的微服务服务发现,这里的一个服务 Service 指的是一个接口或抽象类,服务提供方则是对这个接口或抽象类的实现。SPI 是 ”基于接口的编程 + 策略模式 + 配置文件“ 组合实现的动态加载机制
二、为什么使用 SPI模块化设计中,模块之间基于接口编程,把装配的控制权放到程序之外,实现系统的解耦
- 使用场景
适用于调用方根据实际需求启用、扩展、替换服务的策略实现。许多开源框架中都使用了 Java 的 SPI 机制,如 JDBC 的 SPI 加载模式、日志框架 SLF4J 加载不同提供商的日志实现、Spring 中也大量适用了 SPI、Dubbo 的扩张机制、ServiceComb Java Chassis (CSE) 的 Filter、异常处理等扩展机制
三、SPI 的实现- SPI 的实现步骤
在类路径下的 meta-INF/services 目录下,创建以服务接口的”全限定名“命名的文件,文件的内容为接口实现类的全限定名实现类必须在当前程序的 classpath 下使用 bash java.util.ServiceLoader 动态加载实现,会扫描 meta-INF/services 下的配置文件加载实现类
- 先定义一个基接口
public interface baseDriver {
void url();
}
并使用maven打包,并安装在本地maven仓库
- 新建maven项目,导入含有baseDriver的maven坐标,编写一个接口实现类
public class MysqlDriver implements baseDriver
{
@Override
public void url() {
System.out.println("this is a mysql url");
}
}
以下这一步很重要:
再次使用maven打包,并安装在本地maven仓库
可是多写一个实现类
public class OracleDriver implements baseDriver {
public void url() {
System.out.println("this is an oracle url");
}
}
也要再次打包
- 新建maven项目使用
pom.xml
com.spi MYSQL_SPI 0.0.1-SNAPSHOT com.spi ORACLE_SPI 0.0.1-SNAPSHOT
- 测试
我们知道:Servlet 2.5 实现 webApp 加载的方式是 web.xml,获取其中配置的ServletContextListener 的实现类,通过实现类的 contextInitialized 方法来加载 webapp。
org.springframework.web.context.ContextLoaderListener
具体可参考我另一篇文章
而 Servlet 3.0 就无需配置 web.xml, 直接通过实现接口 ServletContainerInitializer 就可以实现 webApp 的加载,就如类名一样,这个类的作用就是在 servlet 容器初始化过程中加入自定义操作,因为是自定义的,所以可扩展性就非常强,能干很多事情。
两者执行位置的区别
两个方法的执行位置都是在 Tomcat 启动过程中,Context 容器启动时。具体方法是 StandardContext 类的 startInternal() 方法,一个 context 容器就代表了一个 webapp。
ServletContainerInitializer源码
public interface ServletContainerInitializer {
public void onStartup(Set> c, ServletContext ctx)
throws ServletException;
}
那么servlet3.0规范下,是如何不通过web.xml的方式,使用纯java代码的方式加载spring应用的呢
直接看到spring-web:5.3.13的源码:
没错,tomcat启动后,可以根据servlet3.0的规范,通过SPI机制,将实现了ServletContainerInitializer 接口的类全部进行加载,并排序后,依次调用onstartup方法
在看看javax.servlet.ServletContainerInitializer的内容:
org.springframework.web.SpringServletContainerInitializer
于是,我们直接找到SpringServletContainerInitializer
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List initializers = Collections.emptyList();
if (webAppInitializerClasses != null) {
initializers = new ArrayList<>(webAppInitializerClasses.size());
for (Class> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
这里要先知道@HandleTypes这个注解的含义,该注解也是Servlet规范所定义的,作用就是:将注解指定的Class对象(包括其实现类及子类)作为参数传递到onStartup(也就是@HandleTypes只能作用在实现了ServletContainerInitializer 的类上)
//含义就是,将WebApplicationInitializer.class的子类获取实现类, //作为一个Set>参数传入道到SpringServletContainerInitializer.onstartup方法中 @HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer {}
然后,我们就可以在onstartup方法中,进行我们的开发,例如初始化web容器,初始化DispatcherServlet等操作
AbstractContextLoaderInitializer.onstartup:
public void onStartup(ServletContext servletContext) throws ServletException {
this.registerContextLoaderListener(servletContext);
}
AbstractDispatcherServletInitializer.onstartup:
public void onStartup(ServletContext servletContext) throws ServletException {
//调用父类
super.onStartup(servletContext);
this.registerDispatcherServlet(servletContext);
}
参考文章
参考文章



