简介:
在Java语言里面提供有一个系统的环境变量:CLASSPATH,这个环境变量的主要作用就是在JVM启动的时候进行类加载路径的定义,在JVM里面可以根据类加载器进行指定路径中类的加载(找到了类的加载器就找到了类的来源)
类加载器在加载之后的结果在程序中都是用字节(byte)来描述。
要想获得类的加载器要通过ClassLoader来获取,而要想获取ClassLoader就必须通过Class类(反射的根源)实现:
public ClassLoader getClassLoader();
当获取了ClassLoader之后,还可以继续获取其父类的ClassLoader类对象:
public final ClassLoader getParent();
类加载之后会保存到JVM的内存之中,而内存中的类要使用对象来描述,对象是通过Class类的反射来操作,而通过反射也可以获取ClassLoader对象
代码实现:
class Message{
}
public class MAIN {
public static void main(String[] args) {
Class<? extends Message> messageClass = Message.class;
System.out.println(messageClass.getClassLoader()); // 获取当前类加载器
System.out.println(messageClass.getClassLoader().getParent()); // 获取父类加载器
System.out.println(messageClass.getClassLoader().getParent().getParent()); // 获取父类的父类加载器;返回为空
}
}
结果:
jdk.internal.loader.ClassLoaders$AppClassLoader@63947c6b
jdk.internal.loader.ClassLoaders$PlatformClassLoader@776ec8df
null
所有的类加载器都是由上向下执行的(系统类加载器是看不见的)
当获得了类加载的时候,就可以使用类加载来实现类的反射加载处理。
自定义加载器:
清楚了类加载器的结构之后就可以进行自定义的类加载器的使用,有一点需要记住的就是自定义类加载器是在所有系统类加载器的最后。
作用:
系统类中提供的类加载器都是根据CLASSPATH路径进行类加载的,而如果拥有了自定义的类加载器就可以由自己任意指定类的加载位置,比如从磁盘的某个位置或者某个服务器的某个位置上加载,可以灵活地改变加载位置以达到灵活开发的目的,加载后会得到一个字节数组,从而使用加载的类。
实现步骤:
1、将一个随便编写的程序类保存在磁盘上:
public class Message {
public void send(){
System.out.println("【发送消息】www.baidu.com");
}
}
2、将此类拷贝到D盘上进行编译处理,且不打包;此时没有打包处理,所以这个类无法通过CLASSPATH正常加载:
3、自定义一个类加载器并且继承自ClassLoader类,在ClassLoader类中给用户提供有一个字节转换为类结构的方法:
protected final Class<?> defineClass(byte[] b, int off, int len) throws ClassFormatError // protect只允许在子类中出现
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class CustomClassLoader extends ClassLoader{
private static final String MESSAGE_CLASS_PATH = "D:" + File.separator + "Message.class";
/**
* 进行指定类的加载
* @param className 类的名称
* @return 返回一个指定类的Class对象
* @throws Exception 如果类不存在则不进行加载
*/
public Class<?> loadData(String className) throws Exception{
byte[] data = this.loadClassData(); // 读取二进制文件
if (data != null){
super.defineClass(className,data,0,data.length);
}
return null;
}
private byte[] loadClassData() throws Exception{ // 通过文件进行类的加载
InputStream input = null;
ByteArrayOutputStream bos = null; // 将数据加载到内存之中
byte[] data = null;
try {
input = new FileInputStream(new File(MESSAGE_CLASS_PATH)); // 文件流加载
input.transferTo(bos); // 读取数据
data = bos.toByteArray(); // 将所有读取的字节数据取出
}catch (Exception e){
e.printStackTrace();
}finally {
if (input != null){
input.close();
}if (bos != null){
bos.close();
}
}
return data;
}
}
4、编写测试类实现类加载控制:
下面编写一个类进行指定类的加载,路径指定为事先编译好的claa文件,通过IO操作读取然后进行类加载
import java.io.*;
public class CustomClassLoader extends ClassLoader{
private static final String MESSAGE_CLASS_PATH = "D:" + File.separator + "Msg.class";
/**
* 进行指定类的加载
* @param className 类的名称
* @return 返回一个指定类的Class对象
* @throws Exception 如果类不存在则不进行加载
*/
public Class<?> loadData(String className) throws Exception{
byte[] data = this.loadClassData(); // 读取二进制文件
if (data != null){
return super.defineClass(className,data,0,data.length);
}
return null;
}
private byte[] loadClassData() throws Exception{ // 通过文件进行类的加载
InputStream input = null;
ByteArrayOutputStream bos = null; // 将数据加载到内存之中
byte[] data = null;
try {
bos = new ByteArrayOutputStream(); // 实例化内存流
input = new FileInputStream(new File(MESSAGE_CLASS_PATH)); // 文件流加载
input.transferTo(bos); // 读取数据
data = bos.toByteArray(); // 将所有读取的字节数据取出
}catch (Exception e){
e.printStackTrace();
}finally {
if (input != null){
input.close();
}if (bos != null){
bos.close();
}
}
return data;
}
}
import java.lang.reflect.Method;
public class MAIN {
public static void main(String[] args) throws Exception {
CustomClassLoader classLoder = new CustomClassLoader(); // 实例化自定义类的类加载器
Class<?> cls = classLoder.loadData("com.ul.cn.Msg");// 进行类的加载
Object obj = cls.getDeclaredConstructor().newInstance(); // 实例化对象
Method sendMethod = cls.getDeclaredMethod("send"); // 找到send()方法
sendMethod.invoke(obj); // 掉用send()方法
}
}
输出结果:
通过以上的操作我们可以发现,类不一定要在CLASSPATH环境下,只要是一个类结构就可以从任意指定的路径进行加载然后实例化使用。
这个操作有一个好处就是:在进行网络程序的开发时,可以通过远程的服务器来确定类的功能。
客户端有一个程序需要更新,那么就将更新文件存放在服务器端,然后进行网络类的加载,此时不管是什么加载器,只要加载进来的是一个二进制的文件,就能够正常完成所有相关操作,这样的话,当服务器端的文件进行改变的时候,客户端也会及时地进行响应。
此时再观察加载器的情况:
import java.lang.reflect.Method;
public class MAIN {
public static void main(String[] args) throws Exception {
CustomClassLoader classLoder = new CustomClassLoader(); // 实例化自定义类的类加载器
System.out.println(cls.getClassLoader());
System.out.println(cls.getClassLoader().getParent());
System.out.println(cls.getClassLoader().getParent().getParent());
}
}
输出结果:
如果此时定义一个类的名字为 java.lang.String 并且利用自定义类加载器进行加载,这个类是不会被加载的,因为Java之中针对于类加载器提供有双亲加载机制,如果现在要加载的程序类是由系统提供的类,则由系统类进行加载,如果现在开发者提供的类名称相同,那么系统为了保证安全性,绝对不会加载,此时就需要双亲加载机制。
未完待续。。。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)