ClassLoader

类加载机制

  在编译成的Class文件最终都要加载到虚拟机中才能被运行和使用Java虚拟机把描述类的数据从Class文件加载到内存并对数据进行校验转换解析和初始化最终形成可以被虚拟机直接使用的Java类型这个过程被称作虚拟机的类加载机制

  一个类型从被加载到虚拟机内存中开始到卸载出内存为止它的整个生命周期将会经历加载Loading验证Verification准备Preparation解析Resolution初始化Initialization使用Using和卸载Unloading七个阶段其中验证准备解析三个部分统称为连接Linking

  
对于初始化阶段Java虚拟机规范严格规定了只有六种情况必须立即对类进行初始化:

  1. 遇到newgetstaticputstatic或invokestatic这四条字节码指令时如果类型没有进行过初始化则需要先触发其初始化阶生成这四条指令的典型Java代码场景有
    1. 使用new关键字实例化对象的时候
    2. 读取或设置一个类型的静态字段(被final修饰已在编译期把结果放入常量池的静态字段除外)的时候
    3. 调用一个类型的静态方法
  2. 使用java.lang.reflect包的方法对类型进行反射调用的时候如果类型没有进行过初始化则需要先触发其初始化
  3. 当初始化类的时候如果发现其父类还没有进行过初始化则需要先触发其父类的初始化
  4. 当虚拟机启动时用户需要指定一个要执行的主类包含main方法的那个类虚拟机会先初始化这个主类
  5. 当使用JDK7新加入的动态语言支持时如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStaticREF_putStaticREF_invokeStaticREF_newInvokeSpecial四种类型的方法句柄并且这个方法句柄对应的类没有进行过初始化则需要先触发其初始化
  6. 当一个接口中定义了JDK8新加入的默认方法(被default关键字修饰的接口方法)时如果有这个接口的实现类发生了初始化那该接要在其之前被初始化

下面给个例子说明初始化

public static void main(String[]args){
        System.out.println(SuperClass.value);
        }


public class SuperClass {
    static {
        System.out.println("SuperClass init!");
    }

    public static int value = 123;
}

public class SubClass extends SuperClass {
    static {
        System.out.println("SubClass init!");
    }
}

output:

        SuperClass init!
        123

可以看到SubClass init 并没有输出对于静态字段只有直接定义这个字段才会被初始化因此通过其子类来引用父类中定义的静态字段只会触发父类的初始化而不会触发子类的初始化

这里列出所有初始化的情况是因为发现实际项目中有一段dubbo的静态代码块没有执行而引发的疑问dubbo的AbstractConfig里有关于shutdown的静态代码块因为我发现我们服务在terminating的时候暴露的服务并没有在注册中心下线于是就看我们用的dubbo2.6.2的版本下是如何优雅下线的

      static {
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                if (logger.isInfoEnabled()) {
                    logger.info("Run shutdown hook now.");
                }
                ProtocolConfig.destroyAll();
            }
        }, "DubboShutdownHook"));
    }

  于是在本地测试发现是会执行最后发现外面还有一层pandora刚开始对pandora并不是很了解最后认真了解了一下pandora解释,它其实是做到类隔离容器当我加上-XX:+TraceClassLoading,发现它并不是直接用dubbo2.6.2来加载类数据从而不会执行这段静态代码块从这里开始又把我的视线拉到pandora上为什么要使用pandora呢于是就引出了本篇主题----类加载

类加载器

  Java虚拟机设计团队有意把类加载阶段中的通过一个类的全限定名来获取描述该类的二进制字节流这个动作放到Java虚拟机外部去实现以便让应用程序自己决定如何去获取所需的类实现这个动作的代码被称为类加载器

  类加载器虽然只用于实现类的加载动作但它在Java程序中起到的作用却远超类加载阶段对于任意一个类都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性每一个类加载器都拥有一个独立的类名称空间也就是说两个类是否相等就是来自于同一个class文件同时是被同一类加载器加载的

