一、SPI简介
1.概念:
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。SPI的作用就是为这些被扩展的API寻找服务实现。简单来说就将接口与实现相分离,可以通过外部文件来灵活的控制接口实现。这不是本篇主要内容有个概念即可。
2.实际使用场景:
- 数据库驱动加载接口实现类的加载,JDBC加载不同类型数据库的驱动
- 日志门面接口实现类加载,SLF4J加载不同提供商的日志实现类
- Spring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等
3.使用方法:
要使用Java SPI,需要遵循如下约定:
1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
2、接口实现类所在的jar包放在主程序的classpath中;
3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
4、SPI的实现类必须携带一个不带参数的构造方法;
二、springMvc中的具体使用
根据上面使用方法的叙述我们大致已经了解到SPI的使用方法,下面就去mvc源码中看看它到底是不是这么做的:
内容
org.springframework.web.SpringServletContainerInitializer
可以看到文件名为ServletContainerInitializer的全限定名,其为servlet为我们提供的一个接口,并会在tomcat,jetty等web容器启动时调用
SpringServletContainerInitializer实现了该接口
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
:
if (webAppInitializerClasses != null) {
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);
}
}
}
首先我们看到的是一个很陌生的注解,@HandlesTypes(WebApplicationInitializer.class)。其实在ServletContainerInitializer的注释中我们就已经发现有对次注解做简单介绍。其主要作用就是将当前注释中接口的实现类加入到webAppInitializerClasses中,避免我们有很多的实现类需要在启动时调用,重复的配置写很多配置文件。当我们有其它方法或类需要调用时,只需要在当前实现了ServletContainerInitializer规范的类上加一个@HandlesTypes注解里面提供一个自己定义的接口。当tomcat启动时就会把例如WebApplicationInitializer接口所有的实现类扫描到webAppInitializerClasses集合中。
继续往下看spring在当前onStartup方法中做了什么,首先可以看到:
List<WebApplicationInitializer> initializers = new LinkedList<>();
定义了一个list集合来存放我们的WebApplicationInitializer对象,因为方法参数Set中存放的是类。class是只能调用静态方法的,所以这里需要我们类的对象来分别调用各自onStartup方法。下面做的事情也很简单通过遍历利用反射new出每个类的对象存入了initializers 中。
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
最后调用每个对象的onStartup方法来进行初始化。
至于调用了那些类的onStartup方法我们可以点击看一下:
曾经我们手动配置的dispatcherServlet,ContextLoaderListener等XML配置正是在这些实现类中进行了初始化,从而避免了我们手动配置。
就拿AbstractContextLoaderInitializer实现类为例我们看看其onStartup方法做了什么:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
/**
* Register a {@link ContextLoaderListener} against the given servlet context. The
* {@code ContextLoaderListener} is initialized with the application context returned
* from the {@link #createRootApplicationContext()} template method.
* @param servletContext the servlet context to register the listener against
*/
protected void registerContextLoaderListener(ServletContext servletContext) {
// Root
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
// 创建一个监听对象
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
// 将监听器加入到上下文
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
这段javaConfig正对应着我们曾经的下面这段配置
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
其余很多配置就不去一一看了,到这里我们已经基本明白spring0XML配置的全部流程,下面简单总结一下:
1.通过servlet3.0实现的spi机制,指定在tomcat启动时调用ServletContainerInitializer的实现类SpringServletContainerInitializer中的onStartup方法
2.利用@HandlesTypes(WebApplicationInitializer.class)注解,在tomcat启动时将WebApplicationInitializer所有实现类扫描到Servlet ContainerInitializer的set集合中。
3.定义一个list集合存放符合要求的WebApplicationInitializer对象
4.遍历调用每个WebApplicationInitializer对象的onStartup方法来初始化,从而省去繁琐的XML配置。(一切根源都在/META-INF/services下)