Java的classloader

   类加载器的基本概念 java

    类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。通常来讲,Java 虚拟机使用 Java 类的方式以下:Java 源程序(.java 文件)在通过 Java 编译器编译以后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每一个这样的实例用来表示一个 Java 类。经过此实例的 newInstance()方法就能够建立出该类的一个对象。 web

    基本上全部的类加载器都是 java.lang.ClassLoader类的一个实例。 数据库

    下面详细介绍这个 Java 类。 apache

    java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,而后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例。除此以外,ClassLoader还负责加载 Java 应用所需的资源,如图像文件和配置文件等。为了完成加载类的这个职责,ClassLoader提供了一系列的方法,比较重要的方法如 表 1所示。关于这些方法的细节会在下面进行介绍。

    表 1. ClassLoader 中与加载类相关的方法 api

方法
说明
getParent()
 返回该类加载器的父类加载器。
loadClass(String name)
加载名称为 name的类,返回的结果是 java.lang.Class类的实例
findClass(String name)
查找名称为 name的类,返回的结果是 java.lang.Class类的实例
findLoadedClass(String name)
查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例
defineClass(String name, byte[] b, int off, int len)
把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的
resolveClass(Class<?> c)
连接指定的 Java 类
   
     本文将从如下几个方面来阐述classloader。
 

    1.分类
        1.1.Bootstrap ClassLoader(启动类加载器)
                加载JAVA_HOME/lib目录下的核心api 或 -Xbootclasspath 选项指定的jar包装入工做, 
                是用原生代码来实现的, 并不继承自 java.lang.ClassLoader。
        1.2.Extension ClassLoader(扩展类加载器)
                加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录
                加载JAVA_HOME/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包
        1.3.System ClassLoader(系统类加载器)
                加载java -classpath/-cp/-Djava.class.path所指的目录下的类与jar包
                它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。通常来讲,Java 应用的类都是由它来完成加载的。
                能够经过 ClassLoader.getSystemClassLoader()来获取它。
                
                每一个classpath以文件名或目录结尾,该文件名或目录取决于将类路径设置成什么:
                对于包含.class文件的.zip或.jar文件,路径以.zip或.jar文件名结尾。
                对于未命名包中的.class文件,路径以包含.class文件的目录结尾。
                对于已命名包中的.class文件,路径以包含root包(完整包名中的第一个包)的目录结尾。
数组

        1.4.自定义类加载器
                经过继承 java.lang.ClassLoader类的方式实现本身的类加载器,
                用户自定义 ClassLoader 能够根据用户的须要定制本身的类加载过程,在运行期进行指定类的动态实时加载。
             tomcat

    2. 层次结构 安全

         

        这四种类加载器的层次关系如上图所示。
        通常来讲,这四种类加载器会造成一种父子关系,高层为低层的父加载器。
        能够经过如下代码来获取类加载器, 同时该代码也演示了类的层次结构         
网络

public class ClassLoaderTree { 
	public static void main(String[] args) { 
        ClassLoader loader = ClassLoaderTree.class.getClassLoader(); 
        while (loader != null) { 
            System.out.println(loader.toString()); 
            loader = loader.getParent(); 
        } 
    } 
 }
代码运行结果以下:
sun.misc.Launcher$AppClassLoader@9304b1 
 sun.misc.Launcher$ExtClassLoader@190d11
        第一个输出的是 ClassLoaderTree类的类加载器, 即系统类加载器。它是 sun.misc.Launcher$AppClassLoader类的实例
        第二个输出的是扩展类加载器, 是 sun.misc.Launcher$ExtClassLoader类的实例。
        这里并无输出引导类加载器, 这是因为JDK 的实现对于父类加载器是引导类加载器的状况, getParent()方法返回 null。


    3.加载过程 app

                    

        在进行类加载时,首先会自底向上挨个检查是否已经加载了指定类,若是已经加载则直接返回该类的引用。
        若是到最高层也没有加载过指定类,那么会自顶向下挨个尝试加载,直到用户自定义类加载器,若是还不能成功,就会抛出异常。
        直接使用系统加载器加载类失败抛出的是NoClassDefFoundException异常。
        若是使用自定义的类加载器loadClass方法或者ClassLoader的findSystemClass方法加载类,抛出的是 ClassNotFoundException。
        如下代码是除 BootstrapClassLoader 外的类加载器加载流程:
    

