Java Reflection(十二):动态类加载与重载

原文地址作者: Jakob Jenkov 译者:叶文海(yewenhai@gmail.com)
内容索引
类加载器
类加载体系
类加载
动态类加载
动态类重载
自定义类重载
类加载/重载示例
Java允许你在运行期动态加载和重载类,但是这个功能并没有像人们希望的那么简单直接。这篇文章将阐述在Java中如何加载以及重载类。
你可能会质疑为什么Java动态类加载特性是Java反射机制的一部分而不是Java核心平台的一部分。不管怎样,这篇文章被放到了Java反射系列里面而且也没有更好的系列来包含它了。

类加载器

所有Java应用中的类都是被java.lang.ClassLoader类的一系列子类加载的。因此要想动态加载类的话也必须使用java.lang.ClassLoader的子类。

一个类一旦被加载时,这个类引用的所有类也同时会被加载。类加载过程是一个递归的模式,所有相关的类都会被加载。但并不一定是一个应用里面所有类都会被加载,与这个被加载类的引用链无关的类是不会被加载的,直到有引用关系的时候它们才会被加载。

类加载体系

在Java中类加载是一个有序的体系。当你新创建一个标准的Java类加载器时你必须提供它的父加载器。当一个类加载器被调用来加载一个类的时候,首先会调用这个加载器的父加载器来加载。如果父加载器无法找到这个类,这时候这个加载器才会尝试去加载这个类。

类加载

类加载器加载类的顺序如下:
1、检查这个类是否已经被加载。
2、如果没有被加载,则首先调用父加载器加载。
3、如果父加载器不能加载这个类,则尝试加载这个类。

当你实现一个有重载类功能的类加载器,它的顺序与上述会有些不同。类重载不会请求的他的父加载器来进行加载。在后面的段落会进行讲解。

动态类加载

动态加载一个类十分简单。你要做的就是获取一个类加载器然后调用它的loadClass()方法。下面是个例子:

public class MainClass {