public class ClassLoaderTest {
        public static void main(String[] args) throws Exception {

            ClassLoader myLoader = new ClassLoader() {
                @Override
                public Class<?> loadClass(String name) throws ClassNotFoundException {
                    try {
                        String fileName = name.substring(name.lastIndexOf(".") + 1)
                                + ".class";
                        InputStream is = getClass().getResourceAsStream(fileName);
                        if (is == null) {
                            return super.loadClass(name);
                        }

                        byte[] b = new byte[is.available()];
                        is.read(b);
                        return defineClass(name, b, 0, b.length);
                    } catch (Exception e) {
                        throw new ClassNotFoundException(name);
                    }
                }
            };

            Object obj = myLoader.loadClass("org.demo.test.ClassLoaderTest")
                    .newInstance();

            System.out.println(obj.getClass());
            System.out.println(obj instanceof org.demo.test.ClassLoaderTest);

        }

    }

运行结果
org.demo.test.ClassLoaderTest
false

可以看出虽然来自同一个文件但在Java虚拟机中仍然是两个互相独立的类

双亲委派模型

  站在Java虚拟机的角度来看只存在两种不同的类加载器一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++实现是虚拟机自身的一部分另外一种就是其他所有的类加载器这些类加载器都由Java语言实现独立存在于虚拟机外部并且全都继承自抽象类java.lang.ClassLoader

  本文讨论的是JDK8及之前版本的Java其一直保持着三层类加载器双亲委派的类加载架构不过在JDK9模块化后有一些调整不过本文会关注在三层类加载器

  1. 启动类加载器

  这个类加载器负责加载存放在<JAVA_HOME>\lib 目录或者被-Xbootclasspath参数指定的路径中存放的而且是被Java虚拟机能够识别的(rt.jartools.jar)类库加载

  1. 扩展类加载器

  这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的它负责加载<JAVA_HOME>\lib\ext目录中或者被java.ext.dirs系统变量所指定的路径中所有的类库根据扩展类加载器这个名称就可以推断出这是一种Java系统类库的扩展机制JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展Java SE的功能在JDK9之后这种扩展机制被模块化带来的天然的扩展能力取代

  1. 应用程序类加载器

  这个类加载器由sun.misc.Launcher$AppClassLoader来实现它负责加载用户类路径(ClassPath)上所有的类库开发者同样可以直接在代码中使用这个类加载如果应用程序中没有自定义过自己的类加载器一般情况下这个就是程序中默认的类加载器

JDK9之前的Java应用都是由这三种类加载器互相配合来完成加载的用户也可以自定义加载器来进行拓展

  图中展示的各种类加载器之间的层次关系被称为类加载器的双亲委派模型双亲委派模型要求除了顶层的启动类加载器外其余的类加载器都应有自己的父类加载器外

  双亲委派模型的工作过程是如果一个类加载器收到了类加载的请求它首先不会自己去尝试加载这个类而是把这个请求委派给父类加载器去完成每一个层次的类加载器都是如此因此所有的加载请求最终都应该传送到最顶层的启动类加载器中只有当副加载反馈自己无法完成这个加载请求时子加载器才会尝试自己去完成加载

    protected synchronized Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        Class c = findLoadedClass(name);

        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {

            }

            if (c == null) {
                c = findClass(name);
            }
        }

        if (resolve) {
            resolveClass(c);
        }

        return c;
    }

破坏双亲委派模型

  双亲委派模型并不是一个具有强制性约束的模型而是Java设计者推荐给开发者们的类加载器实现方式下面举个破坏双亲委派模型的例子

  双亲委派模型很好地解决了各个类加载器协作时基础类型的一致性问题(越基础的类由越上层的加载器进行加载)基础类型之所以被称为基础是因为它们总是作为被用户代码继承调用的API存在但程序设计往往没有绝对不变的完美规则如果有基础类型又要调用回用户的代码那该怎么办呢

