为啥说java spi破坏双亲委派模型( 五 )

如果调用的是:
ServiceLoader.load(Class\u0026lt;?\u0026gt; clazz);是没有破坏双亲委派模型的。
但是还有一个:
ServiceLoader.load(Class\u0026lt;?\u0026gt; clazz,ClassLoader classLoader);自定义一个ClassLoader并重写loadClass方法是可以打破双亲委派模型的。

■网友
多少有点IOC的味道。
但凡你觉得没有的代码,设计者们总会有办法把它们解决掉,比如:
Class.forName以及(Connection、Statement、ResultSet、FileInputStream等等).close();
...

■网友
我认为没有SPI没有破坏双亲委派模型,破坏双亲委派模型即重写ClassLoader#loadClass方法,不采用双亲委派模型。我们来看SPI是如何加载服务提供方实现的:启动类加载器加载了SPI,SPI里有段静态代码块显式使用线程上下文类加载器显式加载了SPI实现类,这个线程上下文类加载器通过Thread#getContextClassLoader方法获得,可以set,不set的情况下默认是AppClassLoader。请问各位,加载SPI实现类的AppClassLoader重写loadClass了吗?显然没有。只是在被父类加载器加载的接口中用子类加载器显示加载了SPI实现罢了。

■网友
以java.sql.Driver为例子,带你梭哈一下源代码
//boot加载的,从委任模型来说,ServiceLoader里边出现的class也要boot去加载//但是可以通过指定classLoader去规避这一点,SPI用到这个,pinpoint用到这里ServiceLoader\u0026lt;Driver\u0026gt; load = ServiceLoader.load(Driver.class);public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader);}//LazyIterator(Lazy这个单词很重要) 实现了Iteratorprivate LazyIterator(Class\u0026lt;S\u0026gt; service, ClassLoader loader) { this.service = service; this.loader = loader;}//看Iterator的实现private S nextService() { String cn = nextName; //Class.forName(cn, false, loader); 这里的loader很重要,如果没有指定loader,那么使用的是系统上下文的loader(如果说破坏SPI,应该指的是这里) Class\u0026lt;?\u0026gt; c = Class.forName(cn, false, loader); S p = service.cast(c.newInstance()); providers.put(cn, p); return p;}那么现在,你可以手动指定ClassLoader为extClassLoader试试,会有不一样的发现

■网友
1、还有为什么要用spi机制,只是为了方便吗?
使用SPI是定义规范,具体实现由厂商提供,比如JDBC就是一套规范,对应实现由具体的数据库厂商实现。
2、spi加载的类通过Class.forName()是不是同样可以加载?
3、spi所用的classLoader与Class.forName()所用的是不是同一个loader?
这俩是一个问题,Class.forName()调用时候,Class类本身是由启动类加载器加载的,SPI也是由启动类加载器(BootStrapClassLoader)加载的,但是SPI具体的实现由各个厂商提供,BootStrapClassLoader不可能加载到,所以那个Class.forName()去加载SPI实现类的时候,需要指定一个类加载器,那么这个类加载器就是调用者的类加载器,参考jdk源码:
/** * Returns the {@code Class} object associated with the class or * interface with the given string name. Invoking this method is * equivalent to: * * \u0026lt;blockquote\u0026gt; * {@code Class.forName(className, true, currentLoader)} * \u0026lt;/blockquote\u0026gt; * * where {@code currentLoader} denotes the defining class loader of * the current class. * * \u0026lt;p\u0026gt; For example, the following code fragment returns the * runtime {@code Class} descriptor for the class named * {@code java.lang.Thread}: * * \u0026lt;blockquote\u0026gt; * {@code Class t = Class.forName("java.lang.Thread")} * \u0026lt;/blockquote\u0026gt; * \u0026lt;p\u0026gt; * A call to {@code forName("X")} causes the class named * {@code X} to be initialized. * * @param className the fully qualified name of the desired class. * @return the {@code Class} object for the class with the * specified name. * @exception LinkageError if the linkage fails * @exception ExceptionInInitializerError if the initialization provoked * by this method fails * @exception ClassNotFoundException if the class cannot be located */ @CallerSensitive public static Class\u0026lt;?\u0026gt; forName(String className) throws ClassNotFoundException { Class\u0026lt;?\u0026gt; caller = Reflection.getCallerClass(); return forName0(className, true, ClassLoader.getClassLoader(caller), caller); }


推荐阅读