- 检查这个类是否已经被加载进去了
- 如果还没有加载,调用父对象加载该类
- 如果父对象无法加载,调用本对象的findClass()取得这个类。
因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,如果编写自己的ClassLoader,可以
- 在执行非置信代码之前,自动验证数字签名
- 动态地创建符合用户特定需要的定制化构建类
- 从特定的场所取得java class,例如数据库和网络。
当创建自己的ClassLoader时,需要继承java.lang.ClassLoader或者它的子类。在实例化每个ClassLoader对 象时,需要指定一个父对象;如果没有的话,系统自动指定ClassLoader.getSystemClassLoader()为父对象。
第五部分: Class.forName()与ClassLoader.loadClass()的区别
Class clazz = Class.forName("XXX.XXX");
与
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class clazz = cl.loadClass("XXX.XXX");
都可以装载一个类那么他们的区别是什么呢?
进一步研究Class.forName()是调用
Class.forName(name, initialize, loader); 也就是Class.forName("XXX.XXX"); 等同与 Class.forName("XXX.XXX", true, CALLCLASS.class.getClassLoader());
第二次参数表示装载类的时候是否初始化该类, 即调用类的静态块的语句及初始化静态成员变量。
Class clazz = cl.loadClass("XXX.XXX"); 没有指定是否初始化的选项。只有执行clazz.newInstance();时才能够初始化类。可以说 Class.forName("XXX.XXX", false, cl)执行过程是一致的。只是ClassLoader.loadClass()是更底 层的操作。
看一下JDBC驱动的装载。
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbcurl");
当调用Class.forName("com.mysql.jdbc.Driver");是Driver已经被初始化并注册到DriverManager中。MySQL Driver的代码
public class Driver extends NonRegisteringDriver
implements java.sql.Driver
{
public Driver()
throws SQLException
{
}
static
{
try
{
DriverManager.registerDriver(new Driver());
}
catch(SQLException E)
{
throw new RuntimeException("Can't register driver!");
}
}
}
改修JDBC驱动的装载
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class clazz = cl.loadClass("com.mysql.jdbc.Driver");
clazz.newInstance();
Connection conn = DriverManager.getConnection("jdbcurl");
同样可以执行。
进一步说:
Class .forName是从指定的classloader中装载类,如果没有指定,也就是一个参数的时候,是从装载当前对象实例所在的classloader中装载类.
而ClassLoader的实例调用loadclass方法,是指从当前ClassLoader实例中调用类, 而这个实例与装载当前所在类实例的Classloader也许不是同一个 .
举个例子吧, 有A,B , C两个ClassLoader , 当前运行的类D的实例是d(装载它的是A) , 如果D中使用 Class .forName那么就是使用的ClassLoader就是A,当然,也可以指定为B. 而如果D中代码找到的ClassLoader实例是C,那么就是用D来装载所指定的类.
为什么要用不同的ClassLoader 装载?
举例来说:如果在 Class 被载入的过程中,你希望使用在自己的 Class Loader来实现特定的操作,请使用ClassLoader方式。
貌似CGLib之类的bytecode generation框架很多地方会使用指定特殊ClassLoader的方式。
使用多个classloader的情况非常常见,比如说我们的app server,那么都是这样的. 在Web与EJB间, 他们的classLoader就是不同的,这样做的目的就是为了避免两者间类装载的相互干扰.
再举个例子:
Static初始化区块在什么时候被调用的问题?
Public A{Static{System.out.println(“HaHaHa”);}}
Class.forName(“A”);
Class.forName(“A”,false, ClassLoader.getSystemClassLoader());
看看奥妙在哪里?Java ClassLoader机制和原理又是如何?
程序示例:
public class A {
static { System.out.println("A`static is executed!");}
public A() {System.out.println("A`construct is executed!");}
public void show(){System.out.println("A`method is executed!");}
}
调用程序1:
Class c = Class.forName("A");
Method m = c.getMethod("show", new Class[0]);
System.out.println("A`test is executed!");
Object obj = c.newInstance();
m.invoke(obj, new Object[0]);
执行结果:
A`static is executed!
A`test is executed!
A`construct is executed!
A`method is executed!
调用程序2:
Class c = ClassLoader.getSystemClassLoader().loadClass("A");
System.out.println("A`test is executed!");
Method m = c.getMethod("show", new Class[0]);
Object obj = c.newInstance();
m.invoke(obj, new Object[0]);
执行结果:
A`test is executed!
A`static is executed!
A`construct is executed!
A`method is executed!
可见执行顺序为先执行 static{}块中的代码,然后执行构造函数,之后才是方法的调用。
classloader的两种载入方式:
1)pre-loading预先载入,载入 基础 类
2)load-on-demand按需求载入
java动态载入class的两种方式:
1)implic it隐式,即利用实例化才载入的特性来动态载入class
2)explic it显式方式,又分两种方式:
a)java.lang.Class的forName()方法 (上述调用程序1采用此方式载入)
b)java.lang.ClassLoader的loadClass()方法(上述调用程序2采用此方式载入)
static块在什么时候执行?
当调用forName(String)载入class时执行, ( 这个过程在类的所有父类中递归地调用)
如果调用ClassLoader.loadClass并不会执行.
forName(String,false,ClassLoader)时也不会执行.
如果载入Class时没有执行static块则在第一次实例化时执行.比如new ,Class.newInstance()操作
static块仅执行一次
(1)jvm的装载过程以及装载原理
所谓装载就是寻找一个类或是一个接口的二进制形式并用该二进制形式来构造代表这个类或是这个接口的class对象的过程,其中类或接口的名称是给定了的。当然名称也可以通过计算得到,但是更常见的是通过搜索源代码经过编译器编译后所得到的二进制形式来构造。 在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
装载:查找和导入类或接口的二进制数据;
链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
校验:检查导入类或接口的二进制数据的正确性;
准备:给类的静态变量分配并初始化存储空间;
解析:将符号引用转成直接引用;
初始化:激活类的静态变量的初始化Java代码和静态Java代码块。
(2):java的几种ClassLoader:
类装载器是用来把类(class)装载进JVM的。
JVM规范定义了两种类型的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-defined class loader)。
JVM在运行时会产生三个ClassLoader:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader。
Bootstrap是用C++编写的,我们在Java中看不到它,是null,是JVM自带的类装载器,用来装载核心类库,如java.lang.*等。AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent为Bootstrap ClassLoader。
Java提供了抽象类ClassLoader,所有用户自定义类装载器都实例化自ClassLoader的子类。 System Class Loader是一个特殊的用户自定义类装载器,由JVM的实现者提供,在编程者不特别指定装载器的情况下默认装载用户类。系统类装载器可以通过ClassLoader.getSystemClassLoader() 方法得到。
测试你所使用的JVM的ClassLoader
public class LoaderSample1 {
public static void main(String[] args) {
Class c;
ClassLoader cl;
cl = ClassLoader.getSystemClassLoader ();
System.out.println(cl);
while (cl != null) {
cl = cl.getParent();
System.out.println(cl);
}
}
}
运行结果:
sun.misc.Launcher$AppClassLoader@addbf1
sun.misc.Launcher$ExtClassLoader@42e816
null
第一行表示,系统类装载器实例化自类sun.misc.Launcher$AppClassLoader
第二行表示,系统类装载器的parent实例化自类sun.misc.Launcher$ExtClassLoader
第三行表示,系统类装载器parent的parent为bootstrap
这三个ClassLoader类之间的父子关系(不是继承关系),父子关系在ClassLoader的实现中有一个 ClassLoader类型的属性,我们可以在自己实现自定义的ClassLoader的时候初始化定义,而这三个系统定义的ClassLoader的父子关系分别是
AppClassLoader——————》(Parent)ExtClassLoader——————————》(parent)BootClassLoader(null c++实现)
动态编译
动态编译:运行时(RunningTime)确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性,即Class.forName(“ClassName”)应用。
动态编译需要注意的问题:
1、编译时输出路径问题,在相关集成开发环境下(Eclipse)测试环境与生产环境工作目录会不一样。
1.1、在eclipse下工作目录是项目目录。
1.2、生产环境下工作目录就要依情况而定了在没打成war包时会类是WEB-INF/classes
1.3、通过指定编译器参数(-d),标记编译字节码文件存储路径(见示例代码)。
2、要理解java、javac命令中的两个参数{classpath|sourcepath},加上verbose了解详细装载过程
2.1、classpath:搜索类路径(趋向与class文件),这块也可载入且编译相关java文件
2.2、sourcepath: 引用源文件路径,指定编译所关联的源文件(.java),在未打包情况下会直接将关联的源文件编译成.class
<!--EndFragment--> <!--EndFragment-->