关于服务的加载
同志们还记得我们连接MySQL的时候,我们需要导入jar包,然后第一段代码必须是:
Class.forName("com.mysql.jdbc.Driver");
以前不知道这个类是什么意思,现在当然很清楚。Java只是将常用的类加载到内存中,其他的外部类不会自动加载进内存。所以这里是驱动程序的装载过程。
那么问题来了,java为我们提供了很多的服务,那么这些服务的驱动程序是如何被加载进来的呢?由于是自己研读源码,所有肯定有错误之处,欢迎各位指正,这里也是我的猜测。
这里举个例子吧:NameService,这个调用处的代码在InetAddress的静态块中。
Iterator itr = Service.providers(NameServiceDescriptor.class);
这里的NameServiceDescriptor是继承ServiceDescriptor接口的类,里面描述了服务的信息,这里是描述了NameService的信息,这里你不用去管。这里需要用到Service这个类,也许你找不到这个类,这个是正常的。因为这个类是sun的内部实现类,不公开源码的,你可以反编译jre下的rt.jar这个包,可以找到这个类。
这个类的实现比较简单就是Service用于加载的工具类,其实他是corba包内一个类,关于corba的知识就不多做介绍了,如果有兴趣的话,可以找找相关的资料学习,我也会在后期认真读这部分的代码的时候再来总结。
仔细阅读Service.providers方法,你会发现它是读META-INF/services/类文件夹下一个从ServiceDescriptor获得的serviceName的文件(当然这个名字是带包名的)。比如这里的例子文件名应该是sun.net.spi.nameservice.NameServiceDescriptor
,然后我就在jdk/lib和jre/lib下的jar中找了一下,最后在jre/lib/ext/dnsns.jar包里找到了。
然后我们看一下文件的内容:
# dns service provider descriptor
sun.net.spi.nameservice.dns.DNSNameServiceDescriptor
看到这里我明白,代码中有搜索#
的功能,原来这是注释呀!OK,这样我们也了解了,这个dnsns.jar包是用于DNS相关的功能,我很想打开这个类看看它的实现,但是肯定是没有任何效果,因为一定是顶层代码是native方法,都是c语言实现,关于这部分的知识,大家可以反编译这些dll(或者so)文件来查看C源码。
Service内部定义了一个LazyIterator的懒迭代类来读取文件的内容,这里的迭代并没有全部读取,而是一行一行地去读,所以这个名字也是恰如其分。在读取的过程中会验证名字是否正确符合规范,然后才会去加载这个类,最重要的一点是里面的类必须是文件名指向的类的子类,比如这里DNSNameServiceDescriptor必须是NameServiceDescriptor的子类。否则会报错的。
你看饶了这么一大圈,其实原理就是这么简单。
这里的DNSNameServiceDescriptor是dnsns.jar包里面的源码,看起来比较麻烦,反编译后发现这个类就是为了创建DNSNameService类,就是域名解析服务,这个类呢,也是dnsns.jar包内的类,所以如果要研究dns的实现,要深入研究这个包即可,但是这个包重要的代码还是C语言的实现的,这个规律是不变的。目前来说只要研究好NameService里面的接口就行了,再去研究dnsns.jar包就比较浪费时间了。
我们再来看看一些其他的例子吧!我可找不到这么的源码,我们来看看jre/lib/resource.jar和jdk/lib/tools.jar里面的内容吧,其他的包内也会有,这里就不多做介绍了。
注意这里面涉及到的类,Service、ServiceDesciptor、**Service
等。知道这些原理了,我们就可以在自己的jar中使用类似的机制来实现类的自动加载。我在想可能是一个模块依赖另一个模块的时候需要用到。
或者是服务太多了,先将所有服务都加载进来,然后进行相应的选择哪个服务提供服务。