一、类加载器的加密和解密:
在上一期的文章中,我们介绍了自定义类加载器做法的整个流程,还没有理解同学可以点击回看哈!《第18次文章:JVM中的类加载机制》。在日常生活中,我们有时候需要将一个类文件进行加密处理,然后再传送给用户。此时,我们在设计自定义类加载器的时候,就需要考虑加密类文件的解密处理了。下面,我们来简单的介绍一种加密解密文件系统的类加载器。此处我们的重心在于介绍加解密文件系统,所以我们使用一种简单的异或操作当做加密算法,简单的介绍一下整个流程。
1、为了进行加解密操作,我们首先需要准备加密文件,所以,我们提前需要编写一个加密程序,对原始的class文件进行异或操作。具体的方法就是先利用输入流读取原始class文件,然后利用输出流对其加密输出。实现细节如下所示:
代码语言:javascript复制package com.peng.test;/** * 简单测试加密解密操作 */public class EncrptUtil { public static void main(String[] args) { encrpt("G:/java学习/test/HelloWorld.class","G:/java学习/test/com/peng/test/HelloWorld.class"); } public static void encrpt(String source,String dest) { FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream(source); fos = new FileOutputStream(dest); int temp = 0; while(-1 != (temp = fis.read())) { fos.write(temp^0xff);//取反,相当于加密操作 } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally {//关闭IO流 if(fis != null) { try { fis.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(fos != null) { try { fos.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }}
tips:其重点就在于对读入文件的异或操作,属于将输入文件的每个字节转化为二进制数,然后和十六进制数0xff进行异或操作,最后输出得到一个被取反的新的class文件。
2、得到加密之后的class文件之后,我们先来使用上期编写的文件系统类加载器来加载这个加密后的类。
代码语言:javascript复制 FileSystemClassLoader loader = new FileSystemClassLoader("G:/java学习/test/com/peng/test"); Class<?> c = loader.loadClass("HelloWorld"); System.out.println(c);
控制台输出失败信息:
tips:根据提示信息,主要报错的原因在于,当前加密后的class文件属于文件系统类加载器无法识别的类信息。在之前的类加载器中,由于加密class文件的格式问题,读取加密文件之后,无法使用defineClass这个方法,得到class文件信息。这样就保证了我们文件加密的安全性。
3、与其他加解密算法类似,我们根据加密的异或操作,对应的再次使用异或操作,相当于解密,得到原始文件,然后再重新定义class文件。我们在FileSystemClassLoader的基础上稍加更改,得到一个解密文件系统类加载器DecrptClassLoader。由于两者基本相差无几,我们主要展示改动的部分:
当双亲委派之后,仍然无法找到待加载的类之后,我们需要获取待加载类的信息,在FileSystemClass中的获取方式为:
代码语言:javascript复制 try {//读写待加载类的文件 is = new FileInputStream(classPath); byte[] buffer = new byte[1024]; int temp = 0; while(-1 != (temp=is.read(buffer))) { baos.write(buffer, 0, temp); } return baos.toByteArray(); }
在DecrptClassLoader中,我们做出的改动为再次应用异或操作:
代码语言:javascript复制 try {//读写待加载类的文件 is = new FileInputStream(classPath); int temp = 0; while(-1 != (temp=is.read())) { baos.write(temp^0xff);//对temp进行取反操作,相当于解密 } return baos.toByteArray(); }
4、得到解密文件系统类加载器之后,我们再次对加密class文件进行加载,查看结果:
代码语言:javascript复制 DecrptClassLoader loader = new DecrptClassLoader("G:/java学习/test/com/peng/test"); Class<?> c = loader.findClass("HelloWorld"); System.out.println(c);
控制台输出结果:
tips:如上图所示,控制台正确输出加密后的class文件的信息。由此我们就基本完成了对一个class文件加密解密的操作。当我们在实际使用的时候,需要的加密和解密的算法当然会更加复杂,但是需要改动的也就是加解密算法这部分内容了,整个设计的基本流程不会有大的改动。
二、线程上下文类加载器
1、双亲委托机制以及类加载器的问题
(1)一般情况下,保证同一个类中所有关联的其他类都是由当前类加载器所加载的。比如:classA本身在Ext下找到,那么他里面new出来的一些类也就只能用Ext去查找了(不会低一个级别),所以有些明明App可以找到的,却找不到了。
(2)JDBC API ,他有实现的driven部分(MySQL/sql server),我们的JDBC API都是由Boot或者Ext中载入的,但是JDBC driver 却是由Ext或者App来载入,那么就有可能找不到driver了。在java领域中,其实只要分成这种api SPI(service provide interface,特定厂商提供)的,都会遇到此问题。
(3)常见的SPI的JDBC、JCE、JNDI、JAXP和JBI等。这些SPI的接口由java核心库来提供,如JAXP的SPI接口定义包含在javax.xml.parsers包中,SPI的接口是java核心库的一部分,是由引导类加载器来加载的;SPI实现的java类一般是由系统类加载器来加载的。引导类加载器是无法找到SPI的实现类的,因为它只加载java的核心库。
通常当你需要动态加载资源的时候,我们至少有三个ClassLoader可以选择:
-系统类加载器或应用类加载器
-当前类加载器
-当前线程类加载器
2、线程类加载器是为了抛弃双亲委派加载链模式
每个线程都有一个关联的上下文类加载器,如果你使用new Thread()的方式生成新的线程,新线程将继承其父线程的上下文类加载器。如果程序对线程上下文类加载器没有任何改动的话,程序中所有的线程都将使用系统类加载器作为上下文类加载器。
3、下面我们简单测试一下上面讲述的内容
代码语言:javascript复制package com.peng.test;/** * 线程上下文类加载器的测试 */public class Demo05 {
public static void main(String[] args) throws Exception { ClassLoader loader = Demo05.class.getClassLoader(); System.out.println("##loader##:" loader); ClassLoader loader2 = Thread.currentThread().getContextClassLoader();//获取上下文类加载器 System.out.println("##loader2##:" loader2); Thread.currentThread().setContextClassLoader(new FileSystemClassLoader("G:/java学习/test"));//重新设置上下文类加载器 ClassLoader loader3 = Thread.currentThread().getContextClassLoader(); System.out.println("##loader3##:" loader3);//打印重新设置之后的上下文类加载器 Class<?> c = loader3.loadClass("com.peng.test.Demo01");//我们使用 System.out.println("##c##:" c); System.out.println("##c的类加载器##:" c.getClassLoader());//由于双亲委派机制的原因,在获取c的类加载器的时候,还是应用程序类加载器 }}
我们查看一下结果:
tips:
(1)我们首先获取到当前线程类加载器loader,由于我们并没有更改上下文类加载器,所以,我们获取到的上下文类加载器loader2也是和loader一样,都是应用程序类加载器AppClassLoader。
(2)在获取loader3的时候,我们提前重新设置了上下文类加载器,所以最后得到的loader3为我们更改后的文件系统类加载器。使用文件系统类加载器对Demo01类进行加载,最终可以返回类的加载信息。但是当我们获取c的类加载器的时候,我们发现其类加载器是APP类加载,并不是我们设置的文件系统类加载器。造成这个结果的原因就是双亲委派机制。当我们将Demo01类送给FileSystemClassLoader进行加载的时候,首先是双亲委派机制运行,直接交给了AppClassLoader进行加载,JVM发现应用程序类加载可以加载该类,此时就不会再将Demo01向下传递了。所以最后加载Demo01类的类加载器是AppClassLoader而不是我们自定义的FileSystemClassLoader。
三、内部类的介绍
由于内部类的相关内容主要是用法上的介绍,学习代码主要以语法测试为主,不具有任何实际意义,所以在此处我们不放入相关代码。
1、内部类 嵌套类
静态内部类
非静态内部类
-普通内部类(也称为:成员内部类):在一个类(外部类)中直接定义的内部类
-匿名内部类
-方法内部类:在一个方法(外部类的方法)或代码块中定义的内部类
注意:内部类仍然是一个独立的类,在编译之后,内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号。内部类可以使用修饰符(public,protected,default,private)。
例如:
在Demo01类中,我们定义了四个内部类分别为静态内部类StaticNetClass、方法内部类LocalClass、普通内部类InnerClass和匿名内部类,由于匿名内部类没有设定名称,所以编译的时候,为了加以区分,编译器使用$1进行标注。
2、基本用法
(1)静态内部类基本用法:
-静态内部类可以包含静态成员、非静态成员。
-静态内部类可以直接调用外部类的静态属性、静态方法。但不能调用外部类的普通属性、普通方法。
-在不相关类中,可以直接创建静态内部类的对象(不需要通过所在外部类)
-静态内部类实际上和外部类联系很少,也就是命名空间上的联系。
(2)成员内部类基本用法:
-成员内部类就像一个成员变量一样存在于外部类中。
-成员内部类可以访问外部类的所有成员(包含:private的)
-成员内部类的this指内部类对象本身。要拿到外部类对象可以使用:外部类名 .this
注意:成员内部类的对象是一定要绑定到一个外部类的对象上的。因此,创建成员内部类对象时,需要持有外部类对象的引用。因此,要先有外部类对象,后有成员内部类对象。成员内部类不能有静态成员。
3、个人对内部类的一点理解
经过对内部类的学习之后,我认为可以将内部类看做一个外部类的属性就好了。内部类的存在意义其实也就是方便对外部类属性的调用。在一定的程度上,对外部类的一些功能进行集成,减少不同开发者直接的耦合性,增强内聚性,所以在使用的时候,一般都会对内部类用private进行修饰,避免其他类的使用。如果想要让其他类使用,就可以直接定义一个外部类就好了,没有必要定义一个内部类了。