深入了解Java的ServiceLoader类

深入了解Java的ServiceLoader类

以下内容纯属个人见解,如有不足之处,期待纠正。ServiceLoader源码来自jdk11

在Java的编程世界中,有时我们需要为某个接口提供多种实现,并且在运行时动态地加载这些实现。为了实现这种解耦与插件化的设计,Java提供了java.util.ServiceLoader类。本文将深入探讨ServiceLoader的用途、运行原理与实现原理,并通过一个实战Demo来加深对它的理解。

一、用途与使用场景

ServiceLoader的主要用途是加载和实例化服务提供者,这里的“服务”通常指的是实现了某个接口的类。使用场景包括但不限于插件式架构、框架扩展、第三方库集成等。在这些场景中,核心代码定义了服务接口,而服务提供者(第三方或用户自定义)则实现了这些接口。通过ServiceLoader,核心代码可以在运行时动态地加载和使用这些服务提供者,而无需在编译时知道它们的存在。

使用场景包括但不限于:

  • 插件式架构:当需要支持多种插件时,可以使用ServiceLoader动态加载这些插件。
  • 框架扩展:框架开发者可以定义一些接口,允许使用者通过实现这些接口来扩展框架的功能。
  • 第三方库集成:当需要集成多个第三方库时,可以使用ServiceLoader来加载这些库的实现类。

二、运行原理与实现原理

ServiceLoader的运行原理基于Java的SPI(Service Provider Interface)机制。SPI允许第三方为某个接口提供实现,并将这些实现类配置在特定的目录下。ServiceLoader通过扫描这些目录,找到并加载实现类,然后实例化它们。

具体实现原理如下:

  1. 查找配置文件ServiceLoader在类路径(classpath)下的META-INF/services目录中查找以接口全限定名命名的文件。例如,如果接口名为com.example.MyService,则ServiceLoader会查找名为com.example.MyService的文件。
  2. 解析配置文件:找到文件后,ServiceLoader会读取文件内容。文件内容是一系列实现类的全限定名,每行一个。这些实现类就是服务提供者。
  3. 加载与实例化:对于每个在配置文件中找到的实现类名,ServiceLoader使用Java的类加载机制加载这些类,并创建它们的实例。这些实例被缓存在ServiceLoader内部,供后续使用。
  4. 懒加载机制ServiceLoader采用懒加载的方式,即只有在首次调用iterator.hasNext()方法时才开始加载和实例化服务提供者。这种设计可以提高性能,尤其是在有大量服务提供者但只使用其中一部分的情况下。
  5. 缓存机制:加载过的服务提供者会被缓存起来,后续再次调用iterator()方法时,会返回与之前相同的实例。

三、源码解析

只截部分源码:

  1. 通过调用load进行参数设置,不会真正的加载类
public static <S> ServiceLoader<S> load(Class<S> service) {
	ClassLoader cl = Thread.currentThread().getContextClassLoader();
	return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}

private ServiceLoader(Class<?> caller, Class<S> svc, ClassLoader cl) {
    Objects.requireNonNull(svc);

    if (VM.isBooted()) {
        checkCaller(caller, svc);
        if (cl == null) {
            cl = ClassLoader.getSystemClassLoader();
        }
    } else {

        // if we get here then it means that ServiceLoader is being used
        // before the VM initialization has completed. At this point then
        // only code in the java.base should be executing.
        Module callerModule = caller.getModule();
        Module base = Object.class.getModule();
        Module svcModule = svc.getModule();
        if (callerModule != base || svcModule != base) {
            fail(svc, "not accessible to " + callerModule + " during VM init");
        }

        // restricted to boot loader during startup
        cl = null;
    }

    this.service = svc;
    this.serviceName = svc.getName();
    this.layer = null;
    this.loader = cl;
    this.acc = (System.getSecurityManager() != null)
            ? AccessController.getContext()
            : null;
}
  1. 调用iterator后进行准备工作,最终实例化LazyClassPathLookupIterator,再调用iterator.hasNext()时,进行真正的加载类