下面浅析一下JDBC的加载过程

  DriverManager会尝试加载在"jdbc.drivers"系统属性中引用的驱动程序类这允许用户定制由他们的应用程序使用的JDBC Driver比如在META-INF/services/下放置驱动加载器com.mysql.cj.jdbc.Driver

  1. ServiceLoader会加载所有META-INF/services/实现Driver的class
  2. 迭代next()方法去加载实际的实现

其中迭代器的部分就是ServiceLoader的实现,调用Class.forName去加载类

private Class<?> nextProviderClass() {
        String fullName;
        if (this.configs == null) {
            try {
                fullName = "META-INF/services/" + ServiceLoader.this.service.getName();
                if (ServiceLoader.this.loader == null) {
                    this.configs = ClassLoader.getSystemResources(fullName);
                } else if (ServiceLoader.this.loader == ClassLoaders.platformClassLoader()) {
                    if (BootLoader.hasClassPath()) {
                        this.configs = BootLoader.findResources(fullName);
                    } else {
                        this.configs = Collections.emptyEnumeration();
                    }
                } else {
                    this.configs = ServiceLoader.this.loader.getResources(fullName);
                }
            } catch (IOException var4) {
                ServiceLoader.fail(ServiceLoader.this.service, "Error locating configuration files", var4);
            }
        }

        while (this.pending == null || !this.pending.hasNext()) {
            if (!this.configs.hasMoreElements()) {
                return null;
            }

            this.pending = this.parse((URL) this.configs.nextElement());
        }

        fullName = (String) this.pending.next();

        try {
            return Class.forName(fullName, false, ServiceLoader.this.loader);
        } catch (ClassNotFoundException var3) {
            ServiceLoader.fail(ServiceLoader.this.service, "Provider " + fullName + " not found");
            return null;
        }
    }

而在最开始调用ServiceLoader.load方法时使用的线程上下文类加载器

    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

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

  JDBC使用线程上下文类加载器去加载所需的SPI代码这是一种父类加载器去请求子类加载器完成类加载的行为这种行为实际上是打通了双亲委派模型的层次结构来逆向使用类加载器已经违背了双亲委派模型的一般性原则具体细节代码可以看网上的剖析

实现过程
  可以看到实现SPI是通过调用ServiceLoader.load方法然后通过迭代器来实例化所有在META-INF/services/下面的实现类并放入缓存中

  但是它不能按需加载如果META-INF/services/下面有多个实现那么就会将多个实现通过迭代器的方式同时进行加载同时不能直接根据某个参数直接获取某个具体的实现而是通过遍历的方式获取

Dubbo SPI机制

  可扩展性的优点主要表现模块之间解耦它符合开闭原则对扩展开放对修改关闭当系统增加新功能时不需要对现有系统的结构和代码进行修改仅仅新增一个扩展即可

用户能够基于 Dubbo 提供的扩展能力很方便基于自身需求扩展其他协议过滤器路由等Dubbo 扩展能力的特性

  • 按需加载Dubbo 的扩展能力不会一次性实例化所有实现而是用扩展类实例化减少资源浪费
  • 增加扩展类的 IOC 能力Dubbo 的扩展能力并不仅仅只是发现扩展服务实现类而是在此基础上更进一步如果该扩展类的属性依赖其他对象则 Dubbo 会自动的完成该依赖对象的注入功能
  • 增加扩展类的 AOP 能力Dubbo 扩展能力会自动的发现扩展类的包装类完成包装类的构造增强扩展类的功能
  • 具备动态选择扩展实现的能力Dubbo 扩展会基于参数在运行时动态选择对应的扩展类提高了 Dubbo 的扩展能力
  • 可以对扩展实现进行排序能够基于用户需求指定扩展实现的执行顺序
  • 提供扩展点的 Adaptive 能力该能力可以使的一些扩展类在 consumer 端生效一些扩展类在 provider 端生效

Dubbo 实现的一些例如动态选择扩展实现IOCAOP 等特性能够为用户提供非常灵活的扩展能力

