自定义ClassLoader
个人学习笔记,如有错误欢迎指正。。
Java ClassLoader用于加载Class文件生成Class对象。
jvm 默认启动的ClassLoader:
1.Bootstrap 引导类加载器 java_home/jre/lib下的固定的几个jar包,如rt.jar 等
2.sun.misc.Launcher$ExtClassLoader 扩展类加载器 加载 java_home/jre/lib/ext/下(java.ext.dirs参数指定目录下)的所有jar
3.sun.misc.Launcher$AppClassLoader 系统类加载器 加载ClassPath下的所有JAR
类图:
sun.misc.Launcher$AppClassLoader 和sun.misc.Launcher$ExtClassLoader 都扩展了 java.net.URLClassLoader,并使用java.net.URLClassLoader的loadClass方法来加载类( loadClass属于公共方法,并未被子类覆盖)。
看一下loadClass源码:
//name参数是类的全名 如"java.lang.StringBuffer"
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 首先从缓存中查找类是否加载
Class c = findLoadedClass(name);
if (c == null) {//缓存中没有
try {
if (parent != null) {//有父 类加载器则从父类加载器中加载
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);//没有父类加载器,从引导类加载器中加载。
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);//没有找到则调用findClass 加载,这个方法是空的,可以由子类覆写这个方法,实现类的加载
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
程序默认当前ClassLoader 是sun.misc.Launcher$AppClassLoader ,它负责从ClassPath中加载程序中需要类,它的父ClassLoader是sun.misc.Launcher$ExtClassLoader
public class TestDefaultClassLoader {
public static void main(String args []){
TestDefaultClassLoader testDefaultClassLoader = new TestDefaultClassLoader();
System.out.println(testDefaultClassLoader.getClass().getClassLoader());
//输出:aaa@qq.com
System.out.println(testDefaultClassLoader.getClass().getClassLoader().getParent());
//输出:aaa@qq.com
}
}
类的加载顺序(如果缓存中没有):
1.首先由Bootstrap 引导类加载器从java_home/jre/lib下加载,没有找到进2
2.ExtClassLoader 扩展类加载器从java_home/jre/lib/ext/下加载,没有找到进3
3.AppClassLoader 系统类加载器 从ClassPath路径中加载,没有找到报错ClassNotFoundException
URLClassLoader类通过覆盖findClass方法,实现了如果在上述路径不能加载类时,通过指定的URL加载:
示例 :
1.在另一eclipse工程上新建类 ProductA和B和C,并打jar包 product.jar放到放到F盘下(非ClassPath下):
public class ProductA {
static {
System.out.println("ProductA version 1.0, static code ran");
}
public void printClassLoader(){
System.out.println(" this is ProductA ,version 1.0. classLoader="+this.getClass().getClassLoader());
}
public static void staticPrint(){
System.out.println(" this is ProductA ,version 1.0. static method called ");
}
public void CallB(){
B b = new B();
b.print();
}
}
public class B {
public void print(){
System.out.println("B.classLoader="+this.getClass().getClassLoader());
}
}
public class C {
public void print(){
System.out.println("C.classLoader="+this.getClass().getClassLoader());
}
}
2.当前eclipse 项目中新建测试 URLClassLoader类:
public class TestURLClassLoader {
public static void main(String args []){
try{
File filePath = new File("F:/product.jar");
URL urs [] = new URL[] {filePath.toURI().toURL()};//指定类所在URL
URLClassLoader urlClassLoader = new URLClassLoader(urs);
// 由于类ProductA类所在的jar包 product.jar 未在当前类路径下,因此不能直接NEW (编译器会通不过)或 Class.forName(当前类路径无该类报错:ClassNotFoundException)
// 可以通过指定的类加载器loadClass
Class productAClass = urlClassLoader.loadClass("test.temp.ProductA");//输出"ProductA version 1.0, static code ran",说明loadClass 时 类的静态代码块被执行了
Method method = productAClass.getMethod("staticPrint", null);
method.invoke(null, null);//调用静态方法,不需要对象实例 输出 " this is ProductA ,version 1.0. static method called "
Object obj = productAClass.newInstance();//实例化类
method = productAClass.getMethod("printClassLoader", null);
method.invoke(obj, null);//调用非静态方法,需要对象实例 输出 " this is ProductA ,version 1.0. aaa@qq.com"
method = productAClass.getMethod("CallB", null);
method.invoke(obj, null);
//CallB 方法中直接使用 new B,并调用B的print方法:输出"aaa@qq.com"
method = productAClass.getMethod("CallC", null);
method.invoke(obj, null);
//CallC 方法中使用Class.forName()加载C类,newInstance()方法实例化C,并调用C的print方法,输出:"aaa@qq.com"
//由一个类加载器L加载的类A,由类A代码调用产生的类实例或Class(new 或 Class.forName()),均由类加载器L加载,因此B类没有报ClassNotFoundException
}catch(Exception e){
e.printStackTrace();
}
}
}
以上代码说明:
1.使用非ClassPath下的类需要使用 指定类加载器手工加载,URLClassLoader.loadClass()
2.非ClassPath下的类加载后,需要使用反射方式调用方法(还有接口方式调用,下面介绍)。
3.类完成加载过程后,static代码块已执行。
4.static 方法和static成员变量 存在Class中,因此反射时不需类的实例。
Class只有一个,类的实例可以有多个,因些可以使用 a.getClass() == b.getClass() 来判断a和b是否为同 一个Class产生的实例.
5.由一个类加载器L加载的类A,由类A代码调用产生的类实例或Class(new 或 Class.forName()),均由类加载器L加载(对于非ClassPath下的jar包中的类中功能,入口时需要反射调用,最好能提供一个总的入口(可以使用门面模式),被调用时反射一次就可以了,如果分散成多个入口,需要反射多次)。
上面提到由于当前ClassPath下没有对应的类,所以需要反射调用调用自定义加载的类,那么新建ProductA和B放到类路径下,编译就能通过了,但是URLClassLoader会优先加载ClassPath下的类,我想要调用的类没有被加载:
示例:(新建的ClassPath下的类)
public class ProductA {
static {
System.out.println("ProductA version 2.0, static code ran");
}
public void printClassLoader(){
System.out.println(" this is ProductA ,version 2.0. classLoader="+this.getClass().getClassLoader());
}
public static void staticPrint(){
System.out.println(" this is ProductA ,version 2.0. static method called ");
}
public void CallB(){
B b = new B();
b.print();
}
}
加载类:
public static void main(String args []){
try{
File filePath = new File("F:/product.jar");
URL urs [] = new URL[] {filePath.toURI().toURL()};//指定类所在URL
URLClassLoader urlClassLoader = new URLClassLoader(urs);
Class productAClass = urlClassLoader.loadClass("test.temp.ProductA");//输出"ProductA version 2.0, static code ran",说明loadClass 加载的是ClassPath下的类
ProductA productA = (ProductA)productAClass.newInstance();
productA.printClassLoader();
//输出" this is ProductA ,version 2.0. aaa@qq.com"
//说明URLClassLoader的优先加载ClassPath下的类
}catch(Exception e){
e.printStackTrace();
}
}
因此需要自定义ClassLoader,覆盖loadClass方法,实现优先从非ClassPath中加载:
public class CustomClassLoader extends URLClassLoader{
public CustomClassLoader(URL[] urls) {
super(urls);
}
@Override
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// TODO Auto-generated method stub
Class c = customFindClass(name);
if(c != null){
if (resolve) {
resolveClass(c);
}
}else {
c = super.loadClass(name, resolve);
}
return c;
}
private final Class DEFAULT_CLASS = Object.class;
private Class<?> customFindClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
Map<String,Class> classMap = getClassMap();
Class classz = null;
if(classMap.containsKey(name)){
classz = classMap.get(name);
if(classz == DEFAULT_CLASS){
try{
classz = super.findClass(name);
classMap.put(name, classz);
}catch(ClassNotFoundException e){
classMap.remove(name);
}
}
}
return classz;
}
private volatile Map<String,Class> classMap = null;
private Map<String,Class> getClassMap() throws ClassNotFoundException{
Map<String,Class> tempClassMap = classMap;
if(null == tempClassMap){
tempClassMap = initClassMap();
}
return tempClassMap;
}
private synchronized Map<String,Class> initClassMap() throws ClassNotFoundException{
if(classMap==null){
Map<String,Class> tempClassMap = new ConcurrentHashMap<String,Class>();
URL urls [] = this.getURLs();
if(urls != null && urls.length>0){
for(int i=0;i<urls.length;i++){
URL url = urls[i];
String fileName = url.getFile();
if(fileName != null ){
List<String> classNameList = getClassNameList(fileName);
if(classNameList != null && classNameList.size()>0){
for(String className:classNameList){
tempClassMap.put(className, DEFAULT_CLASS);
System.out.println(className);
}
}
}
}
}
classMap= tempClassMap;
}
return classMap;
}
private List<String> getClassNameList(String fileName) throws ClassNotFoundException{
List<String> classNameList = null;
File file = new File(fileName);
if(file.exists()){
if(file.getName().endsWith("jar") && file.isFile()){
classNameList = getClassNameFromJar(file);
}else if(file.isDirectory()){
classNameList = getClassNameFromDir("",file);
}
}
return classNameList;
}
private List<String> getClassNameFromJar(File file) throws ClassNotFoundException{
List<String> classNameList = new ArrayList();
try{
JarFile jarFile = null;
jarFile = new JarFile(file);
Enumeration<JarEntry> enumeration = jarFile.entries();
while (enumeration.hasMoreElements()) {
JarEntry jarEntry = enumeration.nextElement();
String name = jarEntry.getName();
if (name.endsWith(".class")) {
String className = name.substring(0, name.length() -6).replace('/', '.').replace('\\', '.');
classNameList.add(className);
}
}
}catch(IOException e){
e.printStackTrace();
}
return classNameList;
}
private List<String> getClassNameFromDir(String path,File dir){
List<String> classNameList = new ArrayList();
File files[] = dir.listFiles();
for(File file:files){
if(file.isFile() && file.getName().endsWith(".class")){
String className = file.getName().substring(0, file.getName().length() -6).replace('/', '.').replace('\\', '.');
if(path.length()>0){
className = path+"."+className;
}
classNameList.add(className);
}else{
String subPath = path;
if(path.length()>0){
subPath+=("."+file.getName());
}
List subClassNameList = getClassNameFromDir(subPath,file);
classNameList.addAll(subClassNameList);
}
}
return classNameList;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
}
再测试一下:
public static void main(String args []){
try{
File filePath = new File("F:/product.jar");
URL urs [] = new URL[] {filePath.toURI().toURL()};//指定类所在URL
URLClassLoader urlClassLoader = new CustomClassLoader(urs);//使用自定义ClassLoader ,优先从URL指定的路径加载类
// 由于类ProductA类所在的jar包 product.jar 未在当前类路径下,因此不能直接NEW (编译器会通不过)或 Class.forName(当前类路径无该类报错:ClassNotFoundException)
// 可以通过指定的类加载器loadClass
Class productAClass = urlClassLoader.loadClass("test.temp.ProductA");//输出"ProductA version 1.0, static code ran",说明loadClass 正确加载非ClassPath下的类
Object obj = (Object)productAClass.newInstance();
//这行没有报错?为啥?,因为Object类型不是由自定义ClassLoader加载的,
System.out.println(Object.class.getName()+".classLoader="+Object.class.getClassLoader());//输出"java.lang.Object.classLoader=null"
System.out.println(ProductA.class.getClassLoader());//输出:aaa@qq.com
System.out.println(productAClass.getClassLoader());//输出:aaa@qq.com
ProductA productA = (ProductA)productAClass.newInstance();
//这行报错了:java.lang.ClassCastException: test.temp.ProductA cannot be cast to test.temp.ProductA
//at temp.java.TestURLClassLoader1.main(TestURLClassLoader1.java:23)
productA.printClassLoader();
}catch(Exception e){
e.printStackTrace();
}
}
上面代码中,编译通过了,自定义ClassLoader加载非ClassPath下的类成功了,但类型转换失败了。
可能原因如下:
ProductA (标红的 ProductA )是由系统类加载器加载的,可能是因为明文代码写的,编译时就确定了吧, productAClass 是由自定义类加载的,虽然类全名相同,但jvm不认为它们是相同的。
上面代码中:
Object obj = (Object)productAClass.newInstance();
这行就没有报错,为啥呢? 估计明文写的代码Object是由当前ClassLoader自动加载的()
System.out.println(Object.class.getName()+".classLoader="+Object.class.getClassLoader());//输出"java.lang.Object.classLoader=null"引导类加载器加载的
而自定义类加载器加载了ProductA,ProductA是Object的子类,Object也需要加载,在自定义类加载器中加入输出代码,测试一下 Object是哪个类加载器加载的:
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// TODO Auto-generated method stub
Class c = customFindClass(name);
if(c != null){
if (resolve) {
resolveClass(c);
}
}else {
c = super.loadClass(name, resolve);
}
System.out.println(name+".classLoader="+c.getClassLoader());
return c;
}
输出:java.lang.Object.classLoader=null//引导类加载
aaa@qq.come3//自定义类加载
表明:1.由同一个类加载器加载,可以做类型转换。
2.加载顺序:先加父类或接口,再加载子类
可以在ClassPath类路径上建立父类或接口,由非ClassPath上的类实现,非ClassPath上的类使用:
1.如果ClassPath上没有父类或接口的另一实现版本,使用URLClassLoader就可以加载非ClassPath上的类,并可以做类型转换,转到父类或接口上再调用
2.如果ClassPath上有父类或接口的另一实现版本,需要自定义ClassLoader优先加载非ClassPath上的类,并可以做类型转换,转到父类或接口上再调用
3.非ClassPath上的类没有父类或接口,又或者父类或接口不在ClassPath上,类加载后,需要通过反射调用。
参考资料:http://blog.csdn.net/lovingprince/article/details/4238695
http://jiajun.iteye.com/blog/608564
推荐阅读
-
Zend Framework自定义Helper类相关注意事项总结_PHP
-
自定义英雄生存1.87 PHP高自定义性安全验证码代码
-
Android 自定义ToolBar并沉浸式
-
小米相机不能自定义设置水印怎么办? 小米水印的设置方法
-
SpringBoot validator参数验证restful自定义错误码响应方式
-
一小时学会TensorFlow2之自定义层
-
ThinkPHP模板之变量输出、自定义函数与判断语句用法
-
js中自定义format格式化输出实例
-
mybatis-generator如何自定义注释生成
-
thinkphp3.x自定义Action、Model及View的简单实现方法_php实例