博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java Service Provider Interface
阅读量:4983 次
发布时间:2019-06-12

本文共 9563 字,大约阅读时间需要 31 分钟。

之前

maven 的runtime作用范围

其中JDBC驱动通常使用Class.forName("com.mysql.jdbc.Driver");来引入所需要的驱动。在编译期间不用引入具体jdbc的具体实现类(不管mysql还是oracle等)。所以JDBC包的scope应该设置为runtime。

mysql
mysql-connector-java
${mysql.driver.version}
runtime
复制代码

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.FooImpl1ch.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
loader = ServiceLoader.load(Foo.class); List
foos = new ArrayList<>(); loader.iterator().forEachRemaining(foos::add); assertThat(foos) .isNotNull() .isNotEmpty() .hasSize(2); }}复制代码

Spring 集成 ServiceLoader

首先需要配置对应的factoryBean

@Configurationpublic 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
foos = SpringFactoriesLoader.loadFactories(Foo.class, null); assertThat(foos) .isNotNull() .isNotEmpty() .hasSize(2); }}复制代码

Service Provider Interface的应用

JDBC SPI

查看jdbc的代码

//java.sql.DriverManagerpublic 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
() { 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
() { public Void run() { ServiceLoader
loadedDrivers = ServiceLoader.load(Driver.class); Iterator
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.Drivercom.mysql.fabric.jdbc.FabricMySQLDriver复制代码

文件中有两个驱动名。

其实jdbc可以同时管理多个驱动,()

Commons-Logging

//org.apache.commons.logging.LogFactory#getFactorypublic 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中找要加载的实现类()。

slf4j

而slf4j不是通过SPI来找实现类的。slf4j 1.7是通过找一个固定包下的固定类StaticLoggerBinder类(而SPI是找固定文件下的内容)。这个类定义在各个实现包中。

貌似slf4j 1.8 开始使用SPI了,如下图。

转载于:https://juejin.im/post/5b20ca526fb9a01e67067946

你可能感兴趣的文章
ECharts
查看>>
初识网络爬虫
查看>>
git push 时不用每次都输入密码的方法
查看>>
54点提高PHP编程效率 引入缓存机制提升性能
查看>>
编解码-marshalling
查看>>
CDN原理
查看>>
java.lang.outofmemoryerror android
查看>>
coding
查看>>
省市联级(DataReader绑定)
查看>>
20165219 课上内容补做
查看>>
Tomcat7.0与Oracle10数据库连接池配置
查看>>
解决webpack和gulp打包js时ES6转译ES5时Object.assign()方法没转译成功的问题
查看>>
字节流与字符流的区别详解(转)
查看>>
类操作数据库
查看>>
找球号(一)
查看>>
oracle ebs 笔记
查看>>
Android studio使用git-android学习之旅(79)
查看>>
eclipse中去掉Js/javsscript报错信息
查看>>
网络中,FIFO、LRU、OPT这三种置换算法的缺页次数
查看>>
随机森林算法参数调优
查看>>