自定义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

 

类图:


自定义ClassLoader_第1张图片
 

 

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());
		//输出:sun.misc.Launcher$AppClassLoader@19821f
		
		System.out.println(testDefaultClassLoader.getClass().getClassLoader().getParent());
		//输出:sun.misc.Launcher$ExtClassLoader@1cde100
	}
}

 

 

类的加载顺序(如果缓存中没有): 

  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. classLoader=java.net.URLClassLoader@c17164"
		
			method = productAClass.getMethod("CallB", null);
			method.invoke(obj, null);
			//CallB 方法中直接使用 new B,并调用B的print方法:输出"B.classLoader=java.net.URLClassLoader@c17164"
			
		       method = productAClass.getMethod("CallC", null);
		       method.invoke(obj, null);
                       //CallC 方法中使用Class.forName()加载C类,newInstance()方法实例化C,并调用C的print方法,输出:"C.classLoader=java.net.URLClassLoader@c17164"
			
                       //由一个类加载器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. classLoader=sun.misc.Launcher$AppClassLoader@19821f"
			//说明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());//输出:sun.misc.Launcher$AppClassLoader@19821f
			System.out.println(productAClass.getClassLoader());//输出:temp.java.CustomClassLoader@1fb8ee3
			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//引导类加载

            test.temp.ProductA.classLoader=temp.java.CustomClassLoader@1fb8ee3//自定义类加载 

 

表明: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

 

 

    

 

 

 

 

 

 

 

你可能感兴趣的:(ClassLoader)