  public static void main(String[] args){

    ClassLoader classLoader = MainClass.class.getClassLoader();

    try {
        Class aClass = classLoader.loadClass("com.jenkov.MyClass");
        System.out.println("aClass.getName() = " + aClass.getName());
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

}

动态类重载

动态类重载有一点复杂。Java内置的类加载器在加载一个类之前会检查它是否已经被加载。因此重载一个类是无法使用Java内置的类加载器的,如果想要重载一个类你需要手动继承ClassLoader。

在你定制ClassLoader的子类之后,你还有一些事需要做。所有被加载的类都需要被链接。这个过程是通过ClassLoader.resolve()方法来完成的。由于这是一个final方法,因此这个方法在ClassLoader的子类中是无法被重写的。resolve()方法是不会允许给定的ClassLoader实例链接一个类两次。所以每当你想要重载一个类的时候你都需要使用一个新的ClassLoader的子类。你在设计类重载功能的时候这是必要的条件。

自定义类重载

在前面已经说过你不能使用已经加载过类的类加载器来重载一个类。因此你需要其他的ClassLoader实例来重载这个类。但是这又带来了一些新的挑战。

所有被加载到Java应用中的类都以类的全名(包名 + 类名)作为一个唯一标识来让ClassLoader实例来加载。这意味着,类MyObject被类加载器A加载,如果类加载器B又加载了MyObject类,那么两个加载器加载出来的类是不同的。看看下面的代码:

MyObject object = (MyObject)
    myClassReloadingFactory.newInstance("com.jenkov.MyObject");

MyObject类在上面那段代码中被引用,它的变量名是object。这就导致了MyObject这个类会被这段代码所在类的类加载器所加载。

如果myClassReloadingFactory工厂对象使用不同的类加载器重载MyObject类,你不能把重载的MyObject类的实例转换(cast)到类型为MyObject的对象变量。一旦MyObject类分别被两个类加载器加载,那么它就会被认为是两个不同的类,尽管它们的类的全名是完全一样的。你如果尝试把这两个类的实例进行转换就会报ClassCastException。
你可以解决这个限制,不过你需要从以下两个方面修改你的代码:
1、标记这个变量类型为一个接口,然后只重载这个接口的实现类。
2、标记这个变量类型为一个超类,然后只重载这个超类的子类。

请看下面这两个例子:

MyObjectInterface object = (MyObjectInterface)
    myClassReloadingFactory.newInstance("com.jenkov.MyObject");
MyObjectSuperclass object = (MyObjectSuperclass)
    myClassReloadingFactory.newInstance("com.jenkov.MyObject");

只要保证变量的类型是超类或者接口,这两个方法就可以正常运行,当它们的子类或是实现类被重载的时候超类跟接口是不会被重载的。

为了保证这种方式可以运行你需要手动实现类加载器然后使得这些接口或超类可以被它的父加载器加载。当你的类加载器加载MyObject类时,超类MyObjectSuperclass或者接口MyObjectSuperclass也会被加载,因为它们是MyObject的依赖。你的类加载器必须要代理这些类的加载到同一个类加载器,这个类加载器加载这个包括接口或者超类的类。

类加载/重载示例

光说不练假把式。让我们看看一个简单的例子。下面这个例子是一个类加载器的子类。注意在这个类不想被重载的情况下它是如何把对一个类的加载代理到它的父加载器上的。如果一个类被它的父加载器加载,这个类以后将不能被重载。记住,一个类只能被同一个ClassLoader实例加载一次。
就像我之前说的那样,这仅仅是一个简单的例子,通过这个例子会向你展示类加载器的基本行为。这并不是一个可以让你直接用于设计你项目中类加载器的模板。你自己设计的类加载器应该不仅仅只有一个,如果你想用来重载类的话你可能会设计很多加载器。并且你也不会像下面这样将需要加载的类的路径硬编码(hardcore)到你的代码中。

public class MyClassLoader extends ClassLoader{

    public MyClassLoader(ClassLoader parent) {
        super(parent);
    }

    public Class loadClass(String name) throws ClassNotFoundException {
        if(!"reflection.MyObject".equals(name))
                return super.loadClass(name);

        try {
            String url = "file:C:/data/projects/tutorials/web/WEB-INF/" +
                            "classes/reflection/MyObject.class";
            URL myUrl = new URL(url);
            URLConnection connection = myUrl.openConnection();
            InputStream input = connection.getInputStream();
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int data = input.read();

            while(data != -1){
                buffer.write(data);
                data = input.read();
            }

            input.close();

            byte[] classData = buffer.toByteArray();

            return defineClass("reflection.MyObject",
                    classData, 0, classData.length);

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

}

下面是使用MyClassLoader的例子:

public static void main(String[] args) throws
    ClassNotFoundException,
    IllegalAccessException,
    InstantiationException {

    ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader();
    MyClassLoader classLoader = new MyClassLoader(parentClassLoader);
    Class myObjectClass = classLoader.loadClass("reflection.MyObject");

    AnInterface2       object1 =
            (AnInterface2) myObjectClass.newInstance();

    MyObjectSuperClass object2 =
            (MyObjectSuperClass) myObjectClass.newInstance();

    //create new class loader so classes can be reloaded.
    classLoader = new MyClassLoader(parentClassLoader);
    myObjectClass = classLoader.loadClass("reflection.MyObject");

    object1 = (AnInterface2)       myObjectClass.newInstance();
    object2 = (MyObjectSuperClass) myObjectClass.newInstance();

}

下面这个就是被加载的reflection.MyObject类。注意它既继承了一个超类并且也实现了一个接口。这样做仅仅是为了通过例子演示这个特性。在你自定义的情况下你可能仅会实现一个类或者继承一两个接口。

public class MyObject extends MyObjectSuperClass implements AnInterface2{
    //... body of class ... override superclass methods
    //    or implement interface methods
}
时间: 2024-10-27 15:29:44

Java Reflection(十二):动态类加载与重载的相关文章

仿酷狗音乐播放器开发日志二十二 动态调色板控件第二版(性能大幅提升附源码)

转载请说明原出处,谢谢~~         在上次写的博客<仿酷狗音乐播放器开发日志二十一 开发动态调色板控件(附源码)>发布后,我在群里和网友讨论这个控件的性能和优 缺点,发现了他很多不足,还有很多提升空间,之后我简单的修改了代码提升了控件的响应速度.        昨天网友"风之羽翼"联系到我,他在我原来写的控件基础上,对绘图的算法进行重写,大幅提升了控件的绘制速度,算法也精准了很多,最终的控 件效果要比原酷狗更加流畅,于是今天把新的控件代码上传,供感兴趣的朋友使用.在

Java Reflection(十):数组

原文地址作者: Jakob Jenkov 译者:叶文海(yewenhai@gmail.com) 内容索引java.lang.reflect.Array创建一个数组访问一个数组获取数组的Class对象获取数组的成员类型 利用反射机制来处理数组会有点棘手.尤其是当你想要获得一个数组的Class对象,比如int[]等等.本节会讨论通过反射机制创建数组和如何获取数组的Class对象. 注意:在阅读Eyal Lupu的博客文章"Two Side Notes About Arrays and Reflect

JAVA之旅(十二)——Thread,run和start的特点,线程运行状态,获取线程对象和名称,多线程实例演示,使用Runnable接口

JAVA之旅(十二)--Thread,run和start的特点,线程运行状态,获取线程对象和名称,多线程实例演示,使用Runnable接口 开始挑战一些难度了,线程和I/O方面的操作了,继续坚持 一.Thread 如何在自定义的代码中,自定义一个线程呢? 我们查看API文档,我们要启动一个线程,先实现一个子类, package com.lgl.hellojava; public class MyThread extends Thread { @Override public void run()

JAVA之旅(二十二)——Map概述,子类对象特点,共性方法,keySet,entrySet,Map小练习

JAVA之旅(二十二)--Map概述,子类对象特点,共性方法,keySet,entrySet,Map小练习 继续坚持下去吧,各位骚年们! 事实上,我们的数据结构,只剩下这个Map的知识点了,平时开发中,也是能看到他的,所以还是非常值得去学习的一个知识点的,我们直接开车了 一.Map概述 泛型< k,v> 键值对,映射关系 基本特点 该集合存储键值对,是一对一对往里存,而且要保证键的唯一性 1.添加 put(key ,values) putAll() 2.删除 clear() remove(ob

JAVA之旅(三十二)——JAVA网络请求,IP地址,TCP/UDP通讯协议概述,Socket,UDP传输,多线程UDP聊天应用

JAVA之旅(三十二)--JAVA网络请求,IP地址,TCP/UDP通讯协议概述,Socket,UDP传输,多线程UDP聊天应用 GUI写到一半电脑系统挂了,也就算了,最多GUI还有一个提示框和实例,我们暂时不讲了,我们直接来重点吧,关于JAVA的网络请求是怎么实现的?当然是HTTP协议,但是不可否认,他的概念和思想都是我们必须去涉及的,包括后面的tcp和socket等,好吧,我们开车吧! 一.JAVA网络请求概述 关于JAVA的网络请求,我们大致的可以分为以下几个分类 网络模式 OSI TCP

深入解析Java中的Class Loader类加载器_java

类加载的过程类加载器的主要工作就是把类文件加载到JVM中.如下图所示,其过程分为三步: 1.加载:定位要加载的类文件,并将其字节流装载到JVM中: 2.链接:给要加载的类分配最基本的内存结构保存其信息,比如属性,方法以及引用的类.在该阶段,该类还处于不可用状态: (1)验证:对加载的字节流进行验证,比如格式上的,安全方面的: (2)内存分配:为该类准备内存空间来表示其属性,方法以及引用的类: (3)解析:加载该类所引用的其它类,比如父类,实现的接口等. 3.初始化:对类变量进行赋值. 类加载器的

JAVA学习(七):方法重载与方法重写、this关键字和super关键字

方法重载与方法重写.this关键字和super关键字 1.方法重载 重载能够使具有相同名称但不同数目和类型参数的类传递给方法. 注: 一是重载方法的参数列表必须与被重载的方法不同,并且这种不同必须足以清楚地确定要调用哪一个方法: 二是重载方法的返回值类型可以与被重载的方法相同,也可以不同,但是只有返回值类型不同不能表示为重载. 例如,最常用的println()方法在JDK的java.io.PrintStream中定义了十几种形式的重载,常用格式如下: public void println(in

第十二章 委托[《.net框架程序设计》读书笔记]

.net框架|笔记|程序|设计 第十二章 委托 一. 委托的使用 静态委托和实例委托,使用方法类似,这里给出一个使用可变参数委托的例子: using System; public class DelCls { public delegate void DelDef(params string[] strParams); public static void CallDel(DelDef dd) { if(dd != null) //请务必在此处进行判断,这是个好习惯 { dd("Hello&qu

求一java语言写的动态加载树!(从数据库读取节点的那种)

问题描述 最近想研究研究java动态树,extjs的倒是看过,不会改,因为对于extjs不熟.想自己封装一个,运用ssh框架,sqlserver2005数据库,jQuery等.哪位能指点一下,给点意见. 解决方案 解决方案二:思路:递归算法~~~还算简单解决方案三:所答非所问,继续求详细指点.解决方案四:这个对于展示的话就是用递归而在数据库设计的时候,则在每条记录存一个他上级的id,页面的话,再加点样式.可能就是页面比较麻烦.后台没有什么东西.用js+css写也行,就是很麻烦.解决方案五:简单的