打造自己的类载入器

Java是一种极具动态性的语言。类似Windows的动态链接库,Java应用程序总是被编译成若干个单独的class文件,
程序执行时根据需要由java虚拟机动态载入相关的类。这种机制使编写动态的分布式应用程序成为可能:我们可以
在客户端编写自己的类载入器,而真正执行的程序却存放在本地、局域网或世界另一端的主机上。下面将介绍
如何在应用程序中实现java的动态类载入机制。
与动态类载入有关的系统类为支持动态类载入机制,系统类组java.lang中提供了两个类:Class类和ClassLoader
类。

Class类
在java虚拟机中,每一个类或接口都是由Class类来操纵的,它不能被显式的的实例化。必须使用其他方
法来获取Class类的对象。动态类载入机制的关键在于如何获得指定类的Class对象。相关方法主要有:
public static Class forName(String className)
这是一个静态方法,它获取指定名字的类的Class类型对象,类名可以是像"sun.applet.Applet"这样的字符串,但
不能带有路径或网络地址等信息。这是从本地系统中动态载入类的最方便的办法。
public Object newInstance()
这是最重要的一个方法,它建立由Class类型对象描述的指定类的实例。
ClassLoader类
这是一个抽象类,如果打算运用它,必须继承它并重写它的loadClass()方法。其主要方法有:
protected ClassLoader()
这是一个构建元,可以用它建议一个ClassLoader类的实例。注意:继承这个类的类必须重写这个方法,而不能使
用缺省的构建元。
protected abstract Class loadClass(String name, boolean resolve)
载入指定的类数据,建立Class类型的对象并根据需要解析它。这是一个抽象方法。大家必须在自己的子类中
重写这个方法,重写的规则可以参考第三部分的例子。
protected final Class defineClass(byte data[], int offset, int length)
将字节数组中的数据定义为Class类型的对象,字节数组的格式由虚拟机规定。
protected final Class findSystemClass(String name)
根据指定的类名载入类,它会自动在当前目录和环境变量classpath指定的路径中寻找,如果找不到,则会抛出
ClassNotFouondException异常。
protected final void resolveClass(Class c)
通过载入与指定的类相关的所有类来解析这个类,这必须在类使用之前完成。
扩充ClassLoader类以实现动态类载入
理解动态类载入机制最好的办法是通过例子,下面这个完整的例子由四个类组成,分别解释如下。
1.MyClassLoader类是ClassLoader类的子类,它重写了loadClass方法,实现了将网络上用URL地址指定的类动态载


入,取得它的Class类型对象。读者可根据自己载入类的具体方式改写下面代码。

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Hashtable;

@SuppressWarnings("all")
public class MyClassLoader extends ClassLoader {
	private Hashtable loadedClasses;

	// 必须定义
	public MyClassLoader() {
		loadedClasses = new Hashtable();
	}

	public synchronized Class<?> loadClass(String className, boolean resolve) throws 

ClassNotFoundException {
		Class newClass;
		byte[] classData; // 存放类的字节数组
		// 检查要载入的类数据是否已经被保存在哈希表中
		newClass = (Class) loadedClasses.get(className);
		// 如果类数据已经存在且resolve值为true,则解析它
		if (newClass != null) {
			if (resolve) resolveClass(newClass);// 链接类
			return newClass;
		}
		/*
		 * 首先必须从本地类组中载入指定类,因为虚拟机将这个类载入后,在
		 * 解析和执行它时所用到的任何其他类等,均不再使用虚拟机的类载入器
		 * 而是调用我们自制的类载入器来加载
		 */
		try {
			newClass = findSystemClass(className);
			return newClass;
		} catch (ClassNotFoundException e) {
			System.out.println(className + " is not a system class");
		}
		// 如果不是系统类,则试图从网络中指定的URL地址载入类
		try {
			// 用自定义方法载入数据,存放于字节数组classData中
			classData = getClassData(className);
			// 用字节数组包含的数据及案例一个class类的对象
			newClass = defineClass(classData, 0, classData.length);
			if (newClass == null) throw new ClassNotFoundException(className);
		} catch (Exception e) {
			throw new ClassNotFoundException(className);
		}
		// 如果类正确载入,则将类保存在哈希表中
		loadedClasses.put(className, newClass);
		if (resolve) {
			resolveClass(newClass);
		}
		return newClass;
	}

	protected byte[] getClassData(String className) throws IOException {
		byte[] data;
		int length;
		try {
			// 从网络中采用URL类的方式载入指定URL地址的类数据
			URL url = new URL(className.endsWith(".class") ? className : className + 

".class");
			URLConnection conn = url.openConnection();
			InputStream is = conn.getInputStream();
			length = conn.getContentLength();// 返回 content-length 头字段的值
			data = new byte[length];
			is.read(data);
			is.close();
			return data;
		} catch (Exception e) {
			throw new IOException(className);
		}
	}
}

2.由于Java是强类型检查语言,通过网络载入的类被实例化后只是一个Object类型的对象,虚拟机并不知道它包含
哪些方法,应从哪个方法开始执行。因此,可以被动态载入的类必须继承某一个抽象类或实现某一个接口,因为父
类只能有一个,所以通常用实现特定接口的办法。下面的代码定义了一个接口类Share和它的方法start().

public interface Share {
	public void start(String[] args);
}

3.TestClassLoader类通过使用MyClassLoader类的loadClass()方法,将指定URL地址的类载入并在本地系统执行它
,从而实现了类的动态载入(在执行被载入类的方法前一定要将它进行强制类型转换)

public class TestClassLoader {
	@SuppressWarnings("all")
	public static void main(String[] args) {
		MyClassLoader loader = new MyClassLoader();
		Class cc;
		Object oo;
		String ss = "http://localhost:8888/Tested.class";
		if (args.length != 0) ss = args[0];
		try {
			System.out.println("Loading class " + ss + " ...");
			// 使用重写的方法loadClass载入类数据
			cc = loader.loadClass(ss);
			System.out.println("Create instance...");
			oo = cc.newInstance();
			System.out.println("Call start method");
			// 强制类型转换后执行载入类中的方法
			((Share) oo).start(args);
		} catch (Exception e) {
			System.out.println("Caught exception: " + e);
		}
	}
}

4.Tested类很简单,可以将它将在任何WEB服务器圣桑,但应注意能动态载入且被执行的类,一定要实现预先定义
的接口中的方法,下面的例子实现了接口Share中的start方法。

public class Tested implements Share {
	@Override
	public void start(String[] args) {
		System.out.println("动态载入类成功");
		for (String arg : args) {
			System.out.println(arg);
		}
	}
}

将Tested放到tomcat根目录下运行:


动态类载入机制的几点应用
1.开发分布式应用。这对开发远程的客户端应用程序最有用,客户端仅需要安装一些基本的系统和一个能实现动态
类载入机制的类;需要本地系统不存在的功能时,仅需要从网络动态载入并执行相应类即可获得特定功能。因为客
户端使用的总是软件的最新版本,所以不再有软件的升级和维护问题,即实现了所谓的“零管理”模式。
2.对.class文件加密。由于Java的字节码容易被反编译,大部分开发java应用程序的公司均担心自己的成果被别人
不劳而获。其实可以将类文件进行适当的加密处理,执行时使用自己的类载入器进行相应的解密,就可以解决这个
问题。
3.使第三方开发者易于扩展应用。从前面可知,所有可以被类载入器动态载入并执行的类,必须继承定义的类或实
现定义的接口,这样,可以制定一些规则,使其他开发者不必了解应用程序也可以扩充功能。
当然,有利必有弊,在网络中使用动态类载入的主要缺陷在于安全性,很可能会载入恶意代码,这个问题要靠java
的安全管理器和适当的加密算法来解决。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值