之前
maven 的runtime作用范围
其中JDBC驱动通常使用Class.forName("com.mysql.jdbc.Driver");来引入所需要的驱动。在编译期间不用引入具体jdbc的具体实现类(不管mysql还是oracle等)。所以JDBC包的scope应该设置为runtime。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.driver.version}</version>
<scope>runtime</scope>
</dependency>
JDBC4之后使用SPI技术
也不用写Class.forName了
Service Provider Interface
Java API提供的使用ServiceLoader来实现的控制反转工具类。用于从classpath中找到接口的实现类。
先定义一个接口
package ch.frankel.blog.serviceloader;
public interface Foo {
}
再定义两个实现类(实现类可能是其他工程中定义的)
package ch.frankel.blog.serviceloader;
public class FooImpl1 implements Foo {
}
public class FooImpl2 implements Foo {
}
然后在Resources/META-INF下的service中新建一个与接口名对应的文件ch.frankel.blog.serviceloader.Foo
ch.frankel.blog.serviceloader.FooImpl1
ch.frankel.blog.serviceloader.FooImpl2
serviceLoader会从该文件中找到要加载的类。
public class JavaServiceLoaderTest {
@Test
public void java_service_loader_should_load_correct_implementations() {
//Service.load会从ch.frankel.blog.serviceloader.Foo中找到要加载的类,然后加载。
ServiceLoader<Foo> loader = ServiceLoader.load(Foo.class);
List<Foo> foos = new ArrayList<>();
loader.iterator().forEachRemaining(foos::add);
assertThat(foos)
.isNotNull()
.isNotEmpty()
.hasSize(2);
}
}
Spring 集成 ServiceLoader
首先需要配置对应的factoryBean
@Configuration
public class ServiceConfiguration {
@Bean
public ServiceListFactoryBean serviceListFactoryBean() {
ServiceListFactoryBean serviceListFactoryBean = new ServiceListFactoryBean();
serviceListFactoryBean.setServiceType(Foo.class);
return serviceListFactoryBean;
}
}
然后通过ServiceListFactoryBean就可以找到接口对应的实现类。
@ContextConfiguration(classes = ServiceConfiguration.class)
public class ServiceLoaderWithSpringTest extends AbstractTestNGSpringContextTests {
@Autowired
private ServiceListFactoryBean serviceListFactoryBean;
@Test
public void spring_service_loader_integration_should_load_correct_implementations() throws Exception {
Object object = serviceListFactoryBean.getObject();
Assertions.assertThat(object)
.isNotNull()
.asList()
.isNotEmpty()
.hasSize(2);
}
}
Spring Facotories Loader
spring 自己也提供了类似的工具类。使用起来更方便。
首先在META-INF下建立一个spring.factories文件。
ch.frankel.blog.serviceloader.Foo=ch.frankel.blog.serviceloader.FooImpl1,ch.frankel.blog.serviceloader.FooImpl2
直接指定接口对应的实现类。
然后通过Spring提供的静态方法SpringFactoriesLoader就可以直接使用了。
public class SpringFactoriesTest {
@Test
public void spring_factories_should_load_correct_implementations() {
List<Foo> foos = SpringFactoriesLoader.loadFactories(Foo.class, null);
assertThat(foos)
.isNotNull()
.isNotEmpty()
.hasSize(2);
}
}
Service Provider Interface的应用
JDBC SPI
查看jdbc的代码
//java.sql.DriverManager
public class DriverManager {
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
//从系统变量中获取jdbc.drivers
//System.getProperty 可以在vm arguments中设置。java -Djdbc.drivers=xxxx
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
//多个drivers?
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
//还是使用Class.forName来加载驱动
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
}
在jdbc的实现包中META-INF下有java.sql.Driver文件。
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
文件中有两个驱动名。
其实jdbc可以同时管理多个驱动,(jdbc详解:2、DriverManager管理多个数据库驱动)
Commons-Logging
//org.apache.commons.logging.LogFactory#getFactory
public static LogFactory getFactory() throws LogConfigurationException {
...
// Second, try to find a service by using the JDK1.3 class
// discovery mechanism, which involves putting a file with the name
// of an interface class in the META-INF/services directory, where the
// contents of the file is a single line specifying a concrete class
// that implements the desired interface.
if (factory == null) {
if (isDiagnosticsEnabled()) {
logDiagnostic(
"[LOOKUP] Looking for a resource file of name [" + SERVICE_ID
+ "] to define the LogFactory subclass to use...");
}
try {
InputStream is = getResourceAsStream(contextClassLoader,
SERVICE_ID);
if( is != null ) {
// This code is needed by EBCDIC and other strange systems.
// It's a fix for bugs reported in xerces
BufferedReader rd;
try {
rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
} catch (java.io.UnsupportedEncodingException e) {
rd = new BufferedReader(new InputStreamReader(is));
}
String factoryClassName = rd.readLine();
rd.close();
if (factoryClassName != null &&
! "".equals(factoryClassName)) {
if (isDiagnosticsEnabled()) {
logDiagnostic(
"[LOOKUP] Creating an instance of LogFactory class " + factoryClassName
+ " as specified by file '" + SERVICE_ID
+ "' which was present in the path of the context"
+ " classloader.");
}
factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader );
}
} else {
// is == null
if (isDiagnosticsEnabled()) {
logDiagnostic(
"[LOOKUP] No resource file with name '" + SERVICE_ID
+ "' found.");
}
}
} catch( Exception ex ) {
// note: if the specified LogFactory class wasn't compatible with LogFactory
// for some reason, a ClassCastException will be caught here, and attempts will
// continue to find a compatible class.
if (isDiagnosticsEnabled()) {
logDiagnostic(
"[LOOKUP] A security exception occurred while trying to create an"
+ " instance of the custom factory class"
+ ": [" + ex.getMessage().trim()
+ "]. Trying alternative implementations...");
}
; // ignore
}
}
...
}
其中也使用到SPI。从 META-INF/services/org.apache.commons.logging.LogFactory中找要加载的实现类(Apache Commons Logging 是如何决定使用哪个日志实现类的)。
slf4j
而slf4j不是通过SPI来找实现类的。slf4j 1.7是通过找一个固定包下的固定类StaticLoggerBinder类(而SPI是找固定文件下的内容)。这个类定义在各个实现包中。
貌似slf4j 1.8 开始使用SPI了,如下图。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。