내 연락처 정보
우편메소피아@프로톤메일.com
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
SPI는 Service Provider Interface의 약자로, 프레임워크 기능을 확장하는 서비스 공급자나 개발자에게 특별히 제공되는 인터페이스로 이해될 수 있습니다. SPI는 서비스 인터페이스를 특정 서비스 구현에서 분리하고 서비스 호출자와 서비스 구현자를 분리하며 프로그램의 확장성과 유지 관리성을 향상시킬 수 있습니다. 서비스 구현을 수정하거나 교체하는 경우 호출자를 수정할 필요가 없습니다. 많은 프레임워크는 Spring 프레임워크, 데이터베이스 로딩 드라이버, 로그 인터페이스, Dubbo 확장 구현 등과 같은 Java의 SPI 메커니즘을 사용합니다. 이 RPC 프레임워크의 SPI 부분을 구현하려면 Dubbo의 SPI 메커니즘을 참조하세요.
예를 들어 클라이언트가 서버와 통신할 때 메시지를 직렬화해야 합니다. Hessian, Kryo 및 ProtoStuff를 포함하여 직렬화 중에 사용할 수 있는 많은 직렬화 알고리즘이 있습니다. 시스템 요구 사항은 메시지의 직렬화 알고리즘 이름을 기반으로 해당 직렬화 알고리즘에 해당하는 클래스의 메서드를 호출하여 직렬화 및 역직렬화를 수행하는 것입니다. 또한 확장을 용이하게 하려면 디커플링에 SPI를 사용해야 합니다. .
SPI는 다음과 같이 사용됩니다.
- Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class)
- .getExtension(codecName);
codecName은 직렬화 알고리즘의 이름이며, 이 이름에 따라 해당 클래스를 로드해야 합니다.
- private final Class<?> type;
-
- private ExtensionLoader(Class<?> type) {
- this.type = type;
- }
-
- // 每个SPI接口都有自身的ExtensionLoader
- public static <S> ExtensionLoader<S> getExtensionLoader(Class<S> type) {
- if (type == null) {
- throw new IllegalArgumentException("Extension type should not be null.");
- }
- if (!type.isInterface()) {
- throw new IllegalArgumentException("Extension type must be an interface.");
- }
- if (type.getAnnotation(SPI.class) == null) {
- throw new IllegalArgumentException("Extension type must be annotated by @SPI");
- }
- // firstly get from cache, if not hit, create one
- ExtensionLoader<S> extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
- if (extensionLoader == null) {
- EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<S>(type));
- extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
- }
- return extensionLoader;
- }
각 SPI 인터페이스에는 자체 ExtensionLoader가 있습니다. getExtensionLoader를 호출하면 먼저 일련의 적법성 검사 작업을 수행한 다음 인터페이스의 ExtensionLoader를 얻으려고 시도합니다. 그렇지 않으면 로컬 캐시 CHM에서 얻으려고 합니다. 얻은 후 Loader 개체를 만듭니다.
이후 getExtension을 통해 인스턴스를 획득하고, 로컬에서도 인스턴스를 캐시합니다. 캐시에 없으면 인스턴스를 다시 생성합니다.
- private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
-
- public T getExtension(String name) {
- if (StringUtil.isBlank(name)) {
- throw new IllegalArgumentException("Extension name should not be null or empty.");
- }
- // firstly get from cache, if not hit, create one
- // 缓存holder
- Holder<Object> holder = cachedInstances.get(name);
- if (holder == null) {
- cachedInstances.putIfAbsent(name, new Holder<>());
- holder = cachedInstances.get(name);
- }
- // create a singleton if no instance exists
- // holder为空,双重检查锁创建示例
- Object instance = holder.get();
- if (instance == null) {
- synchronized (holder) {
- instance = holder.get();
- if (instance == null) {
- instance = createExtension(name);
- holder.set(instance);
- }
- }
- }
- return (T) instance;
- }
클래스의 Class 객체를 얻은 후 리플렉션을 통해 이 객체를 생성할 수 있습니다.
- // 缓存
- private static final Map<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
-
- private T createExtension(String name) {
- // load all extension classes of type T from file and get specific one by name
- // SPI接口对应的实现类,其标识名与class文件的映射,根据标识名获取class
- Class<?> clazz = getExtensionClasses().get(name);
- if (clazz == null) {
- throw new RuntimeException("No such extension of name " + name);
- }
- T instance = (T) EXTENSION_INSTANCES.get(clazz);
- if (instance == null) {
- try {
- // 缓存中不存在,则创建实例
- EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
- instance = (T) EXTENSION_INSTANCES.get(clazz);
- } catch (Exception e) {
- log.error(e.getMessage());
- }
- }
- return instance;
- }
핵심은 Class 객체, 즉 getExtensionCalsses 메소드를 획득하는 프로세스입니다.
- // 该SPI接口所有实现类的标识与其Class对象的缓存
- private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
-
- private static final String SERVICE_DIRECTORY = "META-INF/extensions/";
-
- private Map<String, Class<?>> getExtensionClasses() {
- // get the loaded extension class from the cache
- // 根据Interface实现类的类名获取对应类的缓存
- Map<String, Class<?>> classes = cachedClasses.get();
- // double check
- if (classes == null) {
- synchronized (cachedClasses) {
- classes = cachedClasses.get();
- if (classes == null) {
- classes = new HashMap<>();
- // load all extensions from our extensions directory
- loadDirectory(classes);
- // 将Map集合存储在Holder中进行缓存
- cachedClasses.set(classes);
- }
- }
- }
- return classes;
- }
-
- private void loadDirectory(Map<String, Class<?>> extensionClasses) {
- // 固定路径下的文件,SPI接口的类名作为文件名,在此文件中规定需要加载的实现类
- String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();
- try {
- Enumeration<URL> urls;
- // 系统类加载器,它能够加载用户类路径(ClassPath)上的类和资源。对于SPI机制尤为重要,因为SPI的实现类通常是由应用程序提供并放置在应用程序的类路径下的
- ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
- // 获取当前类加载器加载的URL资源,文件名确定一般urls是唯一的
- urls = classLoader.getResources(fileName);
- if (urls != null) {
- while (urls.hasMoreElements()) {
- URL resourceUrl = urls.nextElement();
- // 使用classLoader加载资源,资源目标在resourceUrl下,加载后的class存储在extensionClasses Map集合当中
- loadResource(extensionClasses, classLoader, resourceUrl);
- }
- }
- } catch (IOException e) {
- log.error(e.getMessage());
- }
- }
-
- private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, URL resourceUrl) {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {
- String line;
- // read every line
- // #是注释,截取注释之前的部分
- while ((line = reader.readLine()) != null) {
- // get index of comment
- final int ci = line.indexOf('#');
- if (ci >= 0) {
- // string after # is comment so we ignore it
- line = line.substring(0, ci);
- }
- line = line.trim();
- if (line.length() > 0) {
- try {
- final int ei = line.indexOf('=');
- // 标识与类名
- String name = line.substring(0, ei).trim();
- String clazzName = line.substring(ei + 1).trim();
- // our SPI use key-value pair so both of them must not be empty
- if (name.length() > 0 && clazzName.length() > 0) {
- // 加载类
- Class<?> clazz = classLoader.loadClass(clazzName);
- // 在map中保存
- extensionClasses.put(name, clazz);
- }
- } catch (ClassNotFoundException e) {
- log.error(e.getMessage());
- }
- }
-
- }
- } catch (IOException e) {
- log.error(e.getMessage());
- }
- }
- kyro=github.javaguide.serialize.kyro.KryoSerializer
- protostuff=github.javaguide.serialize.protostuff.ProtostuffSerializer
- hessian=github.javaguide.serialize.hessian.HessianSerializer
레이어별 메소드 호출은 META-INF/extensions/ 경로 아래에 해당 SPI 구성 파일을 로드하여 클래스 객체를 로드하고 인스턴스를 얻는 프로세스를 구현합니다. 중요한 부분은 주석을 참조하세요.
META-INF/extensions/ 아래의 파일 이름은 코드와 일치해야 합니다. 코드에 지정된 파일 이름은 SPI 인터페이스 클래스의 전체 클래스 이름입니다. 파일의 내용도 (구현 클래스 식별자 = 구현 클래스의 전체 클래스 이름)에 따라 작성해야 코드와 일치할 수 있으며 프로그램은 파일을 올바르게 구문 분석하고 클래스 로더를 사용하여 다음을 수행할 수 있습니다. 해당 클래스를 로드합니다. 마지막으로 <구현 클래스 식별자, 구현 클래스 Class 객체>에 따라 캐시합니다.
세 가지 캐시(클래스 식별자, 클래스 객체), (클래스 객체, 객체 인스턴스), (클래스 식별자, 객체 인스턴스)가 존재하므로 식별자를 직접 전달하여 해당 클래스의 인스턴스를 얻을 수 있습니다. 또한 RPC 프레임워크 성능을 최적화합니다.