Dubbo 扩展能力使得 Dubbo 项目很方便的切分成一个一个的子模块实现热插拔特性用户完全可以基于自身需求替换 Dubbo 原生实现来满足自身业务需求下面是Dubbo官网上关于可扩展的地方

规范定义

  • 接口名可以随意定义但接口必须被@SPI 注解修饰
  • 提供者配置文件路径在依次查找的目录为
    • META-INF/dubbo/internal
    • META-INF/dubbo
    • META-INF/services
  • 提供者配置文件名称接口的全限定性类名无需扩展名
  • 提供者配置文件内容 文件的内容为 key=value 形式 value 为该接口的实现类的全限类性类名 key 可以随意但一般为该实现类的标识前辍首字母小写一个类名占一行
  • 提供者加载 ExtensionLoader 类相当于 JDK SPI 中的 ServiceLoader 类用于加载提供者配置文件中所有的实现类并创建相应的实例
  • 增加了对扩展点 IoC 和 AOP 的支持一个扩展点可以直接使用 setter() 方法注入其他扩展点 也可 以对扩展点使用 Wrapper 类进行功能增强

下面举个例子

@SPI  
public interface Car {  
	String engineStart();  
}
public class Porsche implements Car {  
	@Override  
	public String engineStart() {  
		System.out.println("Porsche start");  
		return "Porsche start";  
	}  
}
public class Volvo implements Car {  
	@Override  
	public String engineStart() {  
		System.out.println("Volvo start");  
		return "Volvo start";  
	}  
}

在META-INF/dubbo下面添加两行

porsche=com.yonhoo.dubbo_server.spi.Porsche  
volvo=com.yonhoo.dubbo_server.spi.Volvo
public class DubboSpiTest {
        public static void main(String[] args) {
            ExtensionLoader<Car> loader = ExtensionLoader.getExtensionLoader(Car.class);

            Car alipay = loader.getExtension("volvo");
            System.out.println(alipay.engineStart());
        }
    }

output :
Volvo start
Volvo start

自适应机制Adaptive

Adaptive 机制即扩展类的自适应机制即其可以指定想要加载的扩展名也可以不指定若不指定则直接加载默认的扩展类即其会自动匹配做到自适应其是通过@Adaptive注解实现的

@Adaptive 修饰方法

被@Adapative修饰的 SPI 接口中的方法称为Adaptive方法会根据Adaptive方法自动为该SPI接口动态生成一个Adaptive扩展类并自动将其编译


package <SPI 接口所在包>;
public class SPI 接口名$Adpative implements SPI 接口 {
    public adaptiveMethod (arg0, arg1, ...) {
        // 注意<span class="bd-box"><h-char class="bd bd-beg"><h-inner></h-inner></h-char></span>下面的判断仅对 URL 类型<span class="bd-box"><h-char class="bd bd-beg"><h-inner></h-inner></h-char></span>或可以获取到 URL 类型值的参数进行判断
        // 例如<span class="bd-box"><h-char class="bd bd-beg"><h-inner></h-inner></h-char></span> dubbo 的 Invoker 类型中就包含有 URL 属性
        if(arg1==null) throw new IllegalArgumentException(异常信息)<span class="bd-box"><h-char class="bd bd-beg"><h-inner></h-inner></h-char></span>
        if(arg1.getUrl()==null) throw new IllegalArgumentException(异常信息)<span class="bd-box"><h-char class="bd bd-beg"><h-inner></h-inner></h-char></span>
        URL url = arg1.getUrl();
        
        // 其会根据@Adaptive 注解上声明的 Key 的顺序<span class="bd-box"><h-char class="bd bd-beg"><h-inner></h-inner></h-char></span>从 URL 获取 Value<span class="bd-box"><h-char class="bd bd-beg"><h-inner></h-inner></h-char></span>
        // 作为实际扩展类<span class="bd-box"><h-char class="bd bd-beg"><h-inner></h-inner></h-char></span>若有默认扩展类<span class="bd-box"><h-char class="bd bd-beg"><h-inner></h-inner></h-char></span>则获取默认扩展类名<span class="bd-box"><h-char class="bd bd-beg"><h-inner></h-inner></h-char></span>否则获取
        // 指定扩展名名<span class="bd-box"><h-char class="bd bd-beg"><h-inner></h-inner></h-char></span>
        String extName = url.get 接口名() == null?默认扩展前辍名:url.get 接口名();
        if(extName==null) throw new IllegalStateException(异常信息);
        SPI 接口 extension = ExtensionLoader.getExtensionLoader(SPI 接口.class)
            .getExtension(extName);
            
        return extension. adaptiveMethod(arg0, arg1, ...);
    }
    public unAdaptiveMethod( arg0, arg1, ...) {
        throw new UnsupportedOperationException(异常信息);
    }
}

