前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >第19次文章:类加载器的加密解密+内部类

第19次文章:类加载器的加密解密+内部类

作者头像
鹏-程-万-里
发布2019-09-28 18:06:04
7970
发布2019-09-28 18:06:04
举报
文章被收录于专栏:Java小白成长之路

一、类加载器的加密和解密:

在上一期的文章中,我们介绍了自定义类加载器做法的整个流程,还没有理解同学可以点击回看哈!《第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进行修饰,避免其他类的使用。如果想要让其他类使用,就可以直接定义一个外部类就好了,没有必要定义一个内部类了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-05-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java小白成长之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 二、线程上下文类加载器
    • 1、双亲委托机制以及类加载器的问题
      • 2、线程类加载器是为了抛弃双亲委派加载链模式
        • 3、下面我们简单测试一下上面讲述的内容
        • 三、内部类的介绍
          • 1、内部类 嵌套类
            • 静态内部类
            • 非静态内部类
          • 2、基本用法
            • 3、个人对内部类的一点理解
            相关产品与服务
            云数据库 MySQL
            腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档