报错如下图

明明是A接口的实现类,但是报错却说不是
在这里插入图片描述
整段代码如下:

package com.hotload;


import java.util.ServiceLoader;

class MyThread extends Thread{
    @Override
    public void run() {
        try{
            ServiceLoader<A> serviceLoader = ServiceLoader.load(A.class);
            A a = serviceLoader.iterator().next();
            a.say();
        } catch (Exception e){

        }
    }
}

public class Application {

    public static void main(String[] args) throws Exception {
        String rootPath = "/Users/mubi/git_workspace/java8/java8-api/src/main/java";
        MyComOtherClassLoader myClassLoader = new MyComOtherClassLoader();
        myClassLoader.path = rootPath;

        MyThread myThread = new MyThread();
        myThread.setContextClassLoader(myClassLoader);
        myThread.start();
    }


}

错误分析

要知道错误,首先要理解Java spi,ServiceLoader是用线程上线文加载器的,我们debug到报错代码:接口A的类加载器和接口A的实现类Aimp1的类加载器是不同的

  • 接口com.hotload.A的classLoader是AppClassLoader
  • 而实现类com.hotload.Aimp1的classLoader是线程上下文传递的自定义的MyComOtherClassLoader
    在这里插入图片描述
    在这里插入图片描述

在Class方法public native boolean isAssignableFrom(Class<?> cls);

不同的类加载器加载的父子类被判定为不是继承关系,所以导致了java.util.ServiceLoader第376行报错

在这里插入图片描述

设置正确的ClassLoader正确运行

当代码改成如下后正常运行

package com.hotload;


import java.lang.reflect.Method;
import java.util.ServiceLoader;

class MyThread extends Thread{
    @Override
    public void run() {
        try {
        	// 接口类也用线程上下文自定义的类加载器加载
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            Class clazzA = cl.loadClass("com.hotload.A");

            ServiceLoader<A> serviceLoader = ServiceLoader.load(clazzA);
            Object aimp1 = serviceLoader.iterator().next();

            Method method = aimp1.getClass().getMethod("say");
            method.invoke(aimp1);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class Application {

    public static void main(String[] args) throws Exception {
        String rootPath = "/Users/mubi/git_workspace/java8/java8-api/src/main/java";
        MyComOtherClassLoader myClassLoader = new MyComOtherClassLoader();
        myClassLoader.path = rootPath;

        MyThread myThread = new MyThread();
        myThread.setContextClassLoader(myClassLoader);
        myThread.start();
    }


}

在这里插入图片描述

debug 查看如下(java.util.ServiceLoade的 375行 可以走完,不再fail了)
在这里插入图片描述
在这里插入图片描述

结论

在使用Java标准spi时,需要注意ServiceLoader是使用线程上下文类加载器的,需要注意接口和实现类是否是同一个类加载器加载的,否则会报错类似 java.util.ServiceConfigurationError: com.hotload.A: Provider com.hotload.Aimp1 not a subtype 的错误

 public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

Logo

一站式 AI 云服务平台

更多推荐