public Iterator<S> iterator() {
	// create lookup iterator if needed
	if (lookupIterator1 == null) {
		// lookme 这里开始加载类
	    lookupIterator1 = newLookupIterator();
	}
	return new Iterator<S>() {
	
	    // record reload count
	    final int expectedReloadCount = ServiceLoader.this.reloadCount;
	
	    // index into the cached providers list
	    int index;
	
	    /**
	     * Throws ConcurrentModificationException if the list of cached
	     * providers has been cleared by reload.
	     */
	    private void checkReloadCount() {
	        if (ServiceLoader.this.reloadCount != expectedReloadCount)
	            throw new ConcurrentModificationException();
	    }
	
	    @Override
	    public boolean hasNext() {
	        checkReloadCount();
	        if (index < instantiatedProviders.size())
	            return true;
	        return lookupIterator1.hasNext();
	    }
	
	    @Override
	    public S next() {
	        checkReloadCount();
	        S next;
	        if (index < instantiatedProviders.size()) {
	            next = instantiatedProviders.get(index);
	        } else {
	            next = lookupIterator1.next().get();
	            instantiatedProviders.add(next);
	        }
	        index++;
	        return next;
	    }
	
	};
}
// 主要看LazyClassPathLookupIterator,它实现了Iterator接口,当调用hasNext时,会触发加载
private Iterator<Provider<S>> newLookupIterator() {
    assert layer == null || loader == null;
    if (layer != null) {
        return new LayerLookupIterator<>();
    } else {
        Iterator<Provider<S>> first = new ModuleServicesLookupIterator<>();
        Iterator<Provider<S>> second = new LazyClassPathLookupIterator<>();
        return new Iterator<Provider<S>>() {
            @Override
            public boolean hasNext() {
                return (first.hasNext() || second.hasNext());
            }
            @Override
            public Provider<S> next() {
                if (first.hasNext()) {
                    return first.next();
                } else if (second.hasNext()) {
                    return second.next();
                } else {
                    throw new NoSuchElementException();
                }
            }
        };
    }
}

