我们写的程序,默认会编译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文件的位置才行,否则无法编译。