`

自定义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());
		//输出: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

 

 

    

 

 

 

 

 

 

 

  • 大小: 153.9 KB
分享到:
评论

相关推荐

    自定义classloader的使用

    自定义Classloader允许开发者根据特定需求定制类的加载逻辑,例如加密类文件、隔离不同版本的库或者动态加载代码。本文将深入探讨自定义Classloader的使用。 一、Classloader的工作原理 Java的类加载机制遵循双亲...

    Java实现热加载完整代码;Java动态加载class;Java覆盖已加载的class;Java自定义classloader

    让Java支持热加载是个不错的想法。如何做到的呢? 1. 定义好接口和实现类 2. 让代理类通过反射的方式调用实现类,对外暴露的是代理类。 3. 自定义URLClassLoader。检查实现类.class文件的...Java自定义classloader;

    定义ClassLoader调用外部jar包

    当我们需要从外部jar包动态加载类时,自定义ClassLoader就显得尤为关键。这篇博文"定义ClassLoader调用外部jar包"探讨了如何创建一个自定义的ClassLoader,以便能够灵活地加载不在应用主类路径(ClassPath)中的jar...

    ClassLoader运行机制 自己写的

    这里我们将详细讨论ClassLoader的运行机制,特别是自定义ClassLoader的设计与实现。 ClassLoader的基本职责是根据类名动态加载对应的类文件。在Java中,类加载过程遵循双亲委派模型(Parent Delegation Model)。这...

    使用自定义ClassLoader解决反序列化serialVesionUID不一致问题 _ 回忆飘如雪1

    标题和描述中提到的解决方案是通过自定义`ClassLoader`来处理`serialVersionUID`不一致的问题。以下是几种常见方法的优缺点以及自定义`ClassLoader`的详细解释: 1. **修改序列化byte数据**: 这种方法直接修改已...

    关于Android中自定义ClassLoader耗时问题的追查

    在Android开发中,自定义ClassLoader是一项关键技能,尤其在实现热修复和插件化技术时。本文主要探讨了Android中自定义ClassLoader导致的性能问题,特别是冷启动速度的影响。问题的核心在于,通过插入自定义的...

    MyCLRepl:自定义 Scala REPL 示例以添加自定义 ClassLoader 和自定义 REPL 命令

    自定义 ClassLoader 加载任何类时的类名。 ":myCommand" 命令位于默认 REPL 命令之上。 scala &gt; val hello = " hello " MyClassLoader loads classOf &lt; root&gt;.$line3 &lt;&lt;中略&gt;&gt; MyClassLoader loads classOf ...

    ClassLoader 案例

    自定义ClassLoader允许开发者根据特定需求加载类,比如动态加载或更新类文件,这在某些高级应用场景中非常有用,如插件系统、热部署等。本案例将深入探讨如何创建一个自定义的ClassLoader,利用Java反射和注解技术...

    ClassLoader小例子

    下面我们将详细讨论ClassLoader的基本概念、工作流程以及如何自定义ClassLoader。 1. **ClassLoader的基本概念** - 类加载器是Java中的一个核心组件,它负责将类的.class文件加载到JVM中,并转换为可执行的Java...

    classloader

    创建自定义类加载器需要继承ClassLoader类,并重写findClass()方法。在这个方法里,你可以编写代码来从指定的位置(例如,网络、文件系统或内存)读取类的字节码,并通过defineClass()方法将其转换为Class对象。 在...

    Java ClassLoader定制实例

    在某些特定场景下,比如动态加载代码、插件系统或者安全隔离等,我们需要自定义ClassLoader来实现特定的加载逻辑。例如,我们可能希望加载网络上的类,或者从数据库中读取类的字节码。 以...

    Understanding the Java ClassLoader

    为了更好地理解和利用Java的这一特性,本篇将详细介绍Java ClassLoader的作用及其工作原理,并通过构建一个示例ClassLoader来帮助读者深入理解如何自定义ClassLoader,从而扩展JVM的功能。 #### 二、ClassLoader...

    ClassLoader类加载器

    了解和掌握ClassLoader的工作原理以及如何自定义ClassLoader对于深入理解Java应用程序的运行机制非常有帮助。以下是对ClassLoader API的使用和自定义的详细说明。 首先,我们来看ClassLoader的基本概念。在Java中,...

    ClassLoader类加载机制和原理详解

    但有时我们可能需要打破这种模型,比如实现类的版本控制或插件系统,这时可以通过自定义ClassLoader来实现。 5. 类加载器的关系图 Java中的ClassLoader形成了一个树状结构,Bootstrap ClassLoader位于顶端,其他类...

    ClassLoader的 一些测试

    自定义ClassLoader是一种常见的需求,例如在插件系统、热部署等场景。自定义加载器通常需要重写`loadClass(String className)`方法,通过URL读取类的字节流并转换为Class对象。在这个过程中,我们需要注意类的缓存,...

    理解Java ClassLoader机制

    自定义ClassLoader需要继承`java.lang.ClassLoader`类,并重写`findClass()`或`loadClass()`方法。通过这两个方法,你可以控制类的加载来源和方式。 在实际开发中,理解ClassLoader机制可以帮助解决一些问题,例如...

    ClassLoader 详解.doc

    自定义ClassLoader是Java平台的一大特色,开发人员可以根据需要创建自己的类加载器,例如实现模块化、热部署或者加密解密类等高级功能。自定义ClassLoader通常需要重写findClass()或loadClass()方法,以控制类的加载...

    java classloader

    在Java中,用户可以通过自定义ClassLoader来实现特定的加载逻辑,比如从数据库或网络中加载类。这使得Java可以支持许多高级功能,例如插件系统、热部署和模块化系统(如Java 9的Jigsaw项目)。 `JAVA2深度历险.jpg`...

    浅析ClassLoader

    1. 自定义需求:在某些场景下,如动态加载插件、加密类文件等,开发者可能需要自定义ClassLoader。自定义ClassLoader需要继承java.lang.ClassLoader并重写findClass()方法。 2. 实现步骤:创建类加载器实例,读取类...

Global site tag (gtag.js) - Google Analytics