黑马程序员-Java基础总结17——类加载器

高新技术Day03——类加载器

-------android培训java培训、期待与您交流!---------- 

【为保证日志可浏览性,日志中提供源代码以及解析,在此不粘贴】

1、类加载器与自定义类加载器;

2、动态代理与AOP框架;

3、多线程与定时器。

————————————————类加载器————————————————

一、类加载器(ClassLoader:

Java程序代码加载流程:  

Java源代码(.java)——>硬盘中的.class文件——>内存中的字节码———> 程序

                编译                类加载器               运行

(一)类加载器概念:

  1Java虚拟机中有多个类加载器,默认三个主要类加载器分别负责加载不同特定位置的类: BootStrap(嵌套在JVM中,且第一个类加载器,不属于java)ExtClassLoaderAppClassLoader等。

2类加载器也是Java,因此也需要由其他父级类加载器加载,而顶层的就是BootStrap,其随JVM内核的启动而启动,是JVMC语言编写的二进制代码生成

  3Java的类加载器加载方式是委托加载机制,即当加载某个类时先有顶层类加载器加载,如果父类加载器无法加载到,那么就由子类再去加载。

 

(二)各级类加载器负责加载范围:


A、先由bootstrap(引导式Classloader)Javart.jar(Java提供的所有类都存储在此Jar)中寻找并加载;

B、如果没有,然后是由ExtClassloader(扩展类加载器)"jre/lib/ext"目录下寻找并加载;

C、如果也没有找到,最后才是由AppClassloader(加载应用程序[Application]的类加载器)JavaCLASSPATH路径中查找并加载。

自定义创建的类都是存储在此ClassPath路径,即使是自定义类加载器加载的类的路径也属于此路径的分支

 

(三)获取加载类字节码的类加载器方式:

Class方法:  getClassLoader();  : 返回加载该字节码的类加载器;

ClassLoader类方法: getParent();:  返回委托的父类加载器。

代码示例:

    String str = new String("classloader");

    ClassLoader classLoader = str.getClassLoader();

    ClassLoader parent = classLoader.getParent();

注意 System类等是由BootStrap加载,因此通过getClassLoader返回值为null,因BootStrap不是Java

其他:  (注意)

自定义创建java.lang.System类无效的原因:  委托加载机制的由上至下逐层加载

解决方案: 使用自定义特殊(加密等)的类加载器去加载该自定义类主要实现方式是通过指定目标类路径,可避免被上层类加载器加载,但是如果要求不被AppClassloader加载,只能自定义类加载器加载,就需要给字节码加密

【第二和三点在源程序代码中有较详细的解析答案,见system.java等文件】

 

(四)自定义类加载器:

注意事项与方法:

1、自定义类要继承ClassLoader类;

2Class<?>  loadClass(Stringname)  : 读取类名,类加载器加载生成一个类;

                defineClass(Stringname) :  将得到的class文件转换成字节码;

覆盖Class<?>  findClass(String name) 此类加载器自己加载类文件的方法

 

注意:   

loadClass()  不复写,即先由父类加载器去加载是获得类字节码的方法;

findClass()  复写,改变此类加载器的加载方式(使自定义类能访问到目标类/加密类)

 

(五)其他: 

1类加载器体系中父类加载器编写了loadClass()共性方法,而子类直接继承获得根据需要复写本身特有方法内容;Java将这一的编程方式称为模板方法设计模式

2模板方法设计模式: 父类提供共性方法,而局部细节的操作方法定为抽象方法由子类去复写

 

类加载器的一个高级问题实验分析

Web Project  ————> Tomcat


————————自定义类加载器——————————

二、创建自定义类加载器(加密/解密)的流程:


(代码详见:  http://dl.vmall.com/c0y38zarjt 中的classLoader\system包源文件)

【以下是流程明晰,帮助理解创建过程,而源文件代码中有详细的解析、类加载器加载知识以及个别疑难的解析。】

1将程序的源文件(system,该类继承某个Java)先编译成.class文件

package com.Edward.day03.classLoader.system;

//1、继承Object类等可被Java本身类加载器所加载的类;
//2、自定义类加载器只在创建此类实例对象时加载此类的.class文件;
//3、而创建加密类的实例对象时,需要加载该类的字节码,但是由于该类的.class被加密,无法被一般类加载器加载,所以会导致异常;
//4、因此通过继承来通过多态赋值的方式来实现加密类实例对象的引用.
public class system extends Object {

	//需要被加载的类,该类的.class文件将被加密,用自定义类加载器加载并解密;
	//PS(提醒): 如果要重新生成此类的.class文件,需要此类源文件进行重新保存,才会生效;
	public static void main(String[] args){
		System.out.println("system类");
	}
	
	//自定义类加载器只在创建此类实例对象时加载此类的.class文件;
	public String toString(){
		return "I am system";
	}
}

/*   问题: 是否可以自定义创建System类?
答: 1、由于Java类加载器的委托机制,所以一般情况下自定义System类无法被加载进内存;
因为所有类的字节码的加载一般都是:  
A、先由 bootstrap(引导式Classloader) 从Java的rt.jar(Java提供的所有类都存储在此Jar包)中寻找并加载;
B、如果没有,然后是由 ExtClassloader(扩展类加载器)在 "jre/lib/ext"目录下寻找并加载;
C、如果也没有找到,最后才是由AppClassloader(加载应用程序[Application]的类加载器) 从Java的CLASSPATH路径中查找并加载。
【自定义创建的类都是存储在此ClassPath路径中,即使是自定义类加载器加载的类的路径也属于此路径的分支】

因此一般情况下,存放在ClassPath路径或ext目录下的自定义System类都无法被Java加载进内存成为字节码文件,
因为bootstrap类加载器在rt.jar包中加载到java.lang.System类,所以一般情况下即使自定义System类也无效(无法被加载).

2、但是可以通过自定义类加载器指定路径去加载创建自定义的System类的实例对象;
因为: A、用自定义类加载器去加载指定路径下的自定义System类时,Java的bootstrap类加载器不会加载到该路径,因此不会调用rt.jar包中的System类;
但是  B、自定义类加载器只在创建此类实例对象时才能通过loadClass()方法指定路径加载自定义System类的.class文件;
      C、如果该路径在ClassPath路径中,可由AppClassloader加载到,可通过加密.class文件与类加载器加载时解密方式来实现只有自定义类加载器加载.

PS:  下文仅展示加密方式,而不加密覆盖System类,如要进行覆盖模拟,
  因程序代码中会多次调用System类,为避免出错,建议从rt.jar中获取并加密System.class文件等。
  例: 当执行到System.out.println("O(∩_∩)O~");方法时,当自定义类中没有该对应方法,则程序将报错。
 */

2MyClassLoader主函数的流转换加密.class文件并存储到指定位置

(.class编译工具: 加密程序.class文件)

3MyClassLoader中要求继承ClassLoader类并复写findClass()方法

package com.Edward.day03.classLoader.system;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

//主函数:  .class加密工具,将加密后新生成的.class文件存储到指定的子文件夹中;
//findClass:  复写了ClassLoader的类加载器加载.class文件findClass()方法,可加载指定的加密.class文件.
public class MyClassLoader extends ClassLoader 
/*extends ParentClassLoader			//测试ClassLoader体系的findClass与loadLoader方法的加载顺序情况;    */
{	
	public static void main(String[] args)throws Exception{
		//String srcPath = "E:/Workspaces/Java/StaticImport/bin/com/Edward/day03/classLoader/system/system.class";
		//——————提高程序扩展性,将需转换的.class文件以字符串形式传给DemoClassLoader方法————;
		String srcPath = args[0];
		
		//String destPath = "E:/Workspaces/Java/StaticImport/bin/com/Edward/day03/classLoader/system/ hehe /system.class";
		//————指定转换后的.class文件存放的子文件夹名称——————;
		String path = args[1];		//输入任意的子目录名,例如hehe;
		String destPath = srcPath.substring(0, srcPath.lastIndexOf("/"))+"/"+ path + srcPath.substring(srcPath.lastIndexOf("/"));
		
		//导入文件名称到流中,后续通过实现加密动作;
		FileInputStream fis = new FileInputStream(srcPath);
		FileOutputStream fos = new FileOutputStream(destPath);
		
		//加密源文件...............
		classStream(fis,fos);
		//提醒该转换动作正常操作完成,并关闭流;
		System.out.println("转换完毕.....");
		fis.close();
		fos.close();
	}
	
	//对导入的.class文件的流进行加密或解密操作;
	private static void classStream(InputStream ips,OutputStream ops)throws Exception{
		int i = 0;
		while((i = ips.read())!=-1){
			//将二进制数据进行 与255的二进制数操作,实现加密/解密操作;
			ops.write(i^ 0xff);
		}
	}

	//自定义类加载器,复写ClassLoaderDemo的findClass方法,使其可作为类加载器加载并解密指定的类文件成为内存字节码;
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
			System.out.println("....DemoClassLoader.findClass()方法.....");
			
		//获取loadClass方法加载类的名称,并给该类名添加.class后缀;
		String filePath =  name +".class";
		try {
			//文件读取流,读取指定的.class文件;
			FileInputStream fis = new FileInputStream(filePath);
			//创建ByteArrayOutputStream 字节数组输出流,用于将.class文件的读取数据输出成二进制的数据;
			ByteArrayOutputStream fos = new ByteArrayOutputStream();
			
			//转移数据,并进行解密操作;
			classStream(fis,fos);
			fis.close();
			//将字节数组输出流中的数据存储到字节数组中;
			byte[] bytes = fos.toByteArray();
			//将字节数组中的数据以字节码Class类形式返回;
			return defineClass(null, bytes, 0, bytes.length);
			
		} catch (Exception e) {
			System.out.println("Exception...............");
			e.printStackTrace();
		}
		
		//多余操作,判断是否会出现异常,出现了调用父类findClass操作;
		System.out.println("findClass...........");
		//保留此方法,当自定义类加载器无法加载该类时,可调用父类原有的方法;(只是预防)
		return super.findClass(name);
	}
	
	@Override
	public Class<?> loadClass(String name) throws ClassNotFoundException {
		System.out.println("————DemoClassLoader.loadClass()方法——————");
		return super.loadClass(name);
	}
}

4创建加载的主函数类ClassLoaderDemo:

A、通过创建自定义类加载器的实例对象(MyClassLoader匿名对象等)loadClass(String path)方法去加载指定路径下的加密后的.class文件

B、以多态赋值方式,根据返回Class创建加载到的system实例对象

C根据实例对象调用方法或打印加载该类的类加载器

package com.Edward.day03.classLoader.system;
//import com.Edward.day03.classLoader.system.hehe.system;

public class ClassLoaderDemo {

	public static void main(String[] args)throws Exception {
		
		//以匿名对象的形式来加载调用加密类,可无需通过多态赋值的方式调用加密类方法,
		//也就有了匿名的使用局限限制(其他详细分析见下方的多态创建实例对象);
		System.out.println("匿名对象: "+ new MyClassLoader().loadClass
				("E:/Workspaces/Java/StaticImport/bin/com/Edward/day03/classLoader/system/hehe/system")
				.newInstance().toString());
		
/*		system sy = new system();
		System.out.println(sy.toString());
		System.out.println(sy.getClass().getClassLoader().getClass().getName());
				
		1、此处如果可以正常加载,则是因为AppClassLoader加载system下的system.class的未加密文件;
		因为:  JVM为每个类加载器维护一个名字空间(即存储类字节码的空间),
		因此导致AppClassLoader从system目录下读取到system.class的字节码文件;
		而自定义类加载器MyClassLoader从system.hehe目录下读取加密的system.class的字节码文件,所以内存中存在两份字节码文件;
		
		2、PS: 在张孝祥老师的Java邮件开发的第15个视频的大概在20分钟左右处的类加载器的异常问题就是因为这个原因导致的,
		不同的是Java类加载器已加载一个类,而Tomcat类加载器加载了同名的类,内存存在两份同名字节码,
		导致赋值时出现异常无法将  A 类型转换成 A,当然此处的两个A是存在不同名字空间中的两份不同的字节码;
		(最后的处理时删除Tomcat中的包含该A类的jar,可能刚好Tomcat中的A类是不需要的吧)				*/
		
		System.out.println("—————————————分割线———————————————");
		
		//指定自定义类加载器去加载 加密类的.class文件,返回Class对象;
		//————————————注意:此处loadClass()读取的class文件是加密后的.class文件(不需要.class后缀,会自动添加)——————————————
		Class clazz = new MyClassLoader().loadClass("E:/Workspaces/Java/StaticImport/bin/com/Edward/day03/classLoader/system/hehe/system");
		//创建该加密类的实例对象(构造方法),并以多态的方式赋值;
		Object date =  (Object)clazz.newInstance();
		//调用多态形式的加密类的toString方法;
		System.out.println("——Object————:  "+ date);
		
		//验证加载创建该加密类实例对象的类加载器是否为自定义类加载器以及父类加载器;
		ClassLoader loader2 = clazz.getClassLoader();			
		while(loader2!=null){
			//打印此次循环的类加载器的名称: 第一次则是加载 加密类.class文件的类加载器;
			System.out.println("system类的类加载器:  " + loader2.getClass().getName());
			//获取当前类加载器的父类加载器;
			loader2 = loader2.getParent();
		}
	}
}

其他(小知识):

1Native2Ascii: (2to简写),Java的一个文件转码工具,是将特殊各异的内容转为用指定的编码标准文体形式统一的表现出来。

2DOM4j: (4for简写),异常日志工具。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值