类加载器:负责加载类的工具



Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:


BootStrap(根加载器)


ExtClassLoader(扩展类加载器)


AppClassLoader(系统类加载器)


还有一种是用户自定义加载器




获取类文件所使用的加载器名称


System.out.println(ClassLoaderTest.class.getClassLoader().getClass()
				.getName());




类加载器也是Java类,因为java类的类加载器本身也要被类加载器加载,显然第一个类加载器不是java类,这正是BootStrap(存在java虚拟机中)。


Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。 




类加载器之间的父子关系和管辖范围图


java 使用类加载器加载本地类文件 java中类加载器有几种_java 使用类加载器加载本地类文件






总结:


当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?


首先当前线程的类加载器去加载线程中的第一个类。

如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。(类A继承了类B,那么他们的加载器是同一个加载器

还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。



类加载流程:


当加载一个类文件时,首先到AppClassLoader加载器中查找是否有此类文件,无论有没有都到上级加载器(ExtClassLoader)中查找,无论有无都向上级加载器BootStrap中查找,有则加载此类,无则再依次向下查找,如果下面的所有加载器中没有找到该类文件则报ClassNotFoundException异常




规则总结:


每次都从低级加载器开始向高级加载器查找,如果在 最高级加载器 中查找到就直接执行该类文件,没找到就再依次向下级加载器查找,找到后就加载,否则就报ClassNotFoundExcepiton异常。




当类文件在高级加载器和低级加载器中同时存在时:高级加载器总是优先于低级加载器加载




相关面试题:能不能自己写个类叫java.lang.System


可以写,但是这个类永远不会被加载,因为在加载器BootStrap中存在此类,所以加载器优先加载BootStrap负责范围中的System的类




自定义类加载器




类加载器中的loadClass方法内部实现了父类委托机制,因此我们没有必要自己覆盖loadClass,否则需要自己去实 现父类委托机制。我们只需要覆盖findClass方法。loadClass方法中调用了findClass方法,使用的是模板设计模 式。我们得到了Class文件后,就可以通过defineClass方法将二进制数据转换成字节码。这就是自定义类加载器的 编写原理。


编写一个加密文件的程序




要加密的java文件代码


package cn.itheima.ClassLoader;

public class ClassLoaderAttachment
{
	public String toString()
	{
		return "Hello,Class loader!";
	}
}



加密程序:其实就是讲一个文件写入到指定目录,在写入的时候加入一些规则即可。




package cn.itheima.ClassLoader;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;


public class MyClassLoader extends ClassLoader
{

	/**
	 * @param args
	 * 文件加密其实就是在写入文件的时候加上特意的规则,本程序是使用反码来进行加密的
	 */
	public static void main(String[] args) throws Exception
	{
		//将class文件目录和写入目录指定给主函数,分别用args[0]和args[1]存储
		String srcPath = args[0];
		String destDir = args[1];
		//读取指定目录中的文件
		InputStream ips = new FileInputStream(srcPath);
		//获取指定目录文件的文件名
		String destFileName = srcPath.substring(srcPath.lastIndexOf("\\")+1);
		//定义存放加密后的文件目录
		String destPath = destDir + "\\"+ destFileName;
		//写入目标目录
		OutputStream ops = new FileOutputStream(destPath);
		encrypt(ips,ops);
		ips.close();
		ops.close();
		
	}
	//加密其实就是将类文件读取后,在写入的时候进行非正常的写入
	public static void encrypt(InputStream ips,OutputStream ops) throws Exception
	{
		//单个字符字节写入,将每个字节进行加密
		int a = 0;
		while((a = ips.read())!= -1)
		{
			ops.write(a^0xff);//加密的一种方式,反码加密
		}
	}
}



注意:


要加密某个文件,首先要读取文件,在读取文件的时候必须要传入文件的目录路径,此程序中时直接用的主函数传入的参数进行读取的,

具体的步骤如下:

右键-->RunAs-->Run Configurations-->Arguments-->在Program Arguments中输入要读取的文件路径,和写入的文件目录。注意:这两个参数间用空格分开,绝对不可以使用","。

当然你也可以直接输入他们各自的目标路径,个人觉得这样更省事。


加密后的类文件,如果按照正常的类进行加载的话,是不可能正常加载成功的,这时候我们就需要写一个解密的类加载程序。




解密类加载程序




步骤:


1.加载器继承ClassLoader

2.复写findClass()方法

3.使用defineClass()方法




加密文件


package cn.itheima.ClassLoader;

import java.util.Date;

public class ClassLoaderAttachment extends Date
{
	public String toString()
	{
		return "Hello,Class Loader!";
	}
}

加密解密程序


package cn.itheima.ClassLoader;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;


public class MyClassLoader extends ClassLoader
{

	/**
	 * @param args
	 * 文件加密其实就是在写入文件的时候加上特意的规则,本程序是使用反码来进行加密的
	 */
	private String srcPath;
	public MyClassLoader(){}
	public MyClassLoader(String srcPath)
	{
		this.srcPath = srcPath;
	}
	@SuppressWarnings("deprecation")
	@Override
	//复写findClass方法,自定义加载器
	protected Class<?> findClass(String name) throws ClassNotFoundException
	{
		//读取加密后的文件,进行解密,得到原来的类文件
		String classFileName = srcPath + "\\"+name.substring(srcPath.lastIndexOf("\\")+1)+".class";
		try
		{
			InputStream ips = new FileInputStream(classFileName);
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			encrypt(ips,bos);//解密文件
			ips.close();
			byte[] bytes = bos.toByteArray();
			return defineClass(bytes,0,bytes.length);	
		} catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return super.findClass(name);
	}
	public static void main(String[] args) throws Exception
	{
		//将class文件目录和写入目录指定给主函数,分别用args[0]和args[1]存储
		String srcPath = args[0];
		String destDir = args[1];
		//读取指定目录中的文件
		InputStream ips = new FileInputStream(srcPath);
		//获取指定目录文件的文件名
		String destFileName = srcPath.substring(srcPath.lastIndexOf("\\")+1);
		//定义存放加密后的文件目录
		String destPath = destDir + "\\"+ destFileName;
		//写入目标目录
		OutputStream ops = new FileOutputStream(destPath);
		encrypt(ips,ops);
		ips.close();
		ops.close();
		
	}
	//加密其实就是将类文件读取后,在写入的时候进行非正常的写入
	public static void encrypt(InputStream ips,OutputStream ops) throws Exception
	{
		//单个字符字节写入,将每个字节进行加密
		int a = 0;
		while((a = ips.read())!= -1)
		{
			ops.write(a^0xff);//加密的一种方式,反码加密
		}
	}	
}



使用自定义加载器


package cn.itheima.ClassLoader;

import java.util.Date;

public class ClassLoaderTest2
{

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception
	{
		// 加载文件获得对应的class文件
		Class clazz = new MyClassLoader("DestFolder").loadClass("ClassLoaderAttachment"); 
		//创建类文件对象
		Date d = (Date)clazz.newInstance(); 
		//调用该对象的toString()方法
		System. out.println(d.toString());
	}

}



注意: 




1、之所以让ClassLoaderAttachment类继承Date是因为,如果直接写 

ClassLoaderAttachment d = (ClassLoaderAttachment)clazz.newInstance();这条语句,编译的时候就会报错,因为此时的 

ClassLoaderAttachment在AppClassLoader加载进内存后就无法识别。所以需要通过借助一个父类对象绕过编译器。也就 

是:Date d1 = (Date)clazz.newInstance();。 

2、如果想让父类加载器AppClassLoader加载ClassLoaderAttachment类,则需要执行下面的语句: 

Class clazz = new MyClassLoader("ClassLoaderLib").loadClass("com.itheima.day2.ClassLoaderAttachment"); 

但是父类加载可能会出错。 


3、在创建ClassLoaderAttachment类时,在父类选项中,要选择 Date类,否则之后再继承Date类会报错。


4、在测试类加载器是否是自定义类加载器时,一定要先把ClassPath路径下的ClassLoaderAttachment.class文件删除,否则类加载器会优先使用其父类加载器加载(类加载器的委托机制),而不是自定义的加载器。



类加载器的一个高级问题的实验分析 (我觉得这个问题是涉及到Web方面的类加载的问题处理和操作过程)


过程描述


编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,正常发布后,看到打印结果为WebAppClassloader。

把MyServlet.class文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。

把servlet.jar也放到ext目录中,问题解决了,打印的结果是ExtclassLoader 。


原理:




java 使用类加载器加载本地类文件 java中类加载器有几种_加载_02






步骤:


1.新建一个Web工程:new-->Web Project-->命名工程文件名itheimaweb,这里要注意Java EE Version版本,默认的是6.0的。点击finish完成创建;




2.在该工程文件中创建severlet文件:右键src-->new-->servelet-->命名包名,cn.itheima.itheimaweb.web.Servelets;命名文件名,MyServelet-->勾选doGet方法-->Next-->单击finish完成创建;




3.在MyServlet文件中录入代码


package cn.itheima.itheimaweb.web.servlets;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyServlet extends HttpServlet
{

	/**
	 * The doGet method of the servlet. <br>
	 *
	 * This method is called when a form has its tag value method equals to get.
	 * 
	 * @param request the request send by the client to the server
	 * @param response the response send by the server to the client
	 * @throws ServletException if an error occurred
	 * @throws IOException if an error occurred
	 */
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException
	{

		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		ClassLoader loader =this.getClass().getClassLoader(); 
		while(loader!=null)
		{ 
			out.println(loader.getClass().getName()+"<br/>"); 
			loader = loader.getParent(); 
		} 
		out.close(); 	
	}
}




4.右键MyServlet.java的源文件-->Run As-->

MyEclipse Server Application-->在跳出的对话框选择Tomcat服务器-->点击OK

运行结果:


org.apache.catalina.loader.WebappClassLoader
org.apache.catalina.loader.StandardClassLoader
org.apache.catalina.loader.StandardClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader

可以看到MyServlet是由WebappClassLoader类加载器加载的


5.将MyServlet文件进行导包,目标目录为D:\ProgramFiles\Java\jdk1.7.0_05\jre\lib\ext中,这时候再运行该服务器时,将会出现找不到HeepServlet问题


java.lang.ClassNotFoundException: javax.servlet.http.HttpServlet




6.将Tomcat的lib目录中的servlet-api.jar包放入ext目录中,即可正常运行,但是此时的类加载器是ExtClassLoader




总结:


造成这个问题的原因:


因为MyServlet继承了HttpServlet,所以加载器在加载MyServlet的同时也会加载HttpServlet,且他们用的是同一个加载器。当MyServlet被导入ext目录中是,根据委托机制的原理,加载器变为ExtClassLoader,当加载HttpServlet时,再ext目录下将找不到该jar包,于是就出现了以上问题。