maven 的runtime做用范围java
其中JDBC驱动一般使用Class.forName("com.mysql.jdbc.Driver");来引入所须要的驱动。在编译期间不用引入具体jdbc的具体实现类(无论mysql仍是oracle等)。因此JDBC包的scope应该设置为runtime。mysql
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.driver.version}</version> <scope>runtime</scope> </dependency>
JDBC4以后使用SPI技术
也不用写Class.forName了spring
Java API提供的使用ServiceLoader来实现的控制反转工具类。用于从classpath中找到接口的实现类。sql
先定义一个接口数据库
package ch.frankel.blog.serviceloader; public interface Foo { }
再定义两个实现类(实现类多是其余工程中定义的)apache
package ch.frankel.blog.serviceloader; public class FooImpl1 implements Foo { } public class FooImpl2 implements Foo { }
而后在Resources/META-INF下的service中新建一个与接口名对应的文件ch.frankel.blog.serviceloader.Foooracle
ch.frankel.blog.serviceloader.FooImpl1 ch.frankel.blog.serviceloader.FooImpl2
serviceLoader会从该文件中找到要加载的类。maven
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); } }
首先须要配置对应的factoryBeanide
@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 本身也提供了相似的工具类。使用起来更方便。
首先在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); } }
查看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详解:二、DriverManager管理多个数据库驱动)
//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不是经过SPI来找实现类的。slf4j 1.7是经过找一个固定包下的固定类StaticLoggerBinder类(而SPI是找固定文件下的内容)。这个类定义在各个实现包中。
貌似slf4j 1.8 开始使用SPI了,以下图。