JVM-自定义类加载器

通常情况下,我们都是直接使用系统类加载器。但是,有的时候,我们也需要自定义类加载器。
自定义类的应用场景
(1)\color{red}{加密}:Java代码可以轻易的被\color{red}{反编译},如果你需要把自己的代码进行\color{red}{加密以防止反编译},可以先将编译后的代码用某种加密算法加密,类加密后就不能再用Java的ClassLoader去加载类了,这时就需要自定义ClassLoader在加载类的时候\color{red}{先解密类},然后再加载。

(2)\color{red}{从非标准的来源加载代码}:如果你的字节码是放在数据库、甚至是在云端,就可以自定义类加载器,\color{red}{从指定的来源加载类}

(3)\color{red}{以上两种情况在实际中的综合运用}:比如你的应用需要\color{red}{通过网络来传输 Java 类的字节码},为了安全性,这些字节码经过了\color{red}{加密处理}。这个时候你就需要\color{red}{自定义类加载器}来从\color{red}{某个网络地址上}读取加密后的字节代码,接着进行\color{red}{解密和验证},最后定义出在Java虚拟机中运行的类。

举个栗子:

- 首先准备一个用 javac命令行编译成class文件

1.查看javac.exe的位置
打开cmd -输入 java -verbose - 最后俩行大致jdk位置的参考


2.该位置去jdk bin 目录下找找

3.在该位置下打开cmd
地址栏输入cmd回车

4.在盘新建一个txt文档,自定义命名为一个demo.java类做例子如:

文件的位置为E:\Wgdemo.java

public class Wgdemo {
    private String name;
    private int age;
    public Wgdemo() {

    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String toString() {
        return "Wgdemo [name=" + name + ", age=" + age + "]";
    }
}

5.用javac.exe来编译这个文件为.class文件,在刚才用地址栏打开的cmd输入
javac E:\Wgdemo.java


完成后可以看到E盘下有一个Wgdemo.class文件

- 自定义类加载器

1.打开你最喜欢的java编译器,自定义一个类加载器MyClassLoader.java需要继承ClassLoader类,并实现findClass方法。`

其中有两个是比较关键的:

loadClass():是ClassLoader的入口,加载器可根据类名来加载指定类对应的Class对象;

findClass():根据指定名来查找类;

两个方法关系紧密,如下图所示:


import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
public class MyClassLoader extends ClassLoader
{
    public MyClassLoader(){}
    
    public MyClassLoader(ClassLoader parent)
    {
        super(parent);
    }
    
    protected Class<?> findClass(String name) throws ClassNotFoundException
    {
        File file = new File("E:/Wgdemo.class");
        try{
            byte[] bytes = getClassBytes(file);
            //defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
            Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
            return c;
        } 
        catch (Exception e)
        {
            e.printStackTrace();
        }
        
        return super.findClass(name);
    }
    
    private byte[] getClassBytes(File file) throws Exception
    {
        // 这里要读入.class的字节,因此要使用字节流
        FileInputStream fis = new FileInputStream(file);
        FileChannel fc = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel wbc = Channels.newChannel(baos);
        ByteBuffer by = ByteBuffer.allocate(1024);
        
        while (true){
            int i = fc.read(by);
            if (i == 0 || i == -1)
            break;
            by.flip();
            wbc.write(by);
            by.clear();
        }
        fis.close();
        return baos.toByteArray();
    }
}

\color{red}{WgTest.java}

public class WgTest {
    public static void main(String[] args) {
        MyClassLoader mcl = new MyClassLoader(); 
        Class<?> clazz;
        Object obj;
        try {
            //Class.forName方法的作用,就是初始化给定的类
            clazz = Class.forName("Wgdemo", true, mcl);
            obj = clazz.newInstance();
            System.out.println(obj);
            //打印出我们的自定义类加载器
            System.out.println(obj.getClass().getClassLoader());
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }catch (InstantiationException e) {
            e.printStackTrace();
        }catch (IllegalAccessException e) {
            e.printStackTrace();
        }   
    }
}

自定义类加载器的核心在于对字节码文件的获取,如果是加密的字节码则需要在该类中对文件进行解密。由于这里只是演示,我并未对class文件进行加密,因此没有解密的过程。
上面是固定了加载某个.class文件,现在我们需要\color{red}{加载自定义路径下的class文件}
1.在E盘建立如下图文件夹(最后一个与包名一样最好即leetcode是你的编译器中建立的包名)Wgdemo.java内容:

public class Wgdemo2{
    public void sayHello(){
        System.out.println("Hello, I am WG");
    }
}

2.javac E:\wg\leetcode\Wgdemo2.java


3.自定义类加载器MyClassLoader2.java

package leetcode;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;

public class MyClassLoader2 extends ClassLoader {

    private String classPath;

    public MyClassLoader2( String classPath) {

        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] bytes;
        Class<?> clazz;

        try {
            //获取.class 文件的二进制字节
            bytes = getClassByte(name);
            //将二进制字节转化为Class对象
            clazz = defineClass(name,bytes,0,bytes.length);
            return clazz;
        } catch (IOException e) {
            e.printStackTrace();
        }


        return super.findClass(name);
    }


    private byte[] getClassByte(String name) throws IOException {

        String classFile = classPath + File.separator + name.replace(".", File.separator)+".class";
        System.out.println(classFile);

        File file = new File(classFile);

        FileInputStream fileInputStream = new FileInputStream(file);
        FileChannel fileChannel = fileInputStream.getChannel();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        WritableByteChannel writableByteChannel = Channels.newChannel(byteArrayOutputStream);
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        int i;
        while (true) {
            i = fileChannel.read(byteBuffer);
            if (i == 0 || i == -1) {
                break;
            }
            byteBuffer.flip();
            writableByteChannel.write(byteBuffer);
            byteBuffer.clear();
        }
        writableByteChannel.close();
        fileChannel.close();
        fileInputStream.close();

        return byteArrayOutputStream.toByteArray();

    }
}

4.测试代码修改为:
\color{red}{WgTest.java}

package leetcode;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class WgTest {

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {

        String classPath = "E:\\wg\\leetcode";
        String className = "Wgdemo2";

        MyClassLoader2 myClassLoader = new MyClassLoader2(classPath);
        Class clazz = myClassLoader.findClass(className);
    //获取类对象
        Object object = clazz.newInstance();
    //获取类方法
        Method[] methods = clazz.getDeclaredMethods();

        for(Method method:methods){
            //类对象调用方法
            method.invoke(object);
        }
    }

}

原文参考:https://blog.csdn.net/SEU_Calvin/article/details/52315125

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容