java基础增强之类加载器学习笔记

概念:类加载器就是加载类的工具

   java中的类加载器:java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap, ExtClassLoader,AppClassLoader
   类加载器也是一个java类,因此类加载器也需要被加载,所以必须有第一个类加载器不是java类,这个类就是BootStrap,它是用C++
   语言写的一个二进制代码。

   AppClassLoader:负责加载普通classpath下的类
   一个类如果类加载器为null,不是没有类加载器,而是它的类加载器为BootStrap,因为BootStrap是C++写的,所以
   通过getClassLoader()是无法获取到它的。下面是打印一个类中的所有类加载器名称的代码:
    ClassLoader classLoader = JavaClassLoader.class.getClassLoader();
    while(classLoader != null){
        System.out.println(classLoader.getClass().getName());
        classLoader = classLoader.getParent();
    }
    System.out.println(classLoader);

   ExtClassLoader:负责加载JRE/lib/ext/*.jar,如果自己的一个类需要被ExtClassLoader加载,打个jar包丢到这个目录下就可以了

   BootStrap:负责加载JRE/lib/rt.jar

  Java中类加载器的关系图如下:

  
   一个类的加载顺序:如果一个类交给AppClassLoader去加载,它不会去加载,而是交给它的父亲ExtClassLoader,ExtClassLoader也
   不会去加载,而是交给爷爷BootStrap,爷爷是最顶层的类加载器了,于是开始处理,处理时会先找曾经有没有加载过这个类,加载过
   就直接给你了,没加载过就开始去找这个类,找到了就处理,没找到就交给ExtClassLoader,
   ExtClassLoader也是看有没有原来就加载过了,加载过就直接拿出来用不会重新加载,没加载过就开始找这个类了,又没找到,
   又交给AppClassLoader,如果找到了就处理,没找到呢?咋办?抛异常,抛出
   ClassNotFoundException,那为什么它不交给下级去找了呢?因为它是发起者,最先是交给它处理的,所以如果到了他这一级还没找到,
   就抛异常了。这就是java的委托机制,最顶层的爷爷类BootStrap是很累的,每个类的加载都要经过他的手,但是这样有个好处,就是
   集中管理。
   有个问题需要注意:如果项目中有两个类,一个在JRE/lib/ext/*.jar的jar包中,一个在classpath下(也就是你自己编写的一个类),
   那么肯定是先加载JRE/lib/ext/*.jar下的

   模版方法设计模式:有一个方法loadClass,子类跟父类大部分代码都一样,但是方法中有点小细节不一样,这时候,可以把这些小细节定义成一个抽象方法findClass,

让子类去实现这个抽象方法,子类实现这个findClass的抽象方法之后,就具有了loadClass的功能了,而且还是具有自己各色的loadClass功能,因为loadClass里面

调用了findClass,而findClass方法是子类自己实现的。

 

如何定义自己的类加载器呢?

实现自定义类加载器只需要继承ClassLoader类就可以了,然后覆盖findClass方法,在findClass方法
里实现你自己想要对字节码文件的操作。这里有点疑问了,
为什么不是覆盖loadClass方法呢?

这个本来是需要覆盖loadClass(String name)方法的,这个方法底层是先去找内存
中是不是已经加载过这个类,如果加载了这个类就给你这个类,没有加载过就去找父类,
一级一级往上找,父类加载器能找到这个类就给父类处理,如果父类都找不到那么最后回到
发起者来了,发起者在loadClass方法里调用findClass方法加载类,找到了这个类就处理,
没找到就报异常了

如果我自己要写一个类加载器是不是要覆盖loadClass方法呢?不用了,因为我们自己写
类加载器就是想用我们自己的加载器去加载类,找父类的流程都是一样的,没有必要重写,loadClass中
已经有了找父类的流程代码,只是具体的找到字节码文件后的操作细节是不一样的,需要我们实现,
所以直接覆盖findClass方法就可以了。


什么时候子类需要覆盖父类的loadClass方法呢?当我们不需要找父类了,比如说我自己写个System类,这个
类是在rt.jar里面的,而BootStrap类是负责加载rt.jar的,意思就是说我写的类一般来说是不会被自己
的类加载器加载的,因为他每次都要找父类,而父类又能够处理这个Sytem类,这时候,如果我不想让父类处理我
自己写的这个System类,而是用我自己的类加载器来处理,那么我就重写loadClass方法,覆盖掉原来ClassLoader中的找父类
的流程代码就行了。

  以下是 定义自己的类加载器,对类进行简单的加密解密操作:
   
   
public class MainClass {

        /**
         * 需求:
         * 编写一个对文件内容进行简单加密的程序
         * 编写一个自己的类加载器,可以对加密过的类进行加载和解密
         * 编写一个程序调用自己编写的这个类加载器来加载类
         *
         * 思路:
         * 编写一个测试类ClassLoaderTest.java
         * 定义一个用于加密解密字节码文件的类Cypher.java,提供加密解密的方法
         * 定义一个类加载器MyClassLoader.java,继承ClassLoader并重写findClass方法,在findClass方法中对字节码文件进行解密
         * 定义测试类MainClass,用于测试结果
         *
         * @throws Exception
         */
        public static void main(String[] args) throws Exception {
            String inClassDir = "F:\\Soft Installer\\Eclipse_Workspace\\exam\\bin\\com\\javaenhance\\classloader\\ClassLoaderTest.class";
            String outClassDir = "F:\\Soft Installer\\Eclipse_Workspace\\exam\\temp\\ClassLoaderTest.class";
            Cypher cy = new Cypher(inClassDir, outClassDir);
            cy.decodeAndEncodeClass();
            //需要加载的字节码文件的所在路径
            MyClassLoader loader = new MyClassLoader("F:\\Soft Installer\\Eclipse_Workspace\\exam\\bin\\com\\javaenhance\\classloader");
            //用自定义的类加载器加载字节码文件
            Class clazz = loader.loadClass("ClassLoaderTest");
            //此处没有用ClassLoaderTest接收是因为该类已经被加密过,如果直接用其接收,编译肯定有问题了
            //可以让其继承一个父类,然后用起父类接收
            Object obj = clazz.newInstance();
        }

    }

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

    /**
     * 加密解密字节码文件
     * */
    public class Cypher {
        //需要加密的字节码文件的路径
        private String inClassDir;
        //加密后的字节码文件的存放路径
        private String outClassDir;
        
        public Cypher(){}
        
        public Cypher(String inClassDir, String outClassDir){
            this.inClassDir = inClassDir;
            this.outClassDir = outClassDir;
        }
        
        public void decodeAndEncodeClass() throws Exception{
            //将字节码文件加密,加密后就只有自己的这个类加载器能够加载我的class文件了
            FileInputStream fis = new FileInputStream(inClassDir);
            FileOutputStream fos = new FileOutputStream(outClassDir);
            cypher(fis, fos);
            fis.close();
            fos.close();
        }
        
        /**
         * 加密解密方法:
         * @param is 需要加密或解密的输入流
         * @param os 加密后解密后的输出流
         * @throws IOException
         * */
        public static void cypher(InputStream is, OutputStream os) throws IOException{
            int len = 0;
            while((len=is.read()) != -1){
                os.write(len ^ 0xff);
            }
        }
    }

    
    public class ClassLoaderTest {
        
        public ClassLoaderTest(){
            System.out.println("我是ClassLoaderTest构造方法!");
        }
        
        public void show(){
            System.out.println("哈哈,I'm ClassLoaderTest's show method!");
        }
    }


    
    import java.io.ByteArrayOutputStream;
    import java.io.FileInputStream;

    public class MyClassLoader extends ClassLoader{

        public MyClassLoader(){
            
        }
        
        public MyClassLoader(String classDir){
            this.classDir = classDir;
        }
        private String classDir;
        
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            String fileName = classDir + "\\" + name + ".class";
            FileInputStream is;
            ByteArrayOutputStream os;
            byte[] b = null;
            try {
                is = new FileInputStream(fileName);
                os = new ByteArrayOutputStream();
                Cypher.cypher(is,os);
                b = os.toByteArray();
                is.close();
                os.close();
                return defineClass(null, b, 0, b.length);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return super.findClass(fileName);
        }

    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值