// 检查类是否已被装载过
Class c = findLoadedClass(name);
if (c == null ) {
     // 指定类未被装载过
     try {
         if (parent != null ) {
             // 若是父类加载器不为空, 则委派给父类加载
             c = parent.loadClass(name, false );
         } else {
             // 若是父类加载器为空, 则委派给启动类加载加载
             c = findBootstrapClass0(name);
         }
     } catch (ClassNotFoundException e) {
         // 启动类加载器或父类加载器抛出异常后, 当前类加载器将其
         // 捕获, 并经过findClass方法, 由自身加载
         c = findClass(name);
     }
}
    4.加载类时的几个原则

        4.1. 代理/双亲委托
                类加载器在尝试本身去查找某个类的字节代码并定义它时, 会先代理给其父类加载器, 父加载器也会请求它的父加载器代理加载, 依次类推。

                在介绍代理模式背后的动机以前, 首先须要说明一下 Java 虚拟机是如何断定两个 Java 类是相同的。
                Java 虚拟机不只要看类的全名是否相同, 还要看加载此类的类加载器是否同样。
                只有二者都相同的状况, 才认为两个类是相同的。

                即使是一样的字节代码, 被不一样的类加载器加载以后所获得的类,也是不一样的。
                下面经过实例代码来讲明:                

package com.example; 

 public class Sample { 
    private Sample instance; 

    public void setSample(Object instance) { 
        this.instance = (Sample) instance; 
    } 
 }
            测试Java类是否相同:             
public class ClassIdentity {

	public static void main(String[] args) {
		new ClassIdentity().testClassIdentity();
	}
	
	public void testClassIdentity() {
		String classDataRootPath = "C:\\Documents and Settings\\Administrator\\workspace\\Classloader\\classData";
		FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
		FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
		String className = "com.example.Sample";	
		try {
			Class class1 = fscl1.loadClass(className);
			Object obj1 = class1.newInstance();
			Class class2 = fscl2.loadClass(className);
			Object obj2 = class2.newInstance();
			Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);
			setSampleMethod.invoke(obj1, obj2);
		} catch (Exception e) {
			e.printStackTrace();
		} 
	}
}

        测试Java类是否相同的代码运行结果:        

java.lang.reflect.InvocationTargetException 
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597) 
at classloader.ClassIdentity.testClassIdentity(ClassIdentity.java:26) 
at classloader.ClassIdentity.main(ClassIdentity.java:9) 
Caused by: java.lang.ClassCastException: com.example.Sample 
cannot be cast to com.example.Sample 
at com.example.Sample.setSample(Sample.java:7)
        运行结果能够看到,运行时抛出了 java.lang.ClassCastException异常。
        虽然两个对象 obj1和 obj2的类的名字相同,可是这两个类是由不一样的类加载器实例来加载的,所以不被 Java 虚拟机认为是相同的。
        不一样的类加载器为相同名称的类建立了额外的名称空间。
        相同名称的类能够并存在 Java 虚拟机中,只须要用不一样的类加载器来加载它们便可。
        不一样类加载器加载的类之间是不兼容的,这就至关于在 Java 虚拟机内部建立了一个个相互隔离的 Java 类空间。

        
        由于在此模型下用户自定义的类装载器不可能装载应该由父加载器装载的可靠类,从而防止不可靠甚至恶意的代码代替由父加载器装载的可靠代码。
        例如全部 Java 应用都至少须要引用 java.lang.Object类,也就是说在运行的时候,java.lang.Object这个类须要被加载到 Java 虚拟机中。
        若是这个加载过程由 Java 应用本身的类加载器来完成的话,极可能就存在多个版本的 java.lang.Object类,并且这些类之间是不兼容的。
        经过代理模式,对于 Java 核心库的类的加载工做由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。
        可是实际上,类加载器的编写者能够自由选择不用把请求委托给parent加载器,也就是能够违背代理原则, 但正如上所说,会带来安全的问题。

        4.2. 可见性/隔离性
            
            被子加载器加载的类拥有被父加载器加载的类的可见性,但反之则否则。
            自定义类加载器拥有三个其本类加载器加载的全部类的可见性,可是处于不一样分支的自定义类加载器相互之间不具备可见性。
            所谓不可见即不能直接互相访问, 也就是即便它们装载同一个类,也会拥有不一样的命名空间, 会有不一样的Class实例。                         
            但若是持有类所对应的Class对象的引用, 仍是能够访问另外一命名空间的类。正如示例代码中咱们经过反射的方式实现了不一样加载器加载的类的访问。        

