29+30-ServiceLoader在SPI中的重要作用分析+线程上下文类加载器实战分析与难点剖析

ServiceLoader在SPI中的重要作用分析+线程上下文类加载器实战分析与难点剖析

实例一:
public class MyTest26 {
    public static void main(String[] args) {
        ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
        Iterator<Driver> iterator = loader.iterator();
        while (iterator.hasNext()){
            Driver driver = iterator.next();
            System.out.println("driver: " +driver.getClass()+", loader: "+driver.getClass().getClassLoader());
        }
        System.out.println("线程上下文类加载器:" +Thread.currentThread().getContextClassLoader());
        System.out.println("ServiceLoader类加载器:"+ServiceLoader.class.getClassLoader());
    }
}
运行结果:
      driver: class com.mysql.jdbc.Driver, loader: sun.misc.Launcher$AppClassLoader@18b4aac2
      线程上下文类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
      ServiceLoader类加载器:null
  • 结果分析:
    • 1.结果第一行说明,加载mysql驱动的类加载器是系统类加载器。
    • 2.第三行说明,首先ServiceLoader这个类位于核心类库,所以由启动类加载器。
    • 这里,并没有设置一些属性告诉ServiceLoader,你去哪里加载mysql的相关驱动,Driver仅仅是一个接口,那么他是如何加载的呢,看下面!
ServiceLoader的doc阅读:
public final class ServiceLoader<S>
   extends Object
   implements Iterable<S>

​ A simple service-provider loading facility.

​ 一个简单的服务提供者加载工具。

​ A service is a well-known set of interfaces and (usually abstract) classes. A service provider is a specific implementation of a service. The classes in a provider typically implement the interfaces and subclass the classes defined in the service itself. Service providers can be installed in an implementation of the Java platform in the form of extensions, that is, jar files placed into any of the usual extension directories. Providers can also be made available by adding them to the application’s class path or by some other platform-specific means.

​ 这个服务是一组众所周知的接口和(通常是抽象的)类。服务提供者是服务的特定实现。提供者中的类通常实现接口,并为服务本身中定义的类子类。服务提供者[可以理解为:MySQL的jdbc驱动]可以以扩展的形式安装在Java平台的实现中,也就是说,jar文件可以放在任何常用的扩展目录中。提供者也可以通过将它们添加到应用程序的类路径或其他特定于平台的方法来提供。

​ For the purpose of loading, a service is represented by a single type, that is, a single interface or abstract class. (A concrete class can be used, but this is not recommended.) A provider of a given service contains one or more concrete classes that extend this service type with data and code specific to the provider. The provider class is typically not the entire provider itself but rather a proxy which contains enough information to decide whether the provider is able to satisfy a particular request together with code that can create the actual provider on demand. The details of provider classes tend to be highly service-specific; no single class or interface could possibly unify them, so no such type is defined here. The only requirement enforced by this facility is that provider classes must have a zero-argument constructor so that they can be instantiated during loading.

​ 为了加载,服务由单个类型表示,即单个接口或抽象类。(可以使用具体类,但不建议这样做。)给定服务的提供程序包含一个或多个具体类,这些类使用特定于提供程序的数据和代码扩展此服务类型。provider类通常不是整个提供者本身,而是一个代理,它包含足够的信息来决定提供者是否能够满足特定的请求,以及可以根据需要创建实际提供者的代码。提供者类的细节往往是高度特定于服务的;没有单个类或接口可以统一它们,因此这里没有定义此类类型。此工具实施的唯一要求是提供程序类必须具有[无参数构造函数],以便在加载期间实例化它们。

​ A service provider is identified by placing a provider-configuration file in the resource directory META-INF/services. The file’s name is the fully-qualified binary name of the service’s type. The file contains a list of fully-qualified binary names of concrete provider classes, one per line. Space and tab characters surrounding each name, as well as blank lines, are ignored. The comment character is ‘#’ (’\u0023’, NUMBER SIGN); on each line all characters following the first comment character are ignored. The file must be encoded in UTF-8.

​ 通过在资源目录META-INF/services中放置提供者配置文件来标识服务提供者。文件名是服务类型的完全限定的二进制名称。该文件包含具体提供程序类的完全限定的二进制名称列表,每行一个。将忽略每个名称周围的空格和制表符以及空行。注释字符为“35;”(’\u0023’,数字符号);在每行中,第一个注释字符后面的所有字符都将被忽略。文件必须用UTF-8编码。

