Java ServiceLoader使用和解析

一、使用场景

一般使用接口的实现类都是静态new一个实现类赋值给接口引用,如下:

HelloService service = new HelloImpl();

如果需要动态的获取一个接口的实现类呢?全局扫描全部的Class,然后判断是否实现了某个接口?代价太大,一般不会这么做。一种合适的方式就是使用配置文件,把实现类名配置在某个地方,然后读取这个配置文件,获取实现类名。JDK给我们提供的TestServiceLoader 就是这种方式。

二、使用方式

在实现类的jar包的META-INF下新建一个文件夹services,并在services新建一个文件,以接口的全限定名为文件名,内容为实现类的全限定名。

通过以下的例子来分析实现原理.

1. 新建一个接口,2个实现类。

package com.test.loader;

public interface HelloService {
	public void sayHello();
}
package com.test.loader;

public class Dog implements HelloService {

	@Override
	public void sayHello() {
		System.out.println("bark bark bark...");
	}
}
package com.test.loader;

public class Sheep implements HelloService {

	@Override
	public void sayHello() {
		System.out.println("bleat bleat bleat...");
	}
}

2. 分别把接口、2个实现类打成3jar包,放在D盘下。

 

3. Dog.jarSheep.jar分别加上META-INF下新建一个文件夹services,并在services下新建一个文件,以接口的全限定名为文件名,内容为实现类的全限定名。如下:

 

4. 使用指定的ClassLoader不包含实现类

public static void notInTheClassLoader() throws MalformedURLException {
		ClassLoader serviceCL = new URLClassLoader(new URL[] { new URL("file:" + "D:/HelloService.jar") },
				TestServiceLoader.class.getClassLoader().getParent());

		/* 指定的ClassLoader没有实现类,所以扫描不到META-INF/services/com.test.loader.HelloService */
		ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class, serviceCL);

		Iterator<HelloService> it = helloServices.iterator();
		while (it.hasNext()) {
			HelloService service = it.next();
			service.sayHello();
		}
	}

结果不会打印任何信息。

5. 指定的ClassLoader包含实现类

public static void inTheClassLoader() throws MalformedURLException {
		ClassLoader serviceCL = new URLClassLoader(
				new URL[] { new URL("file:" + "D:/Dog.jar"), new URL("file:" + "D:/Sheep.jar") },
				TestServiceLoader.class.getClassLoader().getParent());

		/* 实现类在指定的ClassLoader,所以可以扫描META-INF/services/com.test.loader.HelloService */
		ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class, serviceCL);

		Iterator<HelloService> it = helloServices.iterator();
		while (it.hasNext()) {
			HelloService service = it.next();
			service.sayHello();
		}
	}

结果如下:

bark bark bark...

bleat bleat bleat...

6. 使用指定的ClassLoader加载接口类,不指定ClassLoader加载实现类。

public static void defaultClassLoader() throws MalformedURLException, ClassNotFoundException {
		ClassLoader serviceCL = new URLClassLoader(new URL[] { new URL("file:" + "D:/HelloService.jar"),
				new URL("file:" + "D:/Dog.jar"), new URL("file:" + "D:/Sheep.jar") },
				TestServiceLoader.class.getClassLoader().getParent());

		/* 默认会使用 ClassLoader.getSystemClassLoader() */
		ServiceLoader<HelloService> helloServices = ServiceLoader
				.load(((Class<HelloService>) (serviceCL.loadClass("com.test.loader.HelloService"))));

		Iterator<HelloService> it = helloServices.iterator();
		while (it.hasNext()) {
			HelloService service = it.next();
			service.sayHello();
		}
	}

结果不打印任何信息。

7. 2个实现jar加到工程的Build Path里面,不指定ClassLoader


public static void notSpecifyClassLoader() {
		ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class);

		Iterator<HelloService> it = helloServices.iterator();
		while (it.hasNext()) {
			HelloService service = it.next();
			service.sayHello();
		}
	}

结果如下:

bark bark bark...

bleat bleat bleat...

三、简单解析ServiceLoader

1. 构造函数,如果不指定ClassLoader或者指定的为null,则使用ClassLoader.getSystemClassLoader() AppClassLoader

    private ServiceLoader(Class<S> svc, ClassLoader cl) {

        service = Objects.requireNonNull(svc, "Service interface cannot be null");

        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;

        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;

        reload();

    }

2. 遍历有两个方法

hasNext会调用hasNextService,如果指定的ClassLoader为空(一般不会为空,构造函数会初始化),则调用ClassLoader.getSystemClassLoader().getResources,否则调用指定的ClassLoadergetResources方法,获取META-INF/services/接口的全限定名称,如META-INF/services/com.test.loader.HelloService,从这个文件中找出实现类全限定名称。

next会调用nextService,根据hasNextService获取的实现类信息,使用指定的ClassLoader进行加载和实例化。 

     private boolean hasNextService() {

            if (nextName != null) {

                return true;

            }

            if (configs == null) {

                try {

                    String fullName = PREFIX + service.getName();

                    if (loader == null)

                        configs = ClassLoader.getSystemResources(fullName);

                    else

                        configs = loader.getResources(fullName);

                } catch (IOException x) {

                    fail(service, "Error locating configuration files", x);

                }

            }

            while ((pending == null) || !pending.hasNext()) {

                if (!configs.hasMoreElements()) {

                    return false;

                }

                pending = parse(service, configs.nextElement());

            }

            nextName = pending.next();

            return true;

        }

 

        private S nextService() {

            if (!hasNextService())

                throw new NoSuchElementException();

            String cn = nextName;

            nextName = null;

            Class<?> c = null;

            try {

                c = Class.forName(cn, false, loader);

            } catch (ClassNotFoundException x) {

                fail(service,

                     "Provider " + cn + " not found");

            }

            if (!service.isAssignableFrom(c)) {

                fail(service,

                     "Provider " + cn  + " not a subtype");

            }

            try {

                S p = service.cast(c.newInstance());

                providers.put(cn, p);

                return p;

            } catch (Throwable x) {

                fail(service,

                     "Provider " + cn + " could not be instantiated",

                     x);

            }

            throw new Error();          // This cannot happen

        }

注:以上代码基于JDK1.8.0_144

 

  • 10
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值