@Override
public boolean hasNext() {
    if (acc == null) {
    	// lookme
        return hasNextService();
    } else {
        PrivilegedAction<Boolean> action = new PrivilegedAction<>() {
            public Boolean run() { return hasNextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}
private boolean hasNextService() {
	while (nextProvider == null && nextError == null) {
	    try {
	    	// lookme
	        Class<?> clazz = nextProviderClass();
	        if (clazz == null)
	            return false;
	
	        if (clazz.getModule().isNamed()) {
	            // ignore class if in named module
	            continue;
	        }
	
	        if (service.isAssignableFrom(clazz)) {
	            Class<? extends S> type = (Class<? extends S>) clazz;
	            Constructor<? extends S> ctor
	                = (Constructor<? extends S>)getConstructor(clazz);
	            ProviderImpl<S> p = new ProviderImpl<S>(service, type, ctor, acc);
	            nextProvider = (ProviderImpl<T>) p;
	        } else {
	            fail(service, clazz.getName() + " not a subtype");
	        }
	    } catch (ServiceConfigurationError e) {
	        nextError = e;
	    }
	}
	return true;
}
private Class<?> nextProviderClass() {
    if (configs == null) {
        try {
            String fullName = PREFIX + service.getName();
            if (loader == null) {
                configs = ClassLoader.getSystemResources(fullName);
            } else if (loader == ClassLoaders.platformClassLoader()) {
                // The platform classloader doesn't have a class path,
                // but the boot loader might.
                if (BootLoader.hasClassPath()) {
                    configs = BootLoader.findResources(fullName);
                } else {
                    configs = Collections.emptyEnumeration();
                }
            } else {
                configs = loader.getResources(fullName);
            }
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return null;
        }
        pending = parse(configs.nextElement());
    }
    String cn = pending.next();
    try {
        return Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        fail(service, "Provider " + cn + " not found");
        return null;
    }
}

四、实战Demo

下面是一个简单的ServiceLoader使用示例,演示了如何定义一个服务接口、实现该接口、配置服务提供者,以及使用ServiceLoader加载和使用这些服务提供者。

项目结构:

|____src
| |____main
| | |____java
| | | |____com
| | | | |____xuance
| | | | | |____serviceloader
| | | | | | |____OneXcServiceImpl.java
| | | | | | |____ServiceLoaderDemo.java
| | | | | | |____TwoXcServiceImpl.java
| | | | | | |____XcService.java
| | |____resources
| | | |____META-INF
| | | | |____services
| | | | | |____com.xuance.serviceloader.XcService

首先,定义服务接口XcService

package com.xuance.serviceloader;

public interface XcService {
    void execute();
}

然后,创建两个服务提供者实现类OneXcServiceImpl、TwoXcServiceImpl

package com.xuance.serviceloader;

public class OneXcServiceImpl implements XcService {
    @Override
    public void execute() {
        System.out.println("This is OneXcServiceImpl...");
    }
}

package com.xuance.serviceloader;

public class TwoXcServiceImpl implements XcService{
    @Override
    public void execute() {
        System.out.println("This is TwoXcServiceImpl...");
    }
}

接下来,在META-INF/services目录下创建名为com.xuance.serviceloader.XcService的文件,并添加实现类的全限定名:

    com.xuance.serviceloader.OneXcServiceImpl
    com.xuance.serviceloader.TwoXcServiceImpl

最后,使用ServiceLoader加载和使用服务提供者:

    package com.xuance.serviceloader;

    import java.util.ServiceLoader;

    public class ServiceLoaderDemo {

        public static void main(String[] args) {
            // 加载MyService接口的实现类
            ServiceLoader<XcService> serviceLoader = ServiceLoader.load(XcService.class);
            // 遍历并调用每个服务提供者的execute方法
            Iterator<XcService> iterator = serviceLoader.iterator();
            while (iterator.hasNext()) {
                iterator.next().execute();
            }
        }

    }

运行上述代码,你会看到如下输出:

    This is OneXcServiceImpl...
    This is TwoXcServiceImpl...

这表明ServiceLoader成功加载了OneXcServiceImplTwoXcServiceImpl类,并调用了它的execute方法。

它提供了一种灵活且强大的机制,使得在Java中实现插件式架构和动态扩展变得轻而易举。在实际开发中,我们可以利用ServiceLoader来解耦核心代码与具体实现,从而实现更加灵活和可扩展的应用程序。

  • 42
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Java虚拟机(Java Virtual Machine,JVM)是Java平台的核心组件之一,它是一个用于执行Java字节码的虚拟机。Java源代码通过编译器编译成字节码,然后在JVM上执行。 JVM有三个主要的子系统:加载子系统、运行时数据区和执行引擎。 1. 加载子系统:负责将编译后的字节码加载到JVM中。它包括以下步骤: - 加载:查找并加载的二进制数据。 - 验证:确保被加载的符合Java语言规范。 - 准备:为的静态变量分配内存并设置默认初始值。 - 解析:将符号引用转换为直接引用。 - 初始化:执行的初始化代码,包括静态变量赋值和静态代码块。 2. 运行时数据区:JVM将内存分为不同的区域,用于存储不同型的数据。主要包括: - 方法区:存储的结构信息、常量、静态变量等。 - 堆:存储对象实例和数组。 - 栈:每个线程在运行时会创建一个栈帧,用于存储局部变量、操作数栈、方法调用等。 - 本地方法栈:用于支持本地方法调用。 3. 执行引擎:负责执行字节码指令。JVM有两种执行引擎: - 解释器:逐行解释执行字节码。 - 编译器:将热点代码(经常被执行的代码)编译成本地机器码,以提高执行效率。 除了上述三个主要的子系统外,JVM还提供了垃圾回收、即时编译器、安全管理等功能,以及一些诊断和监控工具。 通过深入了解Java虚拟机,开发者可以更好地理解Java程序的运行机制,并进行性能调优、内存管理、垃圾回收等方面的优化工作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值