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

来源:互联网 发布:2014网络流行语口头禅 编辑:程序博客网 时间:2024/05/17 06:51

我们写的程序,默认会编译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{@Overridepublic 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 blocke.printStackTrace();}Class clazz = null;try {clazz = loader.loadClass("util.TestUtilImp");//clazz = loader.loadClass(servletName);} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}TestUtil testUtil = null;try {testUtil = (TestUtil) clazz.newInstance();testUtil.test();} catch (InstantiationException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();} }}

运行后:


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

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

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

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

package util;public class TestUtilImp implements TestUtil{@Overridepublic 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 blocke.printStackTrace();}Class clazz = null;try {clazz = loader.loadClass("util.TestUtilImp");//clazz = loader.loadClass(servletName);} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}TestUtil testUtil = null;try {testUtil = (TestUtil) clazz.newInstance();testUtil.test();} catch (InstantiationException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.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 blocke.printStackTrace();}Class clazz = null;try {clazz = loader.loadClass("util.TestUtilImp");//clazz = loader.loadClass(servletName);} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}TestUtil testUtil = null;try {testUtil = (TestUtil) clazz.newInstance();testUtil.test();} catch (InstantiationException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();} }}
运行以后:

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

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

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

0 0
原创粉丝点击