- ClassLoader用来加载class文件的。
- 系统内置的ClassLoader通过双亲委托来加载指定路径下的class和资源。
- 可以自定义ClassLoader一般覆盖findClass()方法。
- ContextClassLoader与线程相关,可以获取和设置,可以绕过双亲委托的机制
ClassLoader故名思意,就是“类加载器”,具体作用就是将class文件加载到jvm虚拟机中去,程序就可以正确运行了。
每个类编译后产生一个Class对象,存储在.class文件中,JVM使用类加载器(Class Loader)来加载类的字节码文件(.class),类加载器实质上是一条类加载器链,一般的,我们只会用到一个原生的类加载器,它只加载Java API等可信类,通常只是在本地磁盘中加载,这些类一般就够我们使用了。如果我们需要从远程网络或数据库中下载.class字节码文件,那就需要我们来挂载额外的类加载器。
一般来说,类加载器是按照树形的层次结构组织的,每个加载器都有一个父类加载器。另外,每个类加载器都支持代理模式,即可以自己完成Java类的加载工作,也可以代理给其它类加载器。
任何和Java相关的编程代码都离不开ClassLoader,譬如Jvm启动的时候就需要加载一些基启动.class文件、加载String|Integer.class等基础文件、加载自己写的业务.class文件等
但是,jvm启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。毕竟一次性加载那么多jar包那么多class,内存不崩溃才怪
不管使用什么样的类加载器,类,都是在第一次被用到时,动态加载到JVM的。这句话有两层含义:
1. Java程序在运行时并不一定被完整加载,只有当发现该类还没有加载时,才去本地或远程查找类的.class文件并验证和加载;
2. 当程序创建了第一个对类的静态成员的引用(如类的静态变量、静态方法、构造方法——构造方法也是静态的)时,才会加载该类。
Java的这个特性叫做:动态加载。
一、Class文件的认识
我们都知道在Java中程序是运行在虚拟机中,我们平常用文本编辑器或者是IDE编写的程序都是.java格式的文件,这是最基础的源码,但这类文件是不能直接运行的。
但是java虚拟机并不能直接识别我们平常编写的.java源文件,所以需要javac这个命令转换成.class文件。
另外,如果用C或者PYTHON编写的程序正确转换成.class文件后,java虚拟机也是可以识别运行的。更多信息大家可以参考这篇深入理解Java Class文件格式(一)
如我们编写一个简单的程序HelloWorld.java
//- HelloWorld.java 1KB
public class HelloWorld{
public static void main(String[] args){
System.out.println("Hello world!");
}
}
然后,我们需要在命令行中进行java文件的编译,可以看到目录下生成了.class文件
javac HelloWorld.java
//- HelloWorld.java 1KB
//- HelloWorld.class 1KB
我们再从命令行中执行命令:
java HelloWorld
【图helloWord】
了解了.class文件后,我们再来思考下,我们平常在开发中编写的java程序是如何运行的,也就是我们自己编写的各种类是如何被加载到jvm(java虚拟机)中去并执行的
二、java环境变量的认识
初学java的时候,最害怕的就是下载JDK后要配置环境变量了,关键是当时不理解,所以战战兢兢地照着书籍上或者是网络上的介绍进行操作
问题1:那么为什么要配置环境变量呢?
其实就是,如果你想在搞Java开发,那么你肯定要依赖于一些 Integer、String、List.class等类,这些类在我们引用到的时候,会被编译环境import入我们的代码中
问题2:那么:我们思考一个有趣问题, 这些.class是在那里的呢?
答案就是jdk/lib或者jdk/ext目录下的一众jar包,而环境变量就是指向jar包的位置
2.1 环境变量
- JAVA_HOME
指的是你JDK安装的位置,一般默认安装在C盘,如
C:\Program Files\Java\jdk1.8.0_91
- PATH
将程序路径包含在PATH当中后,在命令行窗口就可以直接键入它的名字了,而不再需要键入它的全路径,比如上面代码中用的到javac和java两个命令。
一般的,也就是在原来的PATH路径上添加JDK目录下的bin目录和jre目录的bin
PATH=%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;%PATH%;
- CLASSPATH
一看就是指向各种jar包路径。需要注意的是前面的.;,.代表当前目录
CLASSPATH=.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
2.2 环境变量的设置与查看
设置可以右击我的电脑,然后点击属性,再点击高级,然后点击环境变量,具体不明白的自行查阅文档。
查看的话可以打开命令行窗口
echo %JAVA_HOME%
echo %PATH%
echo %CLASSPATH%
三、JAVA类加载器
Java语言系统自带有三个类加载器:
-
Bootstrap ClassLoader 最顶层的加载类
sun.boot.class.path
:加载该路径下的jar包- 主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。
- 可以通过启动jvm时指定-Xbootclasspath和路径, 来改变Bootstrap ClassLoader的加载目录。比如java -Xbootclasspath/a:path被指定的文件追加到默认的bootstrap路径中。我们可以打开我的电脑,在上面的目录下查看,看看这些jar包是不是存在于这个目录。
-
Extention ClassLoader 扩展的类加载器
java.ext.dirs
:加载该路径下的jar包- 加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。
- 还可以加载-D java.ext.dirs选项指定的目录。
-
App ClassLoader也称为SystemAppClass
java.class.path
:加载该路径下的jar包- 加载当前应用的classpath的所有类
当然,Java允许我们自定义类加载器:
- 自定义的ClassLoader
- 加载任意位置的.class,譬如网络上下载的某个文件,D盘里的某个文件
3.1 加载器的实例化顺序
我们看到了系统的3个类加载器,他们是在什么时间、以什么样的顺序实例化的呢?答案 :
- Bootstrap CLassloder
- Extention ClassLoader
- AppClassLoader
我们看他们实例化的入口,也就是一个java虚拟机的入口应用,sun.misc.Launcher
public class Launcher {
private static Launcher launcher = new Launcher();
private static String bootClassPath =
System.getProperty("sun.boot.class.path");
public static Launcher getLauncher() {
return launcher;
}
private ClassLoader loader;
public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
}
// Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
}
//设置AppClassLoader为线程上下文类加载器,这个文章后面部分讲解
Thread.currentThread().setContextClassLoader(loader);
}
/*
* Returns the class loader used to launch the main application.
*/
public ClassLoader getClassLoader() {
return loader;
}
/*
* The class loader used for loading installed extensions.
*/
static class ExtClassLoader extends URLClassLoader {}
/**
* The class loader used for loading from java.class.path.
* runs in a restricted security context.
*/
static class AppClassLoader extends URLClassLoader {}
源码有精简,我们可以得到相关的信息:
- Launcher初始化了ExtClassLoader和AppClassLoader。
- Launcher中并没有看见BootstrapClassLoader,因为这是个C实现loader,
- 但通过System.getProperty(“sun.boot.class.path”)得到了字符串bootClassPath,这个应该就是BootstrapClassLoader加载的jar包路径。
BootstrapClassLoader、ExtClassLoader、AppClassLoader实际是查阅相应的环境属性sun.boot.class.path
、java.ext.dirs
和java.class.path
来加载资源文件的
3.1.1 Bootstrap ClassLoader
Bootstrap ClassLoader是由C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用
-
JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载
-
Bootstrap没有父加载器,但是它却可以作为任何一个ClassLoader的父加载器。比如ExtClassLoader
- 这句话的理解,必须结合后文中的 loadClass()过程,也就是在双亲委托模型中向上迭代父加载器查找时,如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader
我们可以先代码测试一下sun.boot.class.path是什么内容:
System.out.println(System.getProperty("sun.boot.class.path"));
得到的结果如下,这些全是JRE目录下的jar包或者是class文件:
C:\Program Files\Java\jre1.8.0_91\lib\resources.jar;
C:\Program Files\Java\jre1.8.0_91\lib\rt.jar;
C:\Program Files\Java\jre1.8.0_91\lib\sunrsasign.jar;
C:\Program Files\Java\jre1.8.0_91\lib\jsse.jar;
C:\Program Files\Java\jre1.8.0_91\lib\jce.jar;
C:\Program Files\Java\jre1.8.0_91\lib\charsets.jar;
C:\Program Files\Java\jre1.8.0_91\lib\jfr.jar;
C:\Program Files\Java\jre1.8.0_91\classes
3.1.2 ExtClassLoader
/*
* The class loader used for loading installed extensions.
*/
static class ExtClassLoader extends URLClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}
/**
* create an ExtClassLoader. The ExtClassLoader is created
* within a context that limits which files it can read
*/
public static ExtClassLoader getExtClassLoader() throws IOException
{
final File[] dirs = getExtDirs();
try {
// Prior implementations of this doPrivileged() block supplied
// aa synthesized ACC via a call to the private method
// ExtClassLoader.getContext().
return AccessController.doPrivileged(
new PrivilegedExceptionAction<ExtClassLoader>() {
public ExtClassLoader run() throws IOException {
int len = dirs.length;
for (int i = 0; i < len; i++) {
MetaIndex.registerDirectory(dirs[i]);
}
return new ExtClassLoader(dirs);
}
});
} catch (java.security.PrivilegedActionException e) {
throw (IOException) e.getException();
}
}
private static File[] getExtDirs() {
String s = System.getProperty("java.ext.dirs");
File[] dirs;
if (s != null) {
StringTokenizer st =
new StringTokenizer(s, File.pathSeparator);
int count = st.countTokens();
dirs = new File[count];
for (int i = 0; i < count; i++) {
dirs[i] = new File(st.nextToken());
}
} else {
dirs = new File[0];
}
return dirs;
}
/*
* Creates a new ExtClassLoader for the specified directories.
*/
public ExtClassLoader(File[] dirs) throws IOException {
super(getExtURLs(dirs), null, factory);
}
}
我们先前的内容有说过,可以指定-D java.ext.dirs参数来添加和改变ExtClassLoader的加载路径。
这里我们通过可以编写测试代码:
System.out.println(System.getProperty("java.ext.dirs"));
结果如下:
C:\Program Files\Java\jre1.8.0_91\lib\ext;C:\Windows\Sun\Java\lib\ext
3.1.3 AppClassLoader
/**
* The class loader used for loading from java.class.path.
* runs in a restricted security context.
*/
static class AppClassLoader extends URLClassLoader {
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
final String s = System.getProperty("java.class.path");
final File[] path = (s == null) ? new File[0] : getClassPath(s);
return AccessController.doPrivileged(
new PrivilegedAction<AppClassLoader>() {
public AppClassLoader run() {
URL[] urls =
(s == null) ? new URL[0] : pathToURLs(path);
return new AppClassLoader(urls, extcl);
}
});
}
......
}
可以看到AppClassLoader加载的就是java.class.path下的路径。
System.out.println(System.getProperty("java.class.path"));
结果如下,这个路径其实就是当前java工程目录bin,里面存放的是编译生成的class文件:
D:\workspace\ClassLoaderDemo\bin
3.2 每个类都有一个加载器
- 创建一个Test.java文件
- 编写一个ClassLoaderTest.java文件
public class Test{}
public class ClassLoaderTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
ClassLoader cl = Test.class.getClassLoader();
System.out.println("ClassLoader is:"+cl.toString());
}
}
我们获取到了Test.class文件的类加载器,然后打印出来。结果是:
//说明Test.class文件是由AppClassLoader加载的
ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
这个Test类是我们自己编写的,那么int.class或者是String.class的加载是由谁完成的呢?
public class ClassLoaderTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
ClassLoader cl = Test.class.getClassLoader();
System.out.println("ClassLoader is:"+cl.toString());
cl = int.class.getClassLoader();
System.out.println("ClassLoader is:"+cl.toString());
}
}
运行一下,却报错了
ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
Exception in thread "main" java.lang.NullPointerException
at ClassLoaderTest.main(ClassLoaderTest.java:15)
提示的是空指针,意思是int.class这类基础类没有类加载器加载?
其实并不是, int.class是由Bootstrap ClassLoader加载的,但是java层没有它的引用,是C层加载的,所有返回null
四、类的加载
java动态载入class的两种方式:
- implicit隐式,即利用实例化才载入的特性来动态载入class。
- 使用 new + 构造方法时,隐式的调用类加载器,加载对应的类到 JVM 中,是最常见的类加载方式。
- explicit显式方式,又分两种方式:
- java.lang.Class的forName()方法
- java.lang.ClassLoader的loadClass()方法
- 当我们获取到了 Class 对象后,需要调用 Class 对象的 newInstance() 方法来生成对象的实例
- 不支持传入参数,如果想使用有参的构造函数,必须通过反射的方式,来获取到该类的有参构造方法。
4.1 每个类加载器都有一个父加载器
4.1.1 AppClassLoader的父加载器是ExtClassLoader
每个类加载器都有一个父加载器,比如加载Test.class是由AppClassLoader完成,那么AppClassLoader也有一个父加载器,怎么样获取呢?
很简单,通过getParent方法。
比如代码可以这样编写:
ClassLoader cl = Test.class.getClassLoader();
System.out.println("ClassLoader is:"+cl.toString());
System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());
运行结果如下:
ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@15db9742
getParent()方法
我们都已经知道
static class ExtClassLoader extends URLClassLoader {}
static class AppClassLoader extends URLClassLoader {}
【图URLClassLoader】
URLClassLoader的源码中并没有找到getParent()方法。这个方法在ClassLoader.java中:
public abstract class ClassLoader {
// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
private final ClassLoader parent;
// The class loader for the system
// @GuardedBy("ClassLoader.class")
private static ClassLoader scl;
public final ClassLoader getParent() {
if (parent == null)
return null;
return parent;
}
//【实例方式1】:由外部类创建ClassLoader时直接指定一个ClassLoader为parent
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
...
}
//【实例方式2】
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
return scl;
}
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
//通过Launcher获取ClassLoader
scl = l.getClassLoader();
try {
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
if (oops != null) {
if (oops instanceof Error) {
throw (Error) oops;
} else {
// wrap the exception
throw new Error(oops);
}
}
}
sclSet = true;
}
}
}
我们可以看到getParent()实际上返回的就是一个ClassLoader对象parent,parent的赋值是在ClassLoader对象的构造方法中,它有两个情况:
- 由外部类创建ClassLoader时直接指定一个ClassLoader为parent。
- 由getSystemClassLoader()方法生成,也就是在
sun.misc.Laucher
通过getClassLoader()
获取,也就是AppClassLoader
。- 直白的说,一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader
现在我们回顾下,Launcher.launcher()中:
public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
}
// Now create the class loader to use to launch the application
try {
//【核心点】将ExtClassLoader对象实例传递进去
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
}
}
public ClassLoader getClassLoader() {
return loader;
}
我们注意:
- 在构建
ExtClassLoader
时,ExtClassLoader的parent为null
//
ExtClassLoader.getExtClassLoader();
//它调用了它的父类也就是URLClassLoder的构造方法并传递了3个参数
public ExtClassLoader(File[] dirs) throws IOException {
super(getExtURLs(dirs), null, factory);
}
//注意 ClassLoader parent 为 null
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {
super(parent);
}
- 在构建
AppClassLoader
时loader = AppClassLoader.getAppClassLoader(extcl)
,这已经说明了AppClassLoader的parent是一个ExtClassLoader实例
4.1.2 ExtClassLoader的父加载是Bootstrap ClassLoader
我们上述中已经说了 ExtClassLoader的parent为null,那么为什么我们还说它有父加载器呢?
这个其实是一种形式上的父加载器,还是要放入后文的 loadclass()过程中的 双亲委托模型中,才有意义
执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。
这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器
4.2 双亲委托
每个ClassLoader都维护了一份自己的名称空间, 同一个名称空间里不能出现两个同名的类
一个类加载器查找class和resource时,是通过“委托模式”进行的
它首先判断这个class是不是在已经加载成功(在当前层级类加载器的缓存中),如果没有的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到Bootstrap ClassLoader。如果Bootstrap
如果Bootstrap classloader找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。
【图双亲委托】
我们可以看出委托是从下向上(向上判断是否缓存已加载),然后具体查找过程却是自上至下(向下查找对应的路径并加入缓存),用序列描述一下:
- 一个AppClassLoader查找资源时,先看看缓存是否有,缓存有从缓存中获取,否则委托给父加载器。
- 递归,重复第1步的操作。
- 如果ExtClassLoader也没有加载过,则由Bootstrap ClassLoader出面。
- Bootstrap ClassLoader首先查找缓存,如果没有找到的话,就去找自己的规定的路径下,也就是
sun.mic.boot.class
下面的路径。找到就返回,没有找到,让子加载器自己去找
- Bootstrap ClassLoader首先查找缓存,如果没有找到的话,就去找自己的规定的路径下,也就是
- Bootstrap ClassLoader如果没有查找成功,则ExtClassLoader自己在
java.ext.dirs
路径中去查找,查找成功就返回,查找不成功,再向下让子加载器找 - ExtClassLoader查找不成功,AppClassLoader就自己查找,在
java.class.path
路径下查找。找到就返回。如果没有找到就让子类找 - 如果没有子类会抛出各种异常
为什么要使用这种双亲委托模式呢?
- 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
- 考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的
String.class
来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String.class
已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoade
4.3 loadClass()
上面已经详细介绍了加载过程,具体的代码实现其实就是
- loadClass()
- findLoadedClass()
- findClass()
- 内部调用了 defineClass
- defineClass()
这个方法在编写自定义classloader的时候非常重要,它能将class二进制内容转换成Class对象,如果不符合要求的会抛出各种异常 - resolveClass 初始化类
我们从 loadClass()看:
- 执行findLoadedClass(String)去检测这个class是不是已经加载过了。
- 执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。
- 如果向上委托父加载器没有加载成功,则通过findClass(String)查找
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//【1】委托是从下向上(向上判断是否缓存已加载)
// 首先,检测是否已经加载,即已经在缓存中
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//父加载器不为空则调用父加载器的loadClass
c = parent.loadClass(name, false);
} else {
//父加载器为空则调用Bootstrap Classloader
//【前面说过ExtClassLoader的parent为null,所以它向上委托时,系统会为它指定Bootstrap ClassLoader】
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//【2】具体查找过程却是自上至下(向下查找对应的路径并加入缓存)
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//父加载器没有找到,则调用findclass,去对应路径下找目标资源
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//调用resolveClass()
resolveClass(c);
}
return c;
}
}
4.4 forName()
Class.forName使用的是被调用者的类加载器来加载类的。
这种特性, 证明了java类加载器中的名称空间是唯一的, 不会相互干扰。
即在一般情况下, 保证同一个类中所关联的其他类都是由当前类的类加载器所加载的
public static Class forName(String className)
throws ClassNotFoundException {
return forName0(className, true , ClassLoader.getCallerClassLoader());
}
/** Called after security checks have been made. */
private static native Class forName0(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException;
@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
Class<?> caller = null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// Reflective call to get caller class is only needed if a security manager
// is present. Avoid the overhead of making this call otherwise.
caller = Reflection.getCallerClass();
if (sun.misc.VM.isSystemDomainLoader(loader)) {
ClassLoader ccl = ClassLoader.getClassLoader(caller);
if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(name, initialize, loader, caller);
}
上面中 ClassLoader.getCallerClassLoader 就是得到调用当前forName方法的类的类加载器
五、自定义ClassLoader
不管是Bootstrap ClassLoader还是ExtClassLoader等,这些类加载器都只是加载指定的目录下的jar包或者资源。如果在某种情况下,我们需要动态加载一些东西呢?比如从D盘某个文件夹加载一个class文件,或者从网络上下载class主内容然后再进行加载,这样可以吗?
如果要这样做的话,需要我们自定义一个classloader
- 编写一个类继承自
ClassLoader
抽象类。 - 复写它的
findClass()
方法。 - 在
findClass()
方法中调用defineClass()
一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader,这样就能够保证它能访问系统内置加载器加载成功的class文件。
5.1 示例之DiskClassLoader
假设我们需要一个自定义的classloader,默认加载路径为D:\lib下的jar包和资源
们在findClass()方法中定义了查找class的方法,然后数据通过defineClass()生成了Class对象
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class DiskClassLoader extends ClassLoader {
private String mLibPath;
public DiskClassLoader(String path) {
// TODO Auto-generated constructor stub
mLibPath = path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
String fileName = getFileName(name);
File file = new File(mLibPath,fileName);
try {
FileInputStream is = new FileInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len = 0;
try {
while ((len = is.read()) != -1) {
bos.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] data = bos.toByteArray();
is.close();
bos.close();
return defineClass(name,data,0,data.length);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return super.findClass(name);
}
//获取要加载 的class文件名
private String getFileName(String name) {
// TODO Auto-generated method stub
int index = name.lastIndexOf('.');
if(index == -1){
return name+".class";
}else{
return name.substring(index+1)+".class";
}
}
}
来看下使用:
//编译Test.java后生成的文件Test.class放到D:\lib这个路径下
package com.frank.test;
public class Test {
public void say(){
System.out.println("Say Hello");
}
}
//调用一个Test对象的say方法
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建自定义classloader对象。
DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib");
try {
//加载class文件
Class c = diskLoader.loadClass("com.frank.test.Test");
if(c != null){
try {
Object obj = c.newInstance();
Method method = c.getDeclaredMethod("say",null);
//通过反射调用Test类的say方法
method.invoke(obj, null);
} catch (InstantiationException | IllegalAccessException
| NoSuchMethodException
| SecurityException |
IllegalArgumentException |
InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
5.2 示例之解密类加载器
六、Context ClassLoader
前面讲到过Bootstrap ClassLoader、ExtClassLoader、AppClassLoader,现在又出来这么一个类加载器,这是为什么?
前面三个之所以放在前面讲,是因为它们是真实存在的类,而且遵从”双亲委托“的机制。
而ContextClassLoader其实是一个概念,只是一个成员变量,通过setContextClassLoader()方法设置,通过getContextClassLoader()设置
public class Thread implements Runnable {
/* The context ClassLoader for this thread */
private ClassLoader contextClassLoader;
public void setContextClassLoader(ClassLoader cl) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("setContextClassLoader"));
}
contextClassLoader = cl;
}
public ClassLoader getContextClassLoader() {
if (contextClassLoader == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(contextClassLoader,
Reflection.getCallerClass());
}
return contextClassLoader;
}
}
每个Thread都有一个相关联的ClassLoader,默认是AppClassLoader。并且子线程默认使用父线程的ClassLoader除非子线程特别设置
使用线程上下文类加载器, 可以在执行线程中, 抛弃双亲委派加载链模式, 使用线程上下文里的类加载器加载类.
典型的例子有, 通过线程上下文来加载第三方库jndi实现, 而不依赖于双亲委派.大部分java app服务器(jboss, tomcat…)也是采用contextClassLoader来处理web服务。还有一些采用 hotswap 特性的框架, 也使用了线程上下文类加载器, 比如 seasar (full stack framework in japenese).
6.1 示例
package com.frank.test;
public interface ISpeak {
public void speak();
}
//生成的SpeakTest.class文件放置在D:\\lib\\test目录下
public class SpeakTest implements ISpeak {
@Override
public void speak() {
// TODO Auto-generated method stub
System.out.println("Test");
}
}
//生成的SpeakTest.class文件放置在D:\\lib目录下
public class SpeakTest implements ISpeak {
@Override
public void speak() {
// TODO Auto-generated method stub
System.out.println("I\' frank");
}
}
我们来看下线程下的使用:
DiskClassLoader1 diskLoader1 = new DiskClassLoader1("D:\\lib\\test");
Class cls1 = null;
try {
//加载class文件
cls1 = diskLoader1.loadClass("com.frank.test.SpeakTest");
System.out.println(cls1.getClassLoader().toString());
if(cls1 != null){
try {
Object obj = cls1.newInstance();
Method method = cls1.getDeclaredMethod("speak",null);
//通过反射调用Test类的speak方法
method.invoke(obj, null);
} catch (InstantiationException | IllegalAccessException
| NoSuchMethodException
| SecurityException |
IllegalArgumentException |
InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib");
System.out.println("Thread "+Thread.currentThread().getName()+" classloader: "+Thread.currentThread().getContextClassLoader().toString());
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread "+Thread.currentThread().getName()+" classloader: "+Thread.currentThread().getContextClassLoader().toString());
// TODO Auto-generated method stub
try {
//加载class文件
//Thread.currentThread().setContextClassLoader(diskLoader);
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class c = cl.loadClass("com.frank.test.SpeakTest");
System.out.println(c.getClassLoader().toString());
if(c != null){
try {
Object obj = c.newInstance();
Method method = c.getDeclaredMethod("speak",null);
//通过反射调用Test类的say方法
method.invoke(obj, null);
} catch (InstantiationException | IllegalAccessException
| NoSuchMethodException
| SecurityException |
IllegalArgumentException |
InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
【图1】
我们可以得到如下的信息:
- DiskClassLoader1加载成功了SpeakTest.class文件并执行成功。
- 子线程的ContextClassLoader是AppClassLoader。
- AppClassLoader加载不了父线程当中已经加载的SpeakTest.class内容
我们修改一下代码,在子线程开头处加上这么一句内容:Thread.currentThread().setContextClassLoader(diskLoader1);
【图2】
我们可以得到如下的信息:
- 子线程的ContextClassLoader变成了DiskClassLoader
其他
显式调用时,static块在什么时候执行?
- 当调用forName(String)载入class时执行,如果调用ClassLoader.loadClass并不会执行.forName(String,false,ClassLoader)时也不会执行.
- 如果载入Class时没有执行static块则在第一次实例化时执行.比如new ,Class.newInstance()操作
- static块仅执行一次
loadClass() 和 forName() 的区别
- loadClass
- 函数中有 resolve 这个布尔变量,resolve 即解析,为true时触发resolveClass()
- resolveClass() 对应了Class Link过程
- 默认的 loadClass() 方法会传入 false,也就是说,使用默认的 loadClass() 方法获得的 Class 对象是还没有执行链接的,只触发了第一步的加载
- forName
- 函数中调用了native函数,传入了initialize 变量,initialize 即初始化。
- forName0(className, true, ClassLoader.getClassLoader(caller), caller);
- 默认传入的 initialize 为 true.说明使用 Class.forName() 方法获得 Class 对象是已经执行完初始化的了
- (注意这里指的是类加载过程中的最后一步:初始化,而非是实例化对象操作的初始化)
各个java类由哪些classLoader加载?
- java类可以通过实例.getClass.getClassLoader()得知
- 接口由AppClassLoader(System ClassLoader,可以由ClassLoader.getSystemClassLoader()获得实例)载入
- ClassLoader类由bootstrap loader载入
NoClassDefFoundError和ClassNotFoundException
- NoClassDefFoundError:当java源文件已编译成.class文件,但是ClassLoader在运行期间在其搜寻路径load某个类时,没有找到.class文件则报这个错
- ClassNotFoundException:试图通过一个String变量来创建一个Class类时不成功则抛出这个异常