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

时间:2022-02-04 21:16:58

                                                                                      ------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

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

一、概述 

类加载器Java虚拟机中可以安装多个类加载器,系统默认有三个主要的类加载器,

每个类加载器负责加载特定位置的类:

BootStrap,ExtClassLoader,AppClassLoader。

类加载器也是Java类,因此Java类的类加载器本身也要被其他的类加载器加载,显然必须有第一个类加载器不

Java 类,它就是BootStrap类加载器。

代码示例:

class ClassLoaderTest 
{
public static void main(String[] args)
{
System.out.println(ClassLoaderTest.class.getClassLoader().
getClass().getName());
System.out.println(System.class.getClassLoader());
}
}

由上面的示例可以看到ClassLoaderTest类是由AppClassLoader类加载器加载的。 

System类是由BootStrap类加载器加载的。 

注意: 

JVM内核启动的时候,BootStrap就已经被加载了,它是内嵌在JVM内核中的,是用C++语言编写的二进制代

 码,因此不需要其他类加载器加载。 Java虚拟机中的所有类装载器采用了具有父子关系的树形结构进行组织。 

代码示例: 

class ClassLoaderTest 
{
public static void main(String[] args)
{
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while(loader !=null)
{
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
System.out.println(loader);
}
}


由上面的示例可以看到AppClassLoader类加载器的父级别类加载器是ExtClassLoader类加载器ExtClassLoader

类加载器的父级别类加载器是BootStrap类加载器。 在实例化每个类加载器对象时,需要为其指定一个父级类加载器

对象或者默认采用系统类加载器为其父级类加载。


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


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


二、类加载器的委托机制 

当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢? 首先当前线程的类加载器去加载线程中的第

一个类。 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B。

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

每个类加载器加载类时,又先委托给其上级类加载器。 当所有祖宗类加载器没有加载到类,回到发起者类加载器,

还加载不了,则抛ClassNotFoundException, 不是再去找发起者类加载器的儿子,因为没有getChild方法,

即使有,那有多个儿子,找哪一个呢? 


注意: 每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,

这就是类加载器的 委托模式。类加载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类

时,然后才一级级回退到子 孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的

装加载, 那就会告ClassNotFoundException异常。 

有一道面试题,能不能自己写个类叫java.lang.System? 

答案是不能,即使写了也不会被类加载器加载。为了不让我们写System类,类加载机制采用委托机制,这样 

可以保证父级类加载器优先,也就是总是使用父级类加载器能找到的类,结果就是总是使用Java系统自身提供System

类,而不会使用我们自己所写的System类。 

自定义类加载器的编写原理分析 :类加载器中的loadClass方法内部实现了父类委托机制,因此我们没有必要自

己覆盖loadClass,否则需要自己去实现父类委托机制。我们只需要覆盖findClass方法。

loadClass方法中调用findClass方法,使用的是模板设计模式。我们得到了Class文件后,

就可以通过defineClass方法将二进制数据转换成字节码。这就是自定义类加载器的 

编写原理。

API文档中的例子:   

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

需求:

编写一个对文件内容进行简单加密的程序。 

步骤: 

1.对生成的字节码文件进行一个字节一个字节读取,然后对读取到的字节进行加密运算。 

2.把加密后的字节读取到另外一个字节码文件中。 

对字节码文件进行加密,代码示例: 

        

public class ClassLoaderAttachment
{
public String toString()
{
return "黑马程序员";
}
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; public class MyClassLoader{ public static void main(String[] args) throws Exception{ String srcPath = args[0]; String destPath = args[1]+"\\"+srcPath.substring(srcPath.lastIndexOf("\\")+1,srcPath.length()); InputStream ips =new FileInputStream(srcPath); OutputStream ops =new FileOutputStream(destPath); encrypt(ips,ops);ips.close(); ops.close(); } //对输入流数据进行加密public static void encrypt(InputStream ips,OutputStream ops) throws Exception{ int by = 0; while((by=ips.read())!=-1){ ops.write(by ^ 0xff); } } } public class ClassLoaderTest { public static void main(String[] args){ System.out.println(new ClassLoaderAttachment().toString()); } }

三、编写和测试自己编写的解密类加载器
需求: 

编写一个自己的类加载器,可实现对加密过的类进行加载和解密。 

步骤: 

1、自定义的类加载器必须继承ClassLoader 

2、覆盖findClass方法。 

3、在findClass方法中对字节码文件进行解密。 

4、调用自定义的类加载器对字节码文件进行解密。 


自定义类加载器解密,代码示例: 

import java.util.Date; 
public class ClassLoaderAttachment extends Date
{
public String toString()
{
return "黑马程序员";
}
 
import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; public class MyClassLoader extends ClassLoader{ private String classDir; public MyClassLoader(){} public MyClassLoader(String classDir){ this.classDir = classDir;  } @SuppressWarnings("deprecation") @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String classFileName = classDir +"\\"+name.substring(name.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){ e.printStackTrace(); } return super.findClass(name); } public static void main(String[] args) throws Exception{ String srcPath = args[0]; String destPath = args[1]+"\\"+srcPath.substring(srcPath.lastIndexOf("\\")+1,srcPath.length()); InputStream ips =new FileInputStream(srcPath); OutputStream ops =new FileOutputStream(destPath); encrypt(ips,ops);ips.close(); ops.close(); } public static void encrypt(InputStream ips,OutputStream ops) throws Exception{ int by = 0; while((by=ips.read())!=-1){ ops.write(by ^ 0xff); }} } import java.util.Date; public class ClassLoaderTest { public static void main(String[] args) throws Exception { Class clazz = new MyClassLoader("ClassLoaderLib").loadClass("ClassLoaderAttachment"); Date d = (Date)clazz.newInstance(); System. out.println(d); } }

注意: 
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"); 

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

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

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

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

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

具体步骤:新建一个Web工程。

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


右击src-->New-->Servlet,创建一个Servlet。

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


命名包名和类名,并且只创建doGet方法。点击Next。

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


点击Finish。

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


Web代码示例:

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 classMyServletextendsHttpServlet
{
public voiddoGet(HttpServletRequest request, HttpServletResponse response) throwsServletException, 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();
}
}