众所周知, Java 或者其余运行在 JVM(java 虚拟机)上面的程序都须要最终便觉得字节码,而后被 JVM加载运行,那么这个加载
到虚拟机的过程就是 classloader 类加载器所干的事情.直白一点,就是 经过一个类的全限定类名称来获取描述此类的二进制字节流 的过程.java
说到 Java 的类加载器,必不可少的就是它的双亲委派模型,从 Java 虚拟机的角度来看,只存在两种不一样的类加载器:git
java.lang.ClassLoader
在 Java 内部,绝大部分的程序都会使用 Java 内部提供的默认加载器.web
负责将$JAVA_HOME/lib
或者 -Xbootclasspath
参数指定路径下面的文件(按照文件名识别,如 rt.jar) 加载到虚拟机内存中.启动类加载器没法直接被 java 代码引用,若是须要把加载请求委派给启动类加载器,直接返回null
便可.api
负责加载$JAVA_HOME/lib/ext
目录中的文件,或者java.ext.dirs
系统变量所指定的路径的类库.tomcat
通常是系统的默认加载器,好比用 main 方法启动就是用此类加载器,也就是说若是没有自定义过类加载器,同时它也是getSystemClassLoader()
的返回值.app
这几种类加载器的工做流程被抽象成一个模型,就是双亲委派模型.webapp
工做流程:ide
这基本就是双亲委派模型.测试
可是这种模型只是一种推荐的方式,并非强制的,你也能够尝试打破这种规则. 自因此这样约定,仍是有必定的好处的, Java 类随着它的类加载器一块儿具有了一种带有优先级的层次关系. 好比本身定义了java.lang.Object
对象,那么按照上面的流程,他永远都是被启动类加载器加载的rt.jar 中的那个类,而不是本身定义的这个类,这样就保证了兄运行的稳定,不然,可能变得很是混乱,能够随意改写任何类.spa
大多数状况下,其实咱们并不须要知道这些,由于你的程序也会运行的很是正常,虽然像Tomcat
,Spring Boot
都有本身定义的类加载器,可是咱们在不用关心的状况下也会运行的好好地.
那么类加载器能够被运行在哪些地方呢?
JavaAgent
来加强字节码的时候.JavaAgent 的使用后续文章补上.先上一张图.
顶层是应用代码实际运行的 ClassLoader, 多是Application ClassLoader
, 也有多是 tomcat 的webapp ClassLoader
或者其余容器自定义的类加载器,老是是真实 的用户编写的代码运行的 classloader.
咱们若是要在javaagent
中加强用户或者用户使用的包进行加强的话,必须实现一个自定义的 classloader 来"继承"(委派)应用代码的类加载器.为何?
javaagent 的代码永远都是被应用类加载器( Application ClassLoader
)所加载,和应用代码的真实加载器无关,举个栗子,当前运行在 tomcat 中的代码是webapp ClassLoader
加载的,若是启动参数加上-javaagent
, 这个 javaagent 仍是在Application ClassLoader
中加载的.
按照上面的双亲委派模型,若是咱们在 javaagent 中想要访问应用里面的 api 包或者类,这是不可能的,由于按照双亲委派模型,通俗来讲就是,子加载器能够访问父加载器中的类,可是反过来就行不通.
那么这个时候有没有办法可以作到呢?
咱们能够自定义本身的类加载器继承应用代码类加载器(能够在 javaagent 中完成, javaagent 每加载一个类,就会回调传回真实的类加载器),而后咱们在Application ClassLoader
中用自定义的类加载器去加载子类,并建立好实例(newInstance()
), 将实例的引用保存 在变量中.
真实运行的时候,就会经过这个变量,去访问咱们自定义加载器的内容,又因为咱们的自定义类加载器是继承自应用代码的类加载器的,因此自定义类加载器中的代码能够访问应用的代码.
总结一句就是,父类加载器没法加载子类加载器的类,可是能够持有子类加载器所加载类的实例,从而实现父类加载器的代码能够调用子类加载器的代码的形式
貌似比较抽象,后面会补上详细的例子供参考.
针对上面的情形,咱们定义一个例子,能够详细解释 ClassLoader 的加载使用,
FooClassLoader
:package com.example.test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/** * @author lican */
public class FooClassLoader extends ClassLoader {
private static final String NAME = "/Users/lican/git/test/foo/";
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass == null) {
String s = name.substring(name.lastIndexOf(".") + 1) + ".class";
File file = new File(NAME + s);
try (FileInputStream fileInputStream = new FileInputStream(file)) {
byte[] b = new byte[fileInputStream.available()];
fileInputStream.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
e.printStackTrace();
}
}
return loadedClass;
}
}
复制代码
/Users/lican/git/test/foo/
这里的,主要是方便测试.package com.example.test;
public class FooTest {
public String getFoo() {
return "foo";
}
}
复制代码
而后测试程序为:
package com.example.test;
import java.lang.reflect.Method;
/** * @author lican */
public class ClassLoaderTest {
private Object fooTestInstance;
private FooClassLoader fooClassLoader = new FooClassLoader();
public static void main(String[] args) throws Exception {
ClassLoaderTest classLoaderTest = new ClassLoaderTest();
classLoaderTest.initAndLoad();
Object fooTestInstance = classLoaderTest.getFooTestInstance();
System.out.println(fooTestInstance.getClass().getClassLoader());
Method getFoo = fooTestInstance.getClass().getMethod("getFoo");
System.out.println(getFoo.invoke(fooTestInstance));
System.out.println(classLoaderTest.getClass().getClassLoader());
}
private void initAndLoad() throws Exception {
Class<?> aClass = Class.forName("com.example.test.FooTest", true, fooClassLoader);
fooTestInstance = aClass.newInstance();
}
public Object getFooTestInstance() {
return fooTestInstance;
}
}
复制代码
咱们用FooClassLoader
来加载com.example.test.FooTest
, 而后在 AppClassLoader中持有引用.被后续使用.