少侠你现在是否还这样连接数据库
1 2 3 4 5 6 try { Class.forName("com.mysql.jdbc.Driver" ); Connection connection = DriverManager.getConnection("URL" ,"root" ,"password" ); } catch (Exception e) { e.printStackTrace(); }
少侠你知道Class.forName()
这句早已经没用了么?Java 官方已经把它优化掉了。想知道怎么优化的么?不要急,且看我慢慢道来。
SPI SPI 全名:Service Provider Interface,是 JDK 内置的一种服务发现机制,可以将服务接口与服务发现分离以达到解耦的效果。可以提升程序的可扩展性。比如 jdbc 只提供了接口,具体的实现由各个运营商解决、经常用的 log 日志类LogFactory
,还有阿里的 dubbo 更是将 SPI 运用的淋漓尽致。
SPI 使用说明:
在 resources 目录下新建 META-INF/services 目录,并在目录下创建一个文件,文件名字为接口
的全限定名,文件内容为实现类
的全限定名。
通过java.util.ServiceLoder
类加载实现类。
SPI 的实现类必须携带一个不带参数的构造方法。
来个示例
首先定义一个接口
1 2 3 public interface Pay { void pay () ; }
再来两个实现类
1 2 3 4 5 6 public class AliPay implements Pay { @Override public void pay () { System.out.println("AliPay" ); } }
1 2 3 4 5 6 public class WeChatPay implements Pay { @Override public void pay () { System.out.println("WeChatPay" ); } }
在 resources 目录下新建 META-INF/services 目录,并在目录下创建一个文件,文件名为接口的权限定名com.yao.spi.Pay
,文件内容为实现类的全限定名
通过java.util.ServiceLoder
获取实现类。
1 2 3 4 5 6 7 8 9 10 public static void main (String[] args) { ServiceLoader<Pay> spiDemoServiceLoader = ServiceLoader.load(Pay.class); Iterator<Pay> payIterator = spiDemoServiceLoader.iterator(); while (payIterator.hasNext()){ Pay pay = payIterator.next(); pay.pay(); } }
看一下结果。
有没有注意到ServiceLoder
获取到的是一个列表?这说明什么问题?对的,可以一次初始化多个实现类,只要在文件中声明就可以了。现在再把WechatPay
给添加上
1 2 com.yao.spi.AliPay com.yao.spi.WeChatPay
看一下运行结果
源码解析 SPI 的功能比较简单,一个ServiceLoader
类包含所有的功能。先来看一下有那些主要的属性和内部类的主要属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public final class ServiceLoader <S > implements Iterable <S > { private static final String PREFIX = "META-INF/services/" ; private final Class<S> service; private final ClassLoader loader; private LinkedHashMap<String, S> providers = new LinkedHashMap(); private ServiceLoader<S>.LazyIterator lookupIterator; private class LazyIterator implements Iterator <S > { Class<S> service; ClassLoader loader; Enumeration<URL> configs; Iterator<String> pending; String nextName; } }
当看到内部类LazyIterator
这个Lazy
时,会不会联想到 spring 加载 bean 时的懒加载。这里其实也是一样,当调用load
方法时,并不会加载类,只初始化一些内部属性。真正加载实现类是在获取实现类时。
沿着上面main
方法第一次获取实现类的过程一步一步解析,第一次以后获取的过程有兴趣的少侠可以自己走一遍。
注册实现类 spiDemoServiceLoader.iterator();
获取的迭代器是ServiceLoader
重写的迭代器,迭代器中的方法都指向了内部类LazyIterator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public Iterator<S> iterator () { return new Iterator<S>() { Iterator<Entry<String, S>> knownProviders; { this .knownProviders = ServiceLoader.this .providers.entrySet().iterator(); } public boolean hasNext () { return this .knownProviders.hasNext() ? true : ServiceLoader.this .lookupIterator.hasNext(); } public S next () { return this .knownProviders.hasNext() ? ((Entry)this .knownProviders.next()).getValue() : ServiceLoader.this .lookupIterator.next(); } public void remove () { throw new UnsupportedOperationException(); } }; }
当调用payIterator.hasNext()
时,调用的是重写迭代器中的
1 2 3 public boolean hasNext () { return this .knownProviders.hasNext() ? true : ServiceLoader.this .lookupIterator.hasNext(); }
然后会调用内部类LazyIterator
中的hasNext()
方法
1 2 3 4 5 6 7 public boolean hasNext () { if (ServiceLoader.this .acc == null ) { return this .hasNextService(); } else { } }
接着调用调用内部类LazyIterator
中的hasNextService()
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 private boolean hasNextService () { if (this .nextName != null ) { return true ; } else { if (this .configs == null ) { try String var1 = "META-INF/services/" + this .service.getName(); if (this .loader == null ) { this .configs = ClassLoader.getSystemResources(var1); } else { this .configs = this .loader.getResources(var1); } } catch (IOException var2) { } } while (this .pending == null || !this .pending.hasNext()) { if (!this .configs.hasMoreElements()) { return false ; } this .pending = ServiceLoader.this .parse(this .service, (URL)this .configs.nextElement()); } this .nextName = (String)this .pending.next(); return true ; } }
第一次到这里时,会将配置文件的信息保存到到内部类中的’configs’属性中,并解析文件将配置的所有实现类保存到内部类中的pending
中,但是这里还是没有未初始化类。
获取实现类 当调用payIterator.next();
时,调用的是重写的
1 2 3 public S next () { return this .knownProviders.hasNext() ? ((Entry)this .knownProviders.next()).getValue() : ServiceLoader.this .lookupIterator.next(); }
并最终调用到内部类LazyIterator
中的next()
方法
1 2 3 4 5 6 7 8 9 10 11 12 public S next () { if (ServiceLoader.this .acc == null ) { return this .nextService(); } else { PrivilegedAction var1 = new PrivilegedAction<S>() { public S run () { return LazyIterator.this .nextService(); } }; return AccessController.doPrivileged(var1, ServiceLoader.this .acc); } }
然后调用内部类LazyIterator
中的nextService()
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private S nextService () { if (!this .hasNextService()) { throw new NoSuchElementException(); } else { String var1 = this .nextName; this .nextName = null ; Class var2 = null ; try { var2 = Class.forName(var1, false , this .loader); } catch (ClassNotFoundException var5) { } } }
看到Class.forName()
,到此才是初始化实现类。
JDBC 中的运用 回到最初的话题,既然Class.forName()
没有用了,那JDBC在那优化的呢?首先要知道Class.forName()
的作用:强制JVM将com.mysql.jdbc.Driver这个类加载入内存,以便DriverManager
类使用,现在既然用不到了,那肯定是在别的地方加载了。那用什么方式加载的?当然是SPI了,要不然前面一大堆东西不是白说了么,哈哈哈哈!看看mysql的jar里 是不是很熟悉,这不就是SPI规范么。
OK,来看看加载的过程。 既然只需要这一句就可以了
1 Connection connection = DriverManager.getConnection("URL" ,"root" ,"password" );
加载 那就从DriverManager
类开始,类加载当然要从静态代码块开始了,看一下DriverManager
的静态代码块。
1 2 3 4 5 6 static { loadInitialDrivers(); println("JDBC DriverManager initialized" ); SET_LOG_PERMISSION = new SQLPermission("setLog" ); DEREGISTER_DRIVER_PERMISSION = new SQLPermission("deregisterDriver" ); }
进loadInitialDrivers()
方法 熟不熟悉?是不是SPI机制?DriverManager
在初始化类时,会先利用SPI机制加载数据库的驱动。那又是怎么注册到DriverManager
的呢?继续看!
加载 在上面SPI机制中说了,当调用next()
方法时会初始化实现类,也就是会初始化Driver
类,那就再看Driver
类。
1 2 3 4 5 6 7 8 9 10 11 12 public class Driver extends NonRegisteringDriver implements java .sql .Driver { static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!" ); } } public Driver () throws SQLException { } }
静态代码块中DriverManager.registerDriver(new Driver());
就是将Driver
注册到DriverManager
。 总结一下加载过程:
DriverManager在静态代码块中利用SPI机制获取到数据库驱动,并初始化Driver
Driver初始化时首先运行静态代码块将Driver注册到DriverManager