有了解jvm的同学应该都知道,在jvm中有一片区域叫元数据区,而在这个区域中存放的一个很重要的数据就是java类的class实例数据。在java中创建一个类的对象之前,必须能够在jvm中找到其对应的class数据。而类的class的装载经历了:1:寻找class所在文件并转化为元数据区的运行时数据结构构造出class对象,2:验证(class文件校验),3:准备(内存分配),4:解析(类的二进制数据中的符号引用替换成直接引用)。验证,准备,解析三个阶段是jvm的底层自动功能,修改他们的需求基本没有,而第1个过程,我们却可以想办法去改动以实现特定的需求。而第一个过程的控制,就是类加载器的作用范围了。我从以下几个方面介绍类加载器:
- 类加载器介绍
- 自定义类加载器
- 自定义类加载器作用意义
一,类加载器介绍(引用地址https://blog.youkuaiyun.com/djokermax/article/details/81539639)
1,三种类型的类加载器:引导类加载器,扩展类加载器,系统类加载器。引导类加载器引导类加载器是用 本地代码实现的类加载器,它负责将 <JAVA_HOME>/lib下面的核心类库 或 -Xbootclasspath选项指定的jar包等 虚拟机识别的类库 加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以 不允许直接通过引用进行操作。
2,扩展(Extension)类加载器:扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的,它负责将 <JAVA_HOME >/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库 加载到内存中。开发者可以直接使用标准扩展类加载器。
3,系统(System)类加载器:系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,它负责 将 用户类路径(java -classpath或-Djava.class.path变量所指的目录,即当前类所在路径及其引用的第三方类库的路径,如第四节中的问题6所述)下的类库 加载到内存中。开发者可以直接使用系统类加载器
当然还有一种比较特殊的类型就是线程上下文类加载器,这篇讲的很细https://blog.youkuaiyun.com/justloveyou_/article/details/72231425
而通常java中的类加载默认是采用双亲委派模型,即加载一个类时,首先判断自身define加载器有没有加载过此类,如果加载了直接获取class对象,如果没有查到,则交给加载器的父类加载器去重复上面过程。而java中加载器关系如下:
引导(bootstrap)加载器是扩展类(ext)加载器父类,扩展类加载器是系统类(app)加载器的父类。所以在启用一个java程序时,一个类一定会且只会被加载一次,什么类由加载器加载则是由上面我们介绍的三种类加载器所加载的目录决定。
二,自定义类加载器
想要自定义类加载器,可以继承ClassLoader,然后重写其中的findClass()方法。查看父类ClassLoader的实现,发现其加载类主要是通过loadClass()方法,而在这个方法里就是我们上面说了的双亲委派模型的实现,建议不要直接重写此方法,一般只要你将你要用自定义加载的类不要放在上面三类java实现的类加载器的加载路径下,然后,你再重写findClass方法,可以按照你的意愿去解析和寻找class二进制文件并使用defineClass解析得到class对象即可。话不多说,给出我自己的实现:
1,将map对象转化为字符串类型的实现类(等下就用自定义的加载器加载他)
public class Transfer {
public static void main(String[] s) {
Map<String,Object> map = new HashMap<String, Object>();
map.put("key1", "value1");
map.put("key2", "value2");
Map<String,Object> map1 = new HashMap<String, Object>();
map1.put("key1", "value1");
map1.put("key2", "value2");
map.put("key4", map1);
map.put("key3", "value3");
map.put("key5", "value5");
System.out.println(transfer(map));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static String transfer(Map<String,Object> map) {
Iterator<String> keys = map.keySet().iterator();
StringBuilder retStr =new StringBuilder("");
int index=0;
while(keys.hasNext()) {
String key = keys.next();
Object value = map.get(key);
if(index++!=0) {
retStr = retStr.append(";");
}
if(value instanceof Map) {
retStr = retStr.append(key+":{"+transfer((Map)value)+"}");
}else if(value instanceof String){
retStr = retStr.append(key+":"+value);
}else {
retStr = retStr.append(key+":"+value.toString());//重写对象的toString方法
}
}
return retStr.toString();
}
}
自定义加载类:
public class MyClassLoader extends ClassLoader{
public StringBuilder baseRoot;
public MyClassLoader(StringBuilder baseRoot) {
this.baseRoot = baseRoot;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
StringBuilder filePath = this.baseRoot.append("\\").append(name.substring(0, name.lastIndexOf(".")).replace(".", "\\")).append(".class");
File file = new File(filePath.toString());
if(!file.exists()) {
System.out.println("=======未查询到制定class文件");
return null;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
InputStream is = new FileInputStream(file);
byte[] buffer = new byte[1024*1024*2];//2kb
int length=0;
try {
if((length=is.read(buffer))!=-1) {
baos.write(buffer, 0, length);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
try {
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
byte[] classFileByte = baos.toByteArray() ;
// System.out.println(name.substring(name.substring(0, name.lastIndexOf(".")).lastIndexOf(".")+1));
// return defineClass(name.substring(name.substring(0, name.lastIndexOf(".")).lastIndexOf(".")+1), classFileByte, 0, classFileByte.length);
//System.out.println();
return defineClass(null, classFileByte, 0, classFileByte.length);
}
}
验证类(可以在另一个完全没有加载和包含上面的Transfer类的项目中运行)
public class TestMyLoader {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
Map map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
String basePath = "C:\\testWorkPlace";
MyClassLoader classLoader = new MyClassLoader(new StringBuilder(basePath));
String className = "mianshi.Transfer.class";
try {
Class transfer = classLoader.loadClass(className);//得到class对象
try {
Object instance = transfer.newInstance();
try {
Method method = null;
try {
method = transfer.getMethod("transfer",Map.class);//利用反射获取我们要调用的方法
} catch (NoSuchMethodException | SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(method.invoke(instance, map));
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (InstantiationException e) {
System.out.println("构造出错");
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//构造实例,这里
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行上面代码,可以看到map类型被转化为字符串类型,说明我们加载类成功,并成功的利用我们加载的class对象达到使用他的目的。
三,自定义类加载器作用意义
为了介绍的简单,下面我会把 想要被自定义类加载器加载的类叫做目的类。不知道,我在介绍类加载器以及实现自定义加载器时使用的例子,会不会让你想到java中的序列化。序列化的目的也可以实现我们本地(非同一jvm)或远程想使用java对象的一些功能的需求,但是他和使用自定义类加载器可以说作用的不是同一阶段或者说有着不同的目的。自定义类加载器完全不需要你的使用方有没有目的类的源码(class文件)甚至jvm里都不需要由其对应的class对象,我们可以通过从本地(可以是非同一jvm)或者远程获取到目的类的class文件二进制数据(你也可以因为安全考虑可以对这些二进制数据做一些编码和解码操作),然后可以在另一个jvm利用上面获取到的二进制数据来在jvm构造其运行时结构(class对象),只要构造出了class对象,然后你就可以任性的使用它了。而序列化的前提则是必须在你的jvm中已经加载了相应类(有了其对应的class对象),然后才能反序列化其对象实例(包含对象的状态数据等),再利用其使用他的一些功能。可以说两者是为了不同的目的。有不同的应用场景。
欢迎交流!!