在这里插入图片描述

​ If a particular concrete provider class is named in more than one configuration file, or is named in the same configuration file more than once, then the duplicates are ignored. The configuration file naming a particular provider need not be in the same jar file or other distribution unit as the provider itself. The provider must be accessible from the same class loader that was initially queried to locate the configuration file; note that this is not necessarily the class loader from which the file was actually loaded.

​ 如果某个特定的具体提供程序类在多个配置文件中命名,或在同一配置文件中多次命名,则会忽略重复项。命名特定提供程序的配置文件不必与提供程序本身位于同一jar文件或其他分发单元中。提供程序必须可从最初查询以定位配置文件的同一类加载程序访问;请注意,这不一定是从中实际加载文件的类加载程序。

​ Providers are located and instantiated lazily, that is, on demand. A service loader maintains a cache of the providers that have been loaded so far. Each invocation of the iterator method returns an iterator that first yields all of the elements of the cache, in instantiation order, and then lazily locates and instantiates any remaining providers, adding each one to the cache in turn. The cache can be cleared via the reload method.

​ 提供者被延迟地定位和实例化,即按需。服务加载程序维护到目前为止已加载的提供程序的缓存。每次调用迭代器方法都返回一个迭代器,该迭代器首先按实例化顺序生成缓存的所有元素,然后惰性地定位和实例化任何剩余的提供程序,依次将每个提供程序添加到缓存中。可以通过重新加载方法清除缓存。

​ Service loaders always execute in the security context of the caller. Trusted system code should typically invoke the methods in this class, and the methods of the iterators which they return, from within a privileged security context.

​ 服务加载程序总是在调用方的安全上下文中执行。受信任的系统代码通常应该在特权安全上下文中调用此类中的方法以及它们返回的迭代器的方法。

​ Instances of this class are not safe for use by multiple concurrent threads.
​ Unless otherwise specified, passing a null argument to any method in this class will cause a NullPointerException to be thrown.

​ 该类的实例对于多个并发线程使用是不安全的。

​ 除非另有说明,否则将空参数传递给该类中的任何方法都将导致引发NullPointerException。

Example Suppose we have a service type com.example.CodecSet which is intended to represent sets of encoder/decoder pairs for some protocol. In this case it is an abstract class with two abstract methods:

​ 示例:假设我们有一个服务类型com.Example.CodecSet,它用于表示某些协议的编码器/解码器对集。在本例中,它是一个带有两个抽象方法的抽象类:

public abstract Encoder getEncoder(String encodingName); //编码器方法
public abstract Decoder getDecoder(String encodingName);	//解码器方法
Each method returns an appropriate object or null if the provider does not support the given encoding. Typical providers support more than one encoding.

​ If com.example.impl.StandardCodecs is an implementation of the CodecSet service then its jar file also contains a file named
​ META-INF/services/com.example.CodecSet //这里相当于mysql驱动中的META-INF/services/java.sql.Driver
​ This file contains the single line:
​ com.example.impl.StandardCodecs # Standard codecs //相当于java.sql.Driver文件中的内:com.mysql.jdbc.Driver
​ The CodecSet class creates and saves a single service instance at initialization:

private static ServiceLoader<CodecSet> codecSetLoader = ServiceLoader.load(CodecSet.class);

​ To locate an encoder for a given encoding name it defines a static factory method which iterates through the known and available providers, returning only when it has located a suitable encoder or has run out of providers.

​ 为查找给定编码名称的编码器,它定义了一个静态工厂方法,该方法在已知和可用的提供程序之间迭代,仅在找到合适的编码器或提供程序用完时返回。

public static Encoder getEncoder(String encodingName) {
for (CodecSet cp : codecSetLoader) {
   Encoder enc = cp.getEncoder(encodingName);
   if (enc != null)
      return enc;
}
return null;
}

​ A getDecoder method is defined similarly.

​ getDecoder方法的定义类似:

Usage Note If the class path of a class loader that is used for provider loading includes remote network URLs then those URLs will be dereferenced in the process of searching for provider-configuration files.

​ 用法说明:如果用于提供程序加载的类加载器的类路径包含远程网络URL,则在搜索提供程序配置文件的过程中将取消对这些URL的引用。

​ This activity is normal, although it may cause puzzling entries to be created in web-server logs. If a web server is not configured correctly, however, then this activity may cause the provider-loading algorithm to fail spuriously.

​ 此活动是正常的,尽管它可能会导致在web服务器日志中创建令人费解的条目。但是,如果web服务器配置不正确,则此活动可能会导致提供程序加载算法错误地失败。

​ A web server should return an HTTP 404 (Not Found) response when a requested resource does not exist. Sometimes, however, web servers are erroneously configured to return an HTTP 200 (OK) response along with a helpful HTML error page in such cases. This will cause a ServiceConfigurationError to be thrown when this class attempts to parse the HTML page as a provider-configuration file. The best solution to this problem is to fix the misconfigured web server to return the correct response code (HTTP 404) along with the HTML error page.

​ Web服务器应该在不存在请求的资源时返回HTTP 404(未找到)响应。然而,有时web服务器被错误地配置为在这种情况下返回htp200(OK)响应和有用的HTML错误页。当此类尝试将HTML页解析为提供程序配置文件时,这将导致抛出ServiceConfigurationError。解决此问题的最佳方法是修复配置错误的web服务器,以返回正确的响应代码(HTTP 404)和HTML错误页。

Since:
1.6
Type parameters:
- The type of the service to be loaded by this loader

ServiceLoader中的load方法:
/**
		Creates a new service loader for the given service type, using the current thread's 
	context class loader.
		An invocation of this convenience method of the form ServiceLoader.load(service) is equivalent to
   ServiceLoader.load(service,Thread.currentThread().getContextClassLoader())
*/
public static <S> ServiceLoader<S> load(Class<S> service) {
   ClassLoader cl = Thread.currentThread().getContextClassLoader(); //★★★核心代码★★★
   /**
   	因为ServiceLoader是在核心类库中,要由启动类加载器去加载,但是mysql的驱动是在当前目录下的classpath中,
   	是要有系统类加载器加载,所以,通过上下文类加载器得到系统类加载器去操作ServiceLoader
   */
   return ServiceLoader.load(service, cl); //将上下文类加载器放到load方法中,上下文类加载器获取的是系统类加载器!
}

//Creates a new service loader for the given service type and class loader.
//为给定的服务类型和类加载器创建新的服务加载器。
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
   //将待加载的service这个class对象和类加载器作为参数,来new这样一个实例
   return new ServiceLoader<>(service, loader);
}

public void reload() {
   providers.clear();  //先清空提供者的缓存
   lookupIterator = new LazyIterator(service, loader);//通过延时加载的方式,new这样一个实例
}

private ServiceLoader(Class<S> svc, ClassLoader cl) {
   service = Objects.requireNonNull(svc, "Service interface cannot be null");
   //如果这个这个上下文类加载器为空,那么就将系统类加载器赋给loader,不为空,则返回c1
   loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
   acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;//安全性检查
   reload();  //在调用reloader方法
}

//Private inner class implementing fully-lazy provider lookup
//实现完全延迟提供程序查找的私有内部类
private class LazyIterator implements Iterator<S>
{ ......}
实例二:
public class MyTest26 {
    public static void main(String[] args) {
        //在原来的基础上,这里设置当前线程的上下文类加载器是:扩展类加载器
        Thread.currentThread().setContextClassLoader(MyTest26.class.getClassLoader().getParent());
        ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
        Iterator<Driver> iterator = loader.iterator();
        while (iterator.hasNext()){
            Driver driver = iterator.next();
            System.out.println("driver: " +driver.getClass()+", loader: "+driver.getClass().getClassLoader());
        }
        System.out.println("线程上下文类加载器:" +Thread.currentThread().getContextClassLoader());
        System.out.println("ServiceLoader类加载器:"+ServiceLoader.class.getClassLoader());
    }
}
运行结果:
   线程上下文类加载器:sun.misc.Launcher$ExtClassLoader@45ee12a7
   ServiceLoader类加载器:null
  • 结果分析:
    • 由于是:扩展类加载器,所以扩展类加载器会到自己所能加载器的目录下去加载内容,这时候就找不到classpath下的mysql驱动文件,所以就没有 迭代输出!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值