当前位置: 首页 > news >正文

Java反射-动态类加载和重新加载

Java中可以在运行时加载和重新加载类,虽然并不像我们想像中那么简单。本文将解释何时、怎样在Java中加载、重新加载类。
你可以争论动态加载类是Java反射的一部分还是Java核心的一部分。不管怎样,我把它放在了Java反射中,因为没有更好的地方放置它。

类加载器

Java程序的所有类都是使用 java.lang.ClassLoader的一些子类加载的。因此,动态加载类也必须使用 java.lang.ClassLoader的子类。
当一个类加载,它所引用的类也会被加载。类加载模式是递归加载的,直到所有需要的类加载完毕。这可能并不是应用程序的所有类。未被引用的类在引用前不会被加载。

类加载层级结构

类加载在Java中被组织成层级。当你创建一个独立的ClassLoader,你必须提供一个父级ClassLoader。如果ClassLoader被请求加载一个类,它会请求它的父级ClassLoader去加载它。如果父级类加载器找不到这个类,子类加载器会尝试自加载。

类加载

类加载器加载类的步骤如下:

  1. 检查该类是否已被加载
  2. 如类未加载,请求父类加载器加载它
  3. 如父类加载器不能加载该类,尝试使用当前类加载器加载它

当你实现一个能够重载类的类加载器时,你需要从这个序列中偏离一点。不应请求父类加载程序加载要重装的类。稍后再谈。

动态类加载

动态加载类非常简单。所有你需要做的是获得一个ClassLoader并调用它的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子类。
即使使用类加载器的自定义子类,也会遇到挑战。所有已被加载的类都需要被链接。这个方法是final的,因此不能被你的ClassLoader子类重载。resolve()方法不允许ClassLoader实例链接一个类2次。因此,每当你需要重新加载类时,你必须重新创建一个ClassLoader类的实例。这不是不可能的,但必须知道何时设计类重新加载。

类重载代码设计

如上文述,不能使用加载指定类的ClassLoader重新加载这个类。因此,必须使用不同的ClassLoader加载这个类。但是,这会带来新的问题。
Java程序中加载的每一个类都以其全限定名(包名+类名)标识,并且由ClassLoader实例加载。这意味着,类MyObject由类加载器A加载,是和由类加载器B加载的同一个类MyObject不相同。模拟代码如下:

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

注意,类MyObject在代码中是如何引用的,是作为object类型的变量。这导致MyObject类被已加载过这个类的驻留代码的类加载器加载。
如果myClassReloadingFactory对象工厂使用与驻留代码不同的类加载器加载MyObject,你不能强制转换重新加载的Object类型的变量MyObjectMyObject类型。因为这两个MyObject由不同的类加载器加载,他们被视为不同的类,尽管他们拥有相同的全限定名。尝试强转一个object的类为另一个类的引用将抛出ClassCastException
有可能绕过这个限制,但是你必须用两种方式来改变你的代码:

  1. 使用接口作为变量类型,并且只重新加载实现类
  2. 使用超类作为变量类型,并且只重新加载子类

这里是示例代码:

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

如果变量类型是接口或超类,上面的代码都会正常运行,接口或超类在重新加载实现或子类时不会被重新加载。
为了上面代码的正常运行,你当然需要实现自己的类加载器,让接口或超类由其父类加载。当你的类加载器被请求加载MyObject时,它也会被请求加载MyObjectInterface接口或者MyObjectSuperclass类,因为它们被MyObject类在内部引用。你的类加载器必须把类加载委派给相同的类加载器,即加载了接口或超类的类加载器。

类加载器加载/重新加载示例

上文包含了很多内容。让我们看一下简单的示例。下面是一个简单的ClassLoader子类。注意它如何将类加载委托给它的父类,除了它想要重装的一个类之外。如果类加载被委派给了它的父类,它以后将不能被重新加载。记住,一个类只能被同一个ClassLoader实例加载。
如前所述,这只是一个示例,它显示了类加载器的行为的基本知识。这并不是一个你的类加载器的生产就绪的模板。你的类加载器可能并不仅限于一个类,可能是一个你想要重新加载的类的集合。此外,你也不能硬编码class path。

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
}

相关文章:

  • 女博士被程序员嘲笑:代码能力太差,不知道怎么招进来的
  • ES学习笔记(10)--ES6中的函数和数组补漏
  • WordCount2.0
  • 用阿里云函数计算部署thinkphp5.1
  • 01什么是面向对象,面向对象的基本操作
  • day-19 django2
  • Go 语言编译器的 //go: 详解
  • 《2019年世界发展报告》发布,阿里巴巴助力小企业发展创造就业
  • @Service注解让spring找到你的Service bean
  • python 3.5 解决csv 读入中的'utf-8' codec can't decode办法
  • 2018 JVM 生态报告:79% 的 Java 开发者使用 Java 8
  • 微信小程序 - 使用七牛云 API 截取第 n 秒图像为封面图
  • 《netty入门与实战》笔记-03:数据传输载体 ByteBuf 介绍
  • 【转】使用 lsof 查找打开的文件
  • 实验报告五201521460014 综合渗透
  • Elasticsearch 参考指南(升级前重新索引)
  • ES6简单总结(搭配简单的讲解和小案例)
  • Making An Indicator With Pure CSS
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • Redis 懒删除(lazy free)简史
  • Sass Day-01
  • vuex 笔记整理
  • vuex 学习笔记 01
  • vue总结
  • 成为一名优秀的Developer的书单
  • 纯 javascript 半自动式下滑一定高度,导航栏固定
  • 给初学者:JavaScript 中数组操作注意点
  • 猴子数据域名防封接口降低小说被封的风险
  • 让你成为前端,后端或全栈开发程序员的进阶指南,一门学到老的技术
  • 跳前端坑前,先看看这个!!
  • 通过来模仿稀土掘金个人页面的布局来学习使用CoordinatorLayout
  • 微信小程序:实现悬浮返回和分享按钮
  • 一加3T解锁OEM、刷入TWRP、第三方ROM以及ROOT
  • 用 vue 组件自定义 v-model, 实现一个 Tab 组件。
  • Android开发者必备:推荐一款助力开发的开源APP
  • 昨天1024程序员节,我故意写了个死循环~
  • !$boo在php中什么意思,php前戏
  • # Apache SeaTunnel 究竟是什么?
  • #考研#计算机文化知识1(局域网及网络互联)
  • (黑客游戏)HackTheGame1.21 过关攻略
  • (算法)N皇后问题
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • .mkp勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET Compact Framework 多线程环境下的UI异步刷新
  • .NET Framework 3.5中序列化成JSON数据及JSON数据的反序列化,以及jQuery的调用JSON
  • .net 打包工具_pyinstaller打包的exe太大?你需要站在巨人的肩膀上-VC++才是王道
  • .net 开发怎么实现前后端分离_前后端分离:分离式开发和一体式发布
  • .NET 事件模型教程(二)
  • .NET/ASP.NETMVC 深入剖析 Model元数据、HtmlHelper、自定义模板、模板的装饰者模式(二)...
  • .NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
  • /var/lib/dpkg/lock 锁定问题
  • @NoArgsConstructor和@AllArgsConstructor,@Builder
  • [Android Pro] android 混淆文件project.properties和proguard-project.txt
  • [Asp.net mvc]国际化
  • [BUAA软工]第一次博客作业---阅读《构建之法》