举个例子

    @SPI
    public interface Car {
        String engineStart();

        @Adaptive
        void type(URL url);
    }
    public class Porsche implements Car {
        @Override
        public String engineStart() {
            System.out.println("Porsche start");
            return "Porsche start";
        }

        @Override
        public void type(URL url) {
            System.out.println("Porsche");
        }
    }
    public class Volvo implements Car {
        @Override
        public String engineStart() {
            System.out.println("Volvo start");
            return "Volvo start";
        }

        @Override
        public void type(URL url) {
            System.out.println("Volvo");
        }
    }
    public static void main(String[] args) {
        ExtensionLoader<Car> loader = ExtensionLoader.getExtensionLoader(Car.class);

        Car car = loader.getAdaptiveExtension();
        URL url = URL.valueOf("dubbo://localhost:20880?car=volvo");
        car.type(url);
    }

output :
Volvo start

@Adaptive 修饰类

    @SPI("volvo")
    public interface Car {
        String engineStart();
    }
    public class Porsche implements Car {
        @Override
        public String engineStart() {
            System.out.println("Porsche start");
            return "Porsche start";
        }
    }
    public class Volvo implements Car {
        @Override
        public String engineStart() {
            System.out.println("Volvo start");
            return "Volvo start";
        }
    }
    @Adaptive
    public class AdaptiveCar implements Car {

        private String type;

        public void setType(String type) {
            this.type = type;
        }


        @Override
        public String engineStart() {
            ExtensionLoader<Car> loader = ExtensionLoader.getExtensionLoader(Car.class);
            Car car;

            if (Strings.isBlank(type)) {
                car = loader.getDefaultExtension();
            } else {
                car = loader.getExtension(type);
            }

            return car.engineStart();
        }
    }
    public class DubboSpiTest {
        public static void main(String[] args) {
            ExtensionLoader<Car> loader = ExtensionLoader.getExtensionLoader(Car.class);

            Car car = loader.getAdaptiveExtension();

            car.engineStart();

            ((AdaptiveCar) car).setType("porsche");

            car.engineStart();

            System.out.println(loader.getSupportedExtensions());
        }
    }

output :
Volvo start
Porsche start
[porsche, volvo]

下面看Dubbo是如何通过Adaptive实现SPI的IOC

    @SPI
    public interface Car {
        String engineStart();
    }
    @SPI
    public interface CarFactory {
        void goingForADrive();
    }
    @Adaptive
    public class Nio implements Car {
        @Override
        public String engineStart() {
            System.out.println("Nio start");
            return "Nio start";
        }
    }
    public class DreamCarFactory implements CarFactory {

        private Car nio;

        @Override
        public void goingForADrive() {
            System.out.println("start");
            if (nio != null) {
                nio.engineStart();
            }
            System.out.println("end");
        }

        public void setCar(Car nio) {
            this.nio = nio;
        }
    }
    public class DubboSpiTest {
        public static void main(String[] args) {

            ExtensionLoader<CarFactory> extensionLoader = ExtensionLoader.getExtensionLoader(CarFactory.class);

            CarFactory dream = extensionLoader.getExtension("dream");

            dream.goingForADrive();
        }
    }

