动态加载(Dynamic Loading)是指在程序运行时加载需要的类库文件,对Java程序来说,一般情况下,一个类文件在启动时或首次初始化时会被加载到内存中,而反射则可以在运行时再决定是否要加载一个类,比如从Web上接收一个String参数作为类名,然后在JVM中加载并初始化,这就是动态加载,此动态加载通常是通过Class.forName(String)实现的,只是这个forName方法到底是什么意思呢?
我们知道一个类文件只有在被加载到内存中后才可能生成实例对象,也就是说一个对象的生成必然会经过以下两个步骤:
加载到内存中生成Class的实例对象。
通过new关键字生成实例对象。
如果我们使用的是import关键字产生的依赖包,JVM在启动时会自动加载所有依赖包下的类文件,这没有什么问题,如果要动态加载类文件,就要使用forName方法了,但问题是我们为什么要使用forName方法动态加载一个类文件呢?那是因为我们不知道生成的实例对象是什么类型(如果知道就不用动态加载),而且方法和属性都不可访问呀。问题又来了:动态加载的意义在什么地方呢?
意义在于:加载一个类即表示要初始化该类的static变量,特别是static代码块,在这里我们可以做大量的工作,比如注册自己,初始化环境等,这才是我们要重点关注的逻辑,例如如下代码:
class Utils{
//静态代码块
static{
System.out.println("Do Something");
}
}
public class Client{
public static void main(Stringargs)throws Exception{
//动态加载
Class.forName("Utils");
}
}
注意看Client类,我们并没有对Utils做任何初始化,只是通过forName方法加载了Utils类,但是却产生了一个"Do Something"的输出,这就是因为Utils类被加载后,JVM会自动初始化其static变量和static代码块,这是类加载机制所决定的。
对于此种动态加载,最经典的应用就是数据库驱动程序的加载片段,代码如下:
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/db?user=&password=";
Connection conn=DriverManager.getConnection(url);
Statement stmt=conn.createStatement();
……
在没有Hibernate和Ibatis等ORM框架的情况下,基本上每个系统都会有这么一个JDBC连接类,然后提供诸如Query、Delete等的方法,大家有没有想过为什么要加上forName这句话呢?没有任何的输出呀,要它干什么用呢?事实上非常有用,我们看一下Driver类的源码:
public class Driver extends NonRegisteringDriver implements java.sql.Driver{
//静态代码块
static{
try{
//把自己注册到DriverManager中
java.sql.DriverManager.registerDriver(new Driver());
}catch(SQLException E){
//异常处理
}
}
//构造函数
public Driver()throws SQLException{
}
}
该程序的逻辑是这样的:数据库的驱动程序已经由NonRegisteringDriver实现了,Driver类只是负责把自己注册到DriverManager中。当程序动态加载该驱动时,也就是执行到Class.forName("com.mysql.jdbc.Driver")时,Driver类会被加载到内存中,于是static代码块开始执行,也就是把自己注册到DriverManager中。
需要说明的是,forName只是把一个类加载到内存中,并不保证由此产生一个实例对象,也不会执行任何方法,之所以会初始化static代码,那是由类加载机制所决定的,而不是forName方法决定的。也就是说,如果没有static属性或static代码块,forName就只是加载类,没有任何的执行行为。
注意 forName只是加载类,并不执行任何代码。