Class<?> class1 = fscl1.loadClass(className); 
        Object obj1 = class1.newInstance(); 
        Class<?> class2 = fscl2.loadClass(className); 
        Object obj2 = class2.newInstance(); 
        Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class); 
        setSampleMethod.invoke(obj1, obj2);

            一样咱们也能够利用可见性原则实现不一样加载器加载的类之间的互访, 只须要对Sample类稍加改造, 让其实现ISample接口。

public interface ISample {
	public void setSample(Object instance)
}
public class Sample implements ISample { 
    private Sample instance; 

    public void setSample(Object instance) { 
        this.instance = (Sample) instance; 
    } 
 }


String classDataRootPath = "C:\\Documents and Settings\\Administrator\\workspace\\Classloader\\classData";
		FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
		FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
		String className = "com.example.Sample";	
		try {
			Class<?> class1 = fscl1.loadClass(className);
			ISample obj1 = (ISample)class1.newInstance();
			Class<?> class2 = fscl2.loadClass(className);
			ISample obj2 = (ISample)class2.newInstance(); 
                        obj1.setSample(obj2);                 
                } catch (Exception e) {
			e.printStackTrace();
		}             

            上面示例的代码中咱们使用自定义类加载器加载了Sample类, 而接口ISample是由系统类加载器加载的, 因此ISample对于Sample是具备可见性的, 所以转型成功。

        4.3. 惟一性

            咱们继续分析上面的示例, 使用下面的代码作转型   

Class<?> class1 = fscl1.loadClass(className);
Sample obj1 = (Sample)class1.newInstance();

            若是咱们尝试直接使用如上的代码来访问, 会抛出 ClassCastException 异常。
            由于在 Java 中, 即便是同一个类文件,若是是由不一样的类加载器加载的, 那么它们的类型是不相同的。
            在上面的例子中class1是由自定义类加载器加载的, 而Sample变量类型声名和转型里的Sample类倒是由类加载器(默认为 AppClassLoader)加载的, 所以是彻底不一样的类型, 因此会抛出转型异常。

            类加载器的代理/双亲委托原则, 决定了每个类在一个加载器里最多加载一次,  固然多个加载器能够加载同一个类。
            每一个类对象在各自的namespace内,对类对象进行比较或者对实例进行类型转换时,会同时比较各自的名字空间。

    5.自定义类加载器
        自定义加载器给Java语言增长了不少灵活性,主要的用途有

  • 能够从多个地方加载类,好比网络上,数据库中,甚至即时的编译源文件得到类文件;
  • 类加载器能够在运行时原则性的加载某个版本的类文件;
  • 类加载器能够动态卸载一些类;
  • 类加载器能够对类进行解密解压缩后再载入类

    下面的代码是上面示例中用到的自定义类加载器的实现类, 功能是从本地文件系统加载类  

package classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class FileSystemClassLoader extends ClassLoader {

	private String rootDir;

	public FileSystemClassLoader(String rootDir) {
		this.rootDir = rootDir;
	}

	protected Class<?> findClass(String name) throws ClassNotFoundException {
		byte[] classData = getClassData(name);
		if (classData == null) {
			throw new ClassNotFoundException();
		}
		else {
			return defineClass(name, classData, 0, classData.length);
		}
	}

	private byte[] getClassData(String className) {
		String path = classNameToPath(className);
		try {
			InputStream ins = new FileInputStream(path);
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			int bufferSize = 4096;
			byte[] buffer = new byte[bufferSize];
			int bytesNumRead = 0;
			while ((bytesNumRead = ins.read(buffer)) != -1) {
				baos.write(buffer, 0, bytesNumRead);
			}
			return baos.toByteArray();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	private String classNameToPath(String className) {
		return rootDir + File.separatorChar
				+ className.replace('.', File.separatorChar) + ".class";
	}
}
        在该代码中必需要说明的一点是,  该自定义类加载器的并无指定父加载器。
         JVM规范中规定在不指定父类加载器的状况下, 默认采用系统类加载器做为其父加载器, 因此在使用该自定义类加载器时, 须要加载的类不能在类路径中, 不然的话依据类加载器的代理/委托原则, 待加载类会由系统类加载器加载,
        这样自定义类加载器想要实现的, 诸如类的热替换, 多版本共存, 将变的不可实现。
        若是咱们必定想要把自定义加载器须要加载的类放在类路径中, 应该怎么办呢, 答案是把自定义类加载器的父加载器设置为null。
        JVM规范中规定若是用户自定义的类加载器将父类加载器强制设置为null,那么会自动将启动类加载器设置为当前用户自定义类加载器的父类加载器。
        须要注意的是自定义类加载器不一样的父加载器决定了加载类的不一样的可见性。
        下面的代码示例是一个把自定义类加载器的父加载器设置为null时, 如何处理加载类的不一样可见性。
class CustomCL extends ClassLoader { 

    private String basedir; // 须要该类加载器直接加载的类文件的基目录
    private HashSet dynaclazns; // 须要由该类加载器直接加载的类名

    public CustomCL(String basedir, String[] clazns) { 
        super(null); // 指定父类加载器为 null 
        this.basedir = basedir; 
        dynaclazns = new HashSet(); 
        loadClassByMe(clazns); 
    } 

    private void loadClassByMe(String[] clazns) { 
        for (int i = 0; i < clazns.length; i++) { 
            loadDirectly(clazns[i]); 
            dynaclazns.add(clazns[i]); 
        } 
    } 

    private Class loadDirectly(String name) { 
        Class cls = null; 
        StringBuffer sb = new StringBuffer(basedir); 
        String classname = name.replace('.', File.separatorChar) + ".class";
        sb.append(File.separator + classname); 
        File classF = new File(sb.toString()); 
        cls = instantiateClass(name,new FileInputStream(classF),
            classF.length()); 
        return cls; 
    }   		

    private Class instantiateClass(String name,InputStream fin,long len){ 
        byte[] raw = new byte[(int) len]; 
        fin.read(raw); 
        fin.close(); 
        return defineClass(name,raw,0,raw.length); 
    } 
    
	protected Class loadClass(String name, boolean resolve) 
            throws ClassNotFoundException { 
        Class cls = null; 
        cls = findLoadedClass(name); 
        if(!this.dynaclazns.contains(name) && cls == null) 
            cls = getSystemClassLoader().loadClass(name); 
        if (cls == null) 
            throw new ClassNotFoundException(name); 
        if (resolve) 
            resolveClass(cls); 
        return cls; 
    } 

}
        在上面的自定义类加载器中, 咱们设置了该自定义类加载器的父加载器为null, 那么当咱们在使用自定义类加载器加载的类中引用第三方的类, 例如引用了原本应该是由扩展类加载器或者系统加载器加载的类时, 就会出现不能加载的问题。
        因此咱们在上面的自定义类加载器中, 重写了loadClass方法, 修改了或者说打破了代理/委托逻辑,  自定义类加载器先尝试本身加载, 当自定义类加载器不能加载的类,  交由系统加载器来加载。        
protected Class loadClass(String name, boolean resolve) 
            throws ClassNotFoundException { 
            Class cls = null; 
            cls = findLoadedClass(name); 
            if(!this.dynaclazns.contains(name) && cls == null) 
                cls = getSystemClassLoader().loadClass(name); 
            if (cls == null) 
                throw new ClassNotFoundException(name); 
            if (resolve) 
                resolveClass(cls); 
            return cls; 
        }
     6. 类加载方式
        除了上面提到的经过自定义类加载器加载类, 咱们一般会使用下面的两种方式来加载类

        6.1. 隐式加载                

A a = new A();
               若是程序运行到这段代码时尚未A类,那么JVM会请求装载当前类的类装器来装载类。

        6.2. 显示加载                                 

//效果相同, 执行类的初始化
Class.forName("test.A");
Class.forName("test.A", true, this.getClass().getClassLoader());
//效果相同, 不执行类的初始化
getClass().getClassLoader().loadClass("test.A");
Class.forName("test.A", false, this.getClass().getClassLoader());
//效果相同, 不执行类的初始化
ClassLoader.getSystemClassLoader().loadClass("test.A");
Class.forName("test.A", false, Classloader.getSystemClassLoader());
//效果相同, 不执行类的初始化
Thread.currentThread().getContextClassLoader().loadClass("test.A")
Class.forName("test.A", false, Thread.currentThread().getContextClassLoader());
    7. 上下文类加载器( ContextClassLoader)
        类 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。
        若是没有经过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。
        Java 应用运行的初始线程的上下文类加载器是系统类加载器。

        在线程中运行的代码能够经过此类加载器来加载类和资源。
        正常的双亲委派模型中,下层的类加载器可使用上层父加载器加载的对象,可是上层父类的加载器不可使用子类加载的对象。
        而有些时候程序的确须要上层调用下层,这时候就须要线程上下文加载器来处理。     

Thread.currentThread().getContextClassLoader()
        前面提到的类加载器的代理模式并不能解决 Java 应用开发中会遇到的类加载器的所有问题。
        Java 提供了不少服务提供者接口(Service Provider Interface,SPI),容许第三方为这些接口提供实现。
        常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,如 JAXP 的 SPI 接口定义包含在 javax.xml.parsers包中。
        这些 SPI 的实现代码极可能是做为 Java 应用所依赖的 jar 包被包含进来,能够经过类路径(CLASSPATH)来找到,如实现了 JAXP SPI 的 Apache Xerces所包含的 jar 包。
        SPI 接口中的代码常常须要加载具体的实现类。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory类中的 newInstance()方法用来生成一个新的 DocumentBuilderFactory的实例。
        这里的实例的真正的类是继承自 javax.xml.parsers.DocumentBuilderFactory,由 SPI 的实现所提供的。如在 Apache Xerces 中,实现的类是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。
        而问题在于,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类通常是由系统类加载器来加载的。引导类加载器是没法找到 SPI 的实现类的,由于它只加载 Java 的核心库。
        它也不能代理给系统类加载器,由于它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式没法解决这个问题。
        线程上下文类加载器正好解决了这个问题。
        若是不作任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器。
        在 SPI 接口的代码中使用线程上下文类加载器,就能够成功的加载到 SPI 实现的类。线程上下文类加载器在不少 SPI 的实现中都会用到。
        
        在5.自定义类加载器中咱们的自定义类加载器CustomCL, 若是放到tomcat下的web应用中去使用会出现什么问题呢, 例如在自定义加载器 待加载的类中使用第三方类, 这个时候自定义加载器不能加载的类会交由系统加载器加载,  而该第三类不存在于类路径中, 只存在于该webApp下,  显然是加载不到的, 为了解决这个问题,  这个时候咱们就须要上下文类加载器来解决这个问题了。

        在给出代码以前先说下Tomcat.6的类加载器, 结构层次以下:     

+-----------------------------+
        |         Bootstrap           |
        |             |               |
        |          System             |
        |             |               |
        |          Common             |
        |         /      \            |
        |     WebApp1  WebApp2        |
        |                             |
        |                             |
        +-----------------------------+
       Webapp 类装载器:
      应用层的类装载器,每一个应用程序都会建立一个单独的类装载器。该类装载器只能本应用程序中可见。
      全部/WEB-INF/classes目录下未压缩的类文件,资源文件都由该类装载器加载。
      全部/WEB-INF/lib目录下压缩后Jar/zip文件都由该类装载器加载
  
      显然上面咱们说到的问题应该用webapp类加载器来加载第三方类, 那咱们在自定义类加载器中如何得到webapp类加载器呢,  在tomcat6中启动webapp线程的上下文类加载器被设置为webapp类加载器了, 因此咱们能够经过以下代码来完成加载。
protected Class loadClass(String name, boolean resolve) 
            throws ClassNotFoundException { 
        Class cls = null; 
        cls = findLoadedClass(name); 
        if(!this.dynaclazns.contains(name) && cls == null) 
            cls = getSystemClassLoader().loadClass(name);
        if (cls == null)
                //自定义加载器和系统加载器均不能正常加载的类, 交由上下文加载器加载
        	cls =  Thread.currentThread().getContextClassLoader().loadClass(name);
        if(cls == null)
            throw new ClassNotFoundException(name); 
        if (resolve) 
            resolveClass(cls); 
        return cls; 
    }
        其实ContextClassLoader就是Thread的一个属性而已,  咱们固然能够不使用ContextClassLoader, 本身找个地方把classLoader保存起来, 在须要获取的时候能获得此classLoader就能够。

     8. 自定义类加载器的其余应用

        8.1. 热加载
                每次建立一个新的类加载器, 咱们修改下上面示例中的ClassIdentity类, 让他能够实现热加载。           

public class ClassIdentity  extends Thread {

    public static void main(String[] args) {
        new ClassIdentity().start();
    }
    public void run() {
        while(true) {
            this.testClassIdentity();
            try {
                Thread.sleep(30 * 1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }           
        }       
    }
    public void testClassIdentity() {
        String classDataRootPath = "C:\\Documents and Settings\\Administrator\\workspace\\Classloader\\classData";
        FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
        FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
        String className = "com.example.Sample";    
        try {
            Class<?> class1 = fscl1.loadClass(className);
            Object obj1 = class1.newInstance();
            Class<?> class2 = fscl2.loadClass(className);
            Object obj2 = class2.newInstance();
            Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);
            setSampleMethod.invoke(obj1, obj2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

        运行该代码, 在运行过程当中咱们修改Sample类, 并覆盖原Sample类。

        8.2. 类加密                 指通常意义上的加密,  经过自定义加载器解密载入加密类         8.3. 应用隔离                 很是典型的应用就是web容器

相关文章
相关标签/搜索