output:
start
Nio start
end

简单描述一下源码,injectExtension会在createExtension中为依赖的其他对象进行注入

    private T injectExtension(T instance) {
        if (this.injector == null) {
            return instance;
        } else {
            try {
                Method[] var2 = instance.getClass().getMethods();
                int var3 = var2.length;

                for(int var4 = 0; var4 < var3; ++var4) {
                    Method method = var2[var4];
                    if (this.isSetter(method) && !method.isAnnotationPresent(DisableInject.class) && method.getDeclaringClass() != ScopeModelAware.class && (!(instance instanceof ScopeModelAware) && !(instance instanceof ExtensionAccessorAware) || !ignoredInjectMethodsDesc.contains(ReflectUtils.getDesc(method)))) {
                        Class<?> pt = method.getParameterTypes()[0];
                        if (!ReflectUtils.isPrimitives(pt)) {
                            try {
                                String property = this.getSetterProperty(method);
                                Object object = this.injector.getInstance(pt, property);
                                if (object != null) {
                                    method.invoke(instance, object);
                                }
                            } catch (Exception var9) {
                                logger.error("0-15", "", "", "Failed to inject via method " + method.getName() + " of interface " + this.type.getName() + ": " + var9.getMessage(), var9);
                            }
                        }
                    }
                }
            } catch (Exception var10) {
                logger.error("0-15", "", "", var10.getMessage(), var10);
            }

            return instance;
        }
    }

this.injector.getInstance(pt, property); 会将这遍历ScopeBeanExtensionInjectorSpringExtensionInjectorSpiExtensionInjector来将不同的对象进行注入

这里重点说一下SpiExtensionInjector

public <T> T getInstance(final Class<T> type, final String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = this.extensionAccessor.getExtensionLoader(type);
            if (loader == null) {
                return null;
            } else {
                return !loader.getSupportedExtensions().isEmpty() ? loader.getAdaptiveExtension() : null;
            }
        } else {
            return null;
        }
    }

它会将set方法,这里demo中的是setCar将扫描Car.class的package然后从实现interface的@Adaptive标注的实现类进行注入

包装机制 Wrapper

Wrapper通过实现interface并且将interface的class作为构造参数来实现interceptor

@SPI
public interface Car {
    String engineStart();
}
    public class Porsche implements Car {
        @Override
        public String engineStart() {
            System.out.println("Porsche start");
            return "Porsche start";
        }
    }
    public class CustomizedCar implements Car {
        private Car car;

        public CustomizedCar(Car car) {
            this.car = car;
        }

        @Override
        public String engineStart() {
            System.out.println("begin customized");
            String result = car.engineStart();
            System.out.println("end customized");
            return result;
        }
    }
    public class DubboSpiTest {
        public static void main(String[] args) {


            ExtensionLoader<Car> loader = ExtensionLoader.getExtensionLoader(Car.class);

            Car car = loader.getExtension("porsche");

            car.engineStart();

        }
    }

output:
begin customized
Porsche start
end customized

激活机制 Activate

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    String[] group() default {};

    String[] value() default {};

    /** @deprecated */
    @Deprecated
    String[] before() default {};

    /** @deprecated */
    @Deprecated
    String[] after() default {};

    int order() default 0;
}

@Activate 有三个还在使用的field

  • group
    为扩展类指定所属的组别
  • value
    为当前扩展类指定key只有特定key的扩展来才会生效如果group和key同时设定则只有key会生效
  • order
    给当前扩展类设定优先级

具体例子就不再详解

  可以看到Dubbo的SPI机制有很多可定制化的扩展比如loadExtensionByName通过Adaptive来实现动态加载以及IOC通过Wrapper来实现Interceptor可以非常灵活的使用其底层实现方式差不太多但是会比ServiceLoader更加灵活和强大

reference

  1. dubbo SPI机制 demo和源码详解
  2. dubbo 官网源码解析