【java】自定义ClassLoader 加载外部类和jar文件

我们写的程序,默认会编译src下的.java文件,但是如果一个类不在这个项目的src下,怎么在本项目内new出它的一个实例?这就需要自定义一个ClassLoader,让它去加载指定位置的class,因为项目中默认的ClassLoader只能加载src下文件编译后形成的class文件。

有几个概念需要强调一下:

1,ClassLoader加载的是字节码(.class),不是源文件(.java),意味着外部的java文件要编译以后才能加载。

2,两个位置的java类最好通过接口来通信。否则项目中不知道外部导入的是什么类型。

这中情况其实就发生在我们身边,web容器就是一个例子。比如tomcat就是一个socket-api编写的程序,它会加载我们用户自己编写的serlet类运行,自己写的类会实现一些接口,这些接口是tomcat提供的,很多在servlet-api。jar内。


举个例子。

在项目中新建一个与src同级的lib/classess目录,用于存放外部的class。这个class实现了util.TestUtil接口,我们从外部加载一个util.TestUtil接口的实例,然后调用其方法。

当然要在自己的项目中先创建接口:

package util;

public interface TestUtil {

	public void test();
}
不需要实现,因为我们要从外部加载一个实现,即lib下的classess。

jvm识别一个类肯定会至少按照“包名+类名”的方式去识别。我们要把自己的接口,连同包都发布出去才行,然后外部再把包导入,添加实现类。因此外部要实现接口时,接口的位置不能改变,包也不能改变。而且外部实现必须得包含接口包,否则实现的类无法编译。

此时的项目目录为:



然后就是编写实现类,例子如下:

package util;

public class TestUtilImp implements TestUtil{

	@Override
	public void test() {
		System.out.println("TestUtilImp.test() run ...");
	}

}
这里实际的过程是,新开一个项目,然后把接口包拷贝进来,写好实现类,编译,复制到classess文件夹下,因为现实中接口的提供方和实现方不同(tomcat提供servlet接口,我们来实现),但是这里作为例子,可以直接在src下编写实现,然后复制到classess下,再删除src下的实现类,最后目录为:


接着编译实现类,因为loader只接受class文件。命令如下:



刷新文件夹,多了两个class文件:


最后就是classloader创建了,使用的是URLClassLoader:

package main;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;

import util.TestUtil;



public class Main {

	public static void main(String[] args) {
          new Main().f();
	}
	
	public void f(){
		String path = System.getProperty("user.dir")+File.separator+"lib"+File.separator+"classess";
		File classpath = new File(path);
		URL[] urls = new URL[1];
		URLClassLoader loader = null;
		try {
			String repository =(new URL("file", null, classpath.getCanonicalPath() + File.separator)).toString() ;
			System.out.println(repository);
			URLStreamHandler streamHandler = null;
			urls[0] = new URL(null, repository, streamHandler); 
			loader = new URLClassLoader(urls);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Class clazz = null;
		try {
			clazz = loader.loadClass("util.TestUtilImp");
//			clazz = loader.loadClass(servletName);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		TestUtil testUtil = null;
		try {
			testUtil = (TestUtil) clazz.newInstance();
			testUtil.test();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
	}
}

运行后:


这个例子中外部类直接以classes的形式给出,与tomcat是一致的。

当然也可以从jar文件中加载。

这时就得新建项目来生成jar了,新建JarProject。

把util包复制到JarProject的src下,在util包下新建TestUtilImp.java:

package util;

public class TestUtilImp implements TestUtil{

	@Override
	public void test() {
		System.out.println("from jar:TestUtilImp.test() run ...");
	}

}

目录为:


然后export为util.jar。这次我们在ClassLoaderProject项目下新建另外一个名为“lib1”的文件夹,把jar拷贝进来。

classloader也要修改相应的路径,在Main类里面新建一个f1方法。

package main;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;

import util.TestUtil;



public class Main {

	public static void main(String[] args) {
          new Main().f();
          new Main().f1();
	}
	
	public void f(){
		String path = System.getProperty("user.dir")+File.separator+"lib"+File.separator+"classess";
		File classpath = new File(path);
		URL[] urls = new URL[1];
		URLClassLoader loader = null;
		try {
			String repository =(new URL("file", null, classpath.getCanonicalPath() + File.separator)).toString() ;
			System.out.println(repository);
			URLStreamHandler streamHandler = null;
			urls[0] = new URL(null, repository, streamHandler); 
			loader = new URLClassLoader(urls);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Class clazz = null;
		try {
			clazz = loader.loadClass("util.TestUtilImp");
//			clazz = loader.loadClass(servletName);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		TestUtil testUtil = null;
		try {
			testUtil = (TestUtil) clazz.newInstance();
			testUtil.test();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
	}
	
	public void f1(){
		String path = System.getProperty("user.dir")+File.separator+"lib1";
		File classpath = new File(path);
		URL[] urls = new URL[1];
		URLClassLoader loader = null;
		try {
			String repository =(new URL("file", null, classpath.getCanonicalPath() + File.separator + "util.jar")).toString() ;
			System.out.println(repository);
			URLStreamHandler streamHandler = null;
			urls[0] = new URL(null, repository, streamHandler); 
			loader = new URLClassLoader(urls);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Class clazz = null;
		try {
			clazz = loader.loadClass("util.TestUtilImp");
//			clazz = loader.loadClass(servletName);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		TestUtil testUtil = null;
		try {
			testUtil = (TestUtil) clazz.newInstance();
			testUtil.test();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
	}
}
运行以后:

可以看到加载了两个不同位置的外部实现类。

从这个例子大致可以看出些tomcat加载servlet的基本原理。反射+classloader。

比如在项目中我们导入了servlet-api。jar的servlet接口,然后从外部加载一个实现,只不过这时接口包是以jar形式提供的。那么外部程序编译的时候必须用-cp参数指定接口jar文件的位置才行,否则无法编译。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值