服务加载器

160

利用 ServiceLoader 可以实现接口与实现的分离, 并且可以让一个 接口有多种实现以供不同情况下的选择, 也可以用于 API 模块 和 实现 模块的分离

使用:

创建公共接口:

  • 新建一个项目, 构建系统为 maven
  • 删除新建项目的 src 目录
  • 新建一个子模块名字叫 api
  • 在新建模块下新建一个包名字叫: serviceLoader
  • 在新建的包下创建一个接口 Cipher, 这是加密和解密的接口
  • 定义加密解密方法等

Cipher 接口:

package serviceLoader
public interface Cipher {
	byte[] encrypt(byte[] source, byte[] key);
	byte[] decrypt(byte[] source, byte[] key);
	int strength();
}

创建实现类:

  • 新建一个子模块名字叫 core
  • 在 core 模块中依赖 api 模块 (scope 为 provide)
  • 新建一个包名字叫: serviceloader.impl
  • 创建实现类继承 Cipher 接口
package serviceLoader.impl

import serviceLoader.Cipher;

public class CaesarCipher implements Cipher {
	// 实现加密方法
	public byte[] encrypt(byte[] source, byte[] key) {
		byte[] result = new byte[source.length];
		for (int i = 0; i < source.length; i++) {
			result[i] = (byte)(source[i] + key[0]);
		}
		return result;
	}

	// 实现解密方法
	public byte[] decrypt(byte[] source, byte[] key) {
		return encrypt(source, new byte[] { (byte)-key[0] });
	}

	public int strength() {
		return 1;
	}
}

配置:

在实现类的模块中创建 resources/META-INF/services 目录

在创建的目录下创建文件 serviceLoader.Cipher
文件内容如下

serviceLoader.impl.CaesarCipher

文件名的格式是: 接口的全限定名(包名.类名), 内容的格式是每个实现类的全限定名

如果有多个实现类, 只需要一行写一个实现类的全限定名即可

加载实现类:

  • 在 api 模块中新建包 serviceLoader.impl
  • 在新建的包中新建类 CipherImpl
package serviceLoader.impl;  
  
import serviceLoader.Cipher;  
  
import java.util.ServiceLoader;

public class CipherImpl {  
	public static ServiceLoader<Cipher> cipherLoader = ServiceLoader.load(Cipher.class);  
  
	public Cipher getCipher(int minStrength) {  
		for (Cipher cipher : cipherLoader) {  
			if (cipher.strength() >= minStrength) {  
				return cipher;  
			}  
		}  
		throw new RuntimeException("not found any impls");  
	}  
}
  • 新建一个子模块 main (名字随意)
  • 依赖模块 api 和 core (scope 为 runtime)
  • 创建一个类 Main.class
  • 在 Main 的 main 方法中测试
import serviceLoader.impl.CipherImpl;
public class Main {  
	public static void main(String[] args) {  
		CipherImpl cipher = new CipherImpl();  
		System.out.println(cipher.getCipher(1));  
	}  
}

运行后打印出东西说明成功

详解:

  1. 配置文件加载:ServiceLoader 在初始化时会根据指定的服务提供者接口类型查找对应的配置文件。它通过 ClassLoader 的资源加载机制来加载位于 META-INF/services/ 目录下的配置文件。配置文件的名称是服务提供者接口的全限定名。

  2. 实例化服务提供者:一旦找到了配置文件,ServiceLoader 会读取文件中的每一行,每行都包含一个服务提供者实现类的全限定名。ServiceLoader 使用反射机制实例化这些服务提供者类,并返回对应的实例。

  3. 延迟加载:ServiceLoader 使用了延迟加载的机制,即在初始化时并不会立即实例化所有的服务提供者类。而是在需要获取服务实例时才会进行实例化。这样可以提高性能,避免不必要的实例化操作。

  4. 缓存机制:ServiceLoader 在第一次加载服务提供者时会将其缓存起来,下次再次需要获取实例时可以直接使用缓存中的数据,避免重复加载和实例化。

  5. 遍历服务提供者:ServiceLoader 提供了一个迭代器,用于遍历所有已加载的服务提供者实例。通过迭代器,可以依次获取每个服务提供者的实例,从而使用其提供的功能。

  6. 动态更新:ServiceLoader 支持动态更新服务提供者。当配置文件或服务提供者模块发生变化时,可以通过重新加载来获取最新的服务提供者实例。通过调用 reload() 方法可以触发重新加载操作。

java.util.ServiceLoader

  • static <S> ServiceLoader<S> load(Class<S> service)

    创建一个服务加载器来加载实现给定服务接口的类

  • Iterator<S> iterator()

    生成一个懒加载方式加载服务类的迭代器. 随着迭代器推进才会加载类

  • Stream<ServiceLoader.Provider<S>> stream()

    返回一个流, 也是懒加载

  • Optional<S> findFirst()

    查找第一个可用的服务提供类 (如果有)

java.util.ServiceLoader.Provider

  • Class<? extends S> type()

    获得这个提供者的类型

  • S get()

    或者这个提供者的实例

示例代码:

以下是本篇博客所使用的代码, 使用的工具为 IntelliJ IDEA

https://bloghexofluid.oss-accelerate.aliyuncs.com/halo/example.zip