通常情况下,我们都是直接使用系统类加载器。但是,有的时候,我们也需要自定义类加载器。
自定义类的应用场景
(1):Java代码可以轻易的被
,如果你需要把自己的代码进行
,可以先将编译后的代码用某种加密算法加密,类加密后就不能再用Java的ClassLoader去加载类了,这时就需要自定义ClassLoader在加载类的时候
,然后再加载。
(2):如果你的字节码是放在数据库、甚至是在云端,就可以自定义类加载器,
。
(3):比如你的应用需要
,为了安全性,这些字节码经过了
。这个时候你就需要
来从
读取加密后的字节代码,接着进行
,最后定义出在Java虚拟机中运行的类。
举个栗子:
- 首先准备一个用 javac命令行编译成class文件
1.查看javac.exe的位置
打开cmd -输入 java -verbose - 最后俩行大致jdk位置的参考

2.该位置去
jdk bin 目录下找找
3.在该位置下打开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();
}
}
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文件,现在我们需要
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.测试代码修改为:
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