转自 http://xtu-tja-163-com.iteye.com/blog/770449
上篇文章主要分析了类加载器的种类以及加载规则。下面,将主要分析下与加载器有关的java程序动态扩展。
这篇文章只挑了类加载器的一个问题来分析,如果要想了解类加载器的一些常见问题以及自定义类加载要注意的一些细节,可以参考我的上篇文章:
http://xtu-tja-163-com.iteye.com/blog/770401
Java的连接模型允许用户运行时扩展应用程序,既可以通过当前虚拟机中预定义的加载器加载编译时已知的类或者接口,又允许用户自行定义类装载器,在运行时动态扩展用户的程序,这就是Java程序动态扩展(运行时加载类)。java动态扩展有两种方式:
1.采用Class.forName方法。动态扩展Java程序最直接的方式就是使用Class的forName()方法,它有两中重载形式:
1)Class.forName(String className)
2)Class.forName(String className,boolean initialize)
它们之间的关系是:Class.forName(string className) == Class.forName(string className,true)。initialize参数用来指定加载className对应的类时,是否还进行初始化。有些场景下,需要将initialize设置为true来强制加载,同时完成初始化,例如典型的就是利用DriverManager进行JDBC驱动程序类注册的问题,因为每一个JDBC驱动程序类的静态初始化方法都用DriverManager注册驱动程序,这样才能被应用程序使用,这就要求驱动程序类必须被初始化,而不单单被加载.
2.采用类加载器的loadClass方法来动态扩展java应用程序。它的加载过程已经在上面分析过了,采用的是委托双亲加载机制。这里注意,它虽然也有个loadClass(String className,boolean resolve)方法,resolve是加载指定名称的类以后,是否进行连接的标志,但是如果resolve为false,也不能保证被装载的类没被连接(例如指定的类在这之前已经被装载过了)。还有,用loadClass加载的类一般不进行初始化。
最后,建议使用Class.forName()方法,因为,它在加载指定的类时,一定都会对加载的类进行初始化,而初始化在一些操作中是非常重要的,如果JDBC驱动的初始化。注意:类的初始化不等于new一个对象,如果对类的初始化不熟悉,可以参考我以前的一篇文章:
http://xtu-tja-163-com.iteye.com/admin/blogs/769226
在类加载器中,比较容易弄错的一个问题就是:由不同的类加载器加载的指定类型还是相同的类型吗?
答案: 在Java中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间.我们可以用两个自定义类加载器去加载某自定义类型(注意,不要将自定义类型的字节码放置到系统路径或者扩展路径中,否则会被系统类加载器或扩展类加载器抢先加载),然后用获取到的两个Class实例进行java.lang.Object.equals(…)判断,将会得到不相等的结果。
首先,在eclipse中部署好下面代码块:
- package com.newTest.test3;
- public class TestClass {
- static {
- System.out.println("我被加载了,加载我的加载器是:"+TestClass.class.getClassLoader());
- }
- public void print() {
- System.out.println("我被加载了,加载我的加载器是:"+TestClass.class.getClassLoader());
- }
- }
- /**
- * 自定义加载器工厂
- */
- public class ClassLoaderFactory {
- private static ClassLoaderFactory instatnce = new ClassLoaderFactory();
- static String s ;
- public static ClassLoaderFactory getInstance() {
- return instatnce;
- }
- //根据制定的路径生成不同的加载器
- public ClassLoader getClassLoader(String path) {
- File file = new File(path);
- if(file.exists()) {
- file.mkdirs();
- }
- URL url = null;
- try {
- String urlPath = file.getCanonicalPath()+File.separator;
- /*
- * 路径前一定要有'/',不然不对,我也不知道原因,知道的说下,谢谢
- */
- urlPath = urlPath.charAt(1)=='/'?urlPath:'/'+urlPath;
- String reposity = new URL("file",null,file.getCanonicalPath()+File.separator).toString();
- URLStreamHandler handler = null;
- url = new URL(null,reposity,handler);
- } catch (Exception e) {
- e.printStackTrace();
- //记录日志 抛出异常
- }
- //生成类加载器,记住:如果你要调试,一定要把它的父加载器设置为null
- return new URLClassLoader(new URL[]{url},null);
- }
- @SuppressWarnings({ "unchecked" })
- public static void main(String[] args) throws Exception {
- String path1 = "D:/classReposity";
- String path2 = "D:/classReposityCopy";
- //---------生成两个自定义类加载器--------------------
- ClassLoader loader1 = ClassLoaderFactory.getInstance().getClassLoader(path1);
- ClassLoader loader2 = ClassLoaderFactory.getInstance().getClassLoader(path2);
- //-----------------采用同一个加载器加载同一个类型实例------------
- Class c = loader1.loadClass("com.newTest.test3.TestClass");
- Class c2 = loader1.loadClass("com.newTest.test3.TestClass");
- //-----------------采用另外一个加载器加载同一个类型实例----------------
- //注意,采用loadClass加载的类时,可能不会被初始化,也就是说,暂时还不会执行TestClass中静态块内容
- Class c3 = loader2.loadClass("com.newTest.test3.TestClass");
- System.out.println("使用同一个类加载器加载的类实例,是否相等?结果是:"+c.equals(c2));
- System.out.println("使用不同的类加载器加载的类实例,是否相等?结果是:"+c.equals(c3));
- //执行加载出的c的一个实例的print方法
- c.getMethod("print", new Class[]{}).invoke(c.newInstance(), new Object[]{});
- }
- }
经过编译后,把生成的Test.class文件分别放在path1和path2目录下,注意:如 果有包,要放在path1或path2的包目录下,如:path1/com/netTest/test3/下。
最后,执行上面程序,得出如下结果:
使用同一个类加载器加载的类实例,是否相等?结果是:true
使用不同的类加载器加载的类实例,是否相等?结果是:false
我被加载了,加载我的加载器是:java.net.URLClassLoader@c17164
我被加载了,加载我的加载器是:java.net.URLClassLoader@c17164