什么是SPI机制?
SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用于启用框架扩展和替换组件
如:java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现
Java中SPI机制的主要思想就是将装配的控制权移到程序之外,在模块化设计中,这个机制尤其重要,其核心思想就是解耦
SPI简单案例
目录结构:
package com.comtom.spi;
import java.util.List;
public interface Search {
public List<String> searchDoc(String keyword);
}
package com.comtom.spi;
import java.util.List;
public class FileSearch implements Search{
@Override
public List<String> searchDoc(String keyword) {
System.out.println("文件搜索"+keyword);
return null;
}
}
package com.comtom.spi;
import java.util.List;
public class DataBaseSearch implements Search{
@Override
public List<String> searchDoc(String keyword) {
System.out.println("数据库搜索"+keyword);
return null;
}
}
第四步:在resource目录下创建目录/META-INF/services
,在该目录下创建Search类的全限定名路径文件,如下图所示:
注:IDEA需要选择文件类型,右键选择Override File Type
,弹框Choose File Type
,选择textmate
在com.comtom.spi.Search
文件中添加FileSearch类的全限定名或DataBaseSearch类的全限定名
com.comtom.spi.FileSearch
com.comtom.spi.DataBaseSearch
开始测试
public class Main {
public static void main(String[] args) {
ServiceLoader<Search> s = ServiceLoader.load(Search.class);
Iterator<Search> iterator = s.iterator();
while (iterator.hasNext()) {
Search next = iterator.next();
next.searchDoc("hello world");
}
}
}
结果:在com.comtom.spi.Search
文件添加几个类全限定名,就会有几条结果数据
文件搜索hello world
数据库搜索hello world
SPI机制的应用
JDBC DriverManager
在JDBC4.0之前,连接数据库需要使用
Class.forName("com.mysql.jdbc.Driver")
先加载数据库相关的驱动,然后再获取连接等操作,在JDBC4.0以后,不需要使用Class.forName("com.mysql.jdbc.Driver")
加载驱动,就可以直接获取连接,这种方式就是使用了SPI机制
-
Java在rt.jar包中定义了
java.sql.Driver
接口,没有具体的实现,类似于上述案例中的Search接口 -
在mysql的jar包
mysql-connector-java-5.1.49.jar
中,有META-INF/services
目录,里面的文件定义了驱动实现类,类似于上述案例的FileSearch和DataBaseSearch -
在建立连接时,就会在加载类的时候加载驱动
-
loadInitialDrivers()方法就会调用Driver.class对应的权限定名文件,读取对应实现类,类似测试时的操作
loadInitialDrivers()方法实现步骤如下:-
从系统变量中获取有关驱动的定义
-
使用SPI来获取驱动的实现
-
遍历使用SPI获取到的具体实现,实例化各个实现类
-
根据第一步获取到的驱动列表来实例化具体实现类
-
SPI机制的实现步骤:
-
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
封装接口类型和类加载器,并初始化了一个迭代器 -
Iterator<Driver> driversIterator = loadedDrivers.iterator();
获取迭代器 -
遍历迭代器,调用
driversIterator.hasNext()
方法时会去resource目录下所有的META-INF/services
目录下的java.sql.Driver
文件,并找到文件中的实现类的名字 -
调用
driversIterator.next();
方法,此时就会根据驱动名字具体实例化各个实现类
Common Logging
common-logging(也称为Jakarta Commons Logging,JCL)是常用的日志库门面
JCL依赖
<!--引入common-logging-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
日志的实例是通过LogFactory的getLog()方法创建的:
public static getLog(Class clazz) throws LogConfigurationException {
return getFactory().getInstance(clazz);
}
分析getFactory方法做了什么
public static LogFactory getFactory() throws LogConfigurationException {
//获取一个类加载器
ClassLoader contextClassLoader = getContextClassLoaderInternal();
//如果类加载器为空,且经过诊断确认,则输出类加载器为空
if (contextClassLoader == null && isDiagnosticsEnabled()) {
logDiagnostic("Context classloader is null.");
}
//返回这个类加载器注册过的日志工厂
LogFactory factory = getCachedFactory(contextClassLoader);
//如果注册过,直接返回,没有注册过,
if (factory != null) {
return factory;
} else {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] LogFactory implementation requested for the first time for context classloader " + objectId(contextClassLoader));
logHierarchy("[LOOKUP] ", contextClassLoader);
}
//加载properties文件
Properties props = getConfigurationFile(contextClassLoader, "commons-logging.properties");
ClassLoader baseClassLoader = contextClassLoader;
String factoryClass;
if (props != null) {
//如果commons-logging.properties配置文件存在,且文件中配置了use_tccl参数,参数值为false,则将当前类加载器赋值给获取到的日志类加载器
factoryClass = props.getProperty("use_tccl");
if (factoryClass != null && !Boolean.valueOf(factoryClass)) {
baseClassLoader = thisClassLoader;
}
}
//决定使用哪个factory
//首先尝试查找vm系统中org.apache.commons.logging.LogFactory,并判断其是否可以指定为factory
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Looking for system property [org.apache.commons.logging.LogFactory] to define the LogFactory subclass to use...");
}
try {
factoryClass = getSystemProperty("org.apache.commons.logging.LogFactory", (String)null);
if (factoryClass != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Creating an instance of LogFactory class '" + factoryClass + "' as specified by system property " + "org.apache.commons.logging.LogF