JVM 自带的类加载器只能加载默认 classpath 下的类,如果我们需要加载 应用程序之外的类文件呢?比如网络上的某个类文件,这种情况一般就要使用自 定义加载器了。自定义类加载器可以自己指定类加载路径,可以实现系统在线升级(热替换)等操作。在我们使用的
tomcat
服务器,
spring
框架,
mybatis 框 架等其内部都自己定义了类加载器。
我们如何自己定义类加载器呢?我们自己写类加载器一般需要直接或间接 继承
ClassLoader
类,然后重写相关方法,具体过程可参考后续小节内容。
3.2.1. 准备工作
在指定包中创建一个自己写的类,例如:
package pkg;
public class Search {
static {
System.out.println("search static");
}
public Search() {
System.out.println("search constructor");
}
}
说明:后续可将此类拷贝到指定目录,由自己指定的类加载器进行加载。
3.2.2. 基于 ClassLoader 创建
代码实现:
class MyClassLoader01 extends ClassLoader {
private String baseDir;
public MyClassLoader01(String baseDir) {
this.baseDir=baseDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException
{
byte[] classData = loadClassBytes(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
/**自己定义*/
private byte[] loadClassBytes(String className) {//pkg.Search
String fileName =baseDir+className.replace('.', File.separatorChar)
+ ".class";
System.out.println("fileName="+fileName);
InputStream ins=null;
try {
ins= new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = 0;
while ((length = ins.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}finally {
if(ins!=null)try{ins.close();}catch(Exception e) {}
}
}
}
说明:自己写类加载器一般不建议重写
loadClass
方法,当然不是不可以重写。
定义测试方法:假如使用自定义类加载器加载我们指定的类,要求被加载的类应与当前类不在同一个命名空间范围内,否则可能直接使用
AppClassLoader 进 行类加载。
public class TestMyClassLoader01 {
public static void main(String[] args) throws Exception{
String baseDir="F:\\WORKSPACE\\";
MyClassLoader01 classLoader = new MyClassLoader01(baseDir);
//此类不要和当前类放相同目录结构中
String pkgCls="pkg.Search";
Class<?> testClass = classLoader.loadClass(pkgCls);
Object object = testClass.newInstance();
System.out.println(object.getClass());
System.out.println(object.getClass().getClassLoader());
}
}
输出的类加载名称应该为我们自己定义的类加载器名称。
3.2.3. 基于 URLClassLoader 创建
URLClassLoader
继承
ClassLoader
,可以从指定目录,
jar 包,网络中加载指 定的类资源。
代码示例:基于此类加载器可加载网络中的
class MyClassLoader02 extends URLClassLoader {
public MyClassLoader02(URL[] urls) {
super(urls,null);// 指定父加载器为 null
}
}
编写测试类
public class TestMyClassLoader02 {
public static void main(String[] args)throws Exception {
File file=new File("f:\\workspace\\");
//File to URI
URI uri=file.toURI();
URL[] urls={uri.toURL()};
ClassLoader classLoader = new MyClassLoader02(urls);
Class<?> cls = classLoader.loadClass("pkg.Search");
System.out.println(classLoader);
Object obj = cls.newInstance();
System.out.println(obj);
}
}
3.3. 基于类加载器实现热替换
当我们的项目运行时假如需要实现在线升级(也就是常说的热替换),可以通过 自定义类加载实现,例如 自定义类加载器
class MyClassLoader03 extends ClassLoader {
private String basedir; // 需要该类加载器直接加载的类文件的基目录
private HashSet<String> loadClasses; // 需要由该类加载器直接加载的类名
public MyClassLoader03(String basedir, String[] classes)
throws IOException {
// 指定父类加载器为 null,打破双亲委派原则
super(null);
this.basedir = basedir;
loadClasses = new HashSet<String>();
customLoadClass(classes);
}
// 获取所有文件完整路径及类名,刷入缓存
private void customLoadClass(String[] classes) throws IOException {
for (String classStr : classes) {
loadDirectly(classStr);
loadClasses.add(classStr);
}
}
// 拼接文件路径及文件名
private void loadDirectly(String name) throws IOException {
StringBuilder sb = new StringBuilder(basedir);
String classname = name.replace('.', File.separatorChar) + ".class";
sb.append(File.separator).append(classname);
File classF = new File(sb.toString());
instantiateClass(name,new FileInputStream(classF),
classF.length());
}
// 读取并加载类
private void instantiateClass(String name, InputStream fin, long len)
throws IOException {
byte[] raw = new byte[(int) len];
齐雷 qilei@tedu.cn 1-14
fin.read(raw);
fin.close();
defineClass(name, raw, 0, raw.length);
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class<?> cls;
// 判断是否已加载(在名字空间中寻找指定的类是否已存在)
cls = findLoadedClass(name);
if(!this.loadClasses.contains(name) && cls == null)
cls = getSystemClassLoader().loadClass(name);
if (cls == null)throw new ClassNotFoundException(name);
if (resolve)resolveClass(cls);
return cls;
}
}
编写测试类
public class TestMyClassLoader03 {
public static void main(String[] args) throws Exception {
MyClassLoader03 loader=new MyClassLoader03("f:\\workspace\\",
new String[] {"pkg.Search"});
Class<?> cls = loader.loadClass("pkg.Search");
System.out.println(cls.getClassLoader());
Object search = cls.newInstance();
System.out.println(search);
Thread.sleep(20000);
loader=new MyClassLoader03("f:\\workspace\\",
new String[] {"pkg.Search"});
cls=loader.loadClass("pkg.Search");
System.out.println(cls.getClassLoader());
search = cls.newInstance();
System.out.println(search);
}
}
说明:程序可运行期间可以将不需要的目标类删除,然后将新的目标类放到原先 的地方,以实现热替换操作。