暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

druid采用spi初始化filter

磊哥谈技术 2018-11-07
1313

       SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。实现机制通过final类ServiceLoader实现,该类实现了Iterable接口。

      druid采用spi初始化filter的代码非常的精简,方法名为initFromSPIServiceLoader,方法代码如下所示:只要实现了druid的filter接口,不关实现类在哪个jar包,只要稍微配置一下就能实现动态加载,是不是非常的神奇?

private void initFromSPIServiceLoader() {

String property = System.getProperty("druid.load.spifilter.skip");
   if (property != null) {
return;
   }

ServiceLoader<Filter> druidAutoFilterLoader = ServiceLoader.load(Filter.class);

   for (Filter autoFilter : druidAutoFilterLoader) {
AutoLoad autoLoad = autoFilter.getClass().getAnnotation(AutoLoad.class);
       if (autoLoad != null && autoLoad.value()) {
if (LOG.isInfoEnabled()) {
LOG.info("load filter from spi :" + autoFilter.getClass().getName());
           }
addFilter(autoFilter);
       }
}
}

       方法的代码也很容易就能看懂,通过ServiceLoader.load(Filter.class),创建了ServiceLoader对象druidAutoFilterLoader,然后foreach该对象就能拿到所有filter对象。所以,要搞清楚SPI的实现原理,就要弄明白ServiceLoader的工作流。

      ServiceLoader.load(Filter.class)最终调用了如下构造方法创建了ServiceLoader对象。此处分别对成员service,loader及acc进行了赋值,并调用了reload()方法。

private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
   loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
   acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
   reload();
}

       reload方法的定义如下:方法中创建了LazyIterator的对象lookupIterator。

public void reload() {
providers.clear();
   lookupIterator = new LazyIterator(service, loader);
}

       LazyIterator是ServiceLoader的内部类,构造lookupIterator的代码如下,只是完成了成员service和loader的赋值。

private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
   this.loader = loader;
}

       至此,load过程已经结束了,我们并没有看到filter实现类的创建过程。通过LazyIterator名称中包含的Lazy,我们可以猜测,filter对象的创建是一种延迟创建,并不在load过程中创建。      

       继续结合initFromSPIServiceLoader()方法,创建filter对象的时机只能是foreach遍历的时候了。通过查看ServiceLoader类的iterator方法的实现过程,发现hasNext()和next()方法,分别调用了LazyIterator的hasNext()和next()方法。所以说,实现的关键又回到了LazyIterator类。而LazyIterator的hasNext()和next()方法,分别调用了自己的hasNextService()和nextService()方法。

       最后我们将LazyIterator类的nextService()方法的实现代码摘出来,如下所示:

private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
   String cn = nextName;
   nextName = null;
   Class<?> c = null;
   try {
c = Class.forName(cn, false, loader);
   } catch (ClassNotFoundException x) {
fail(service,
            "Provider " + cn + " not found");
   }
if (!service.isAssignableFrom(c)) {
fail(service,
            "Provider " + cn + " not a subtype");
   }
try {
S p = service.cast(c.newInstance());
       providers.put(cn, p);
       return p;
   } catch (Throwable x) {
fail(service,
            "Provider " + cn + " could not be instantiated",
            x);
   }
throw new Error();          // This cannot happen
}

       终于找到了我们期望的Class.forName()方法。从而验证了,filter对象的创建时机确实延迟到foreach遍历druidAutoFilterLoader对象的时候。       

      既然整个流程我们已经了解清楚了,前面也提到过通过SPI只要简单的配置就能实现service的动态加载,那么到底如何配置呢?

       我们发现ServiceLoader类中有一个关键成员PREFIX,成员值如下:

 private static final String PREFIX = "META-INF/services/";

      当我们看到META-INF的时候,是否已经猜到了配置文件应该存放的路径了?      

       我们继续查找,发现PREFIX被使用的方法是hasNextService()方法,详细代码如下:

private boolean hasNextService() {
if (nextName != null) {
return true;
   }
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
           if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
           else
               configs = loader.getResources(fullName);
       } catch (IOException x) {
fail(service, "Error locating configuration files", x);
       }
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
       }
pending = parse(service, configs.nextElement());
   }
nextName = pending.next();
   return true;
}

        通过此方法我们能看出,配置文件需要存放在META-INF/services/目录下,文件必须以接口的名称(包含包路径)命名。而文件中保存的就是各式各样的实现了该接口的类名(包含包路径),至此,我们理清楚了SPI的实现原理。

      mysql的驱动程序已经完成了SPI的文件配置,我们不妨用它的配置格式来验证一下我们的结论,配置截图如下所示:


文章转载自磊哥谈技术,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论