20210614_Java自定义类加载器2
1概述
我们知道自定义类加载器就是用于对象隔离,如何实现呢?
本章主要学习下Java类加载器,完成完成了两个同名的class com.kikop.Address共存。
主要内容如下:
- 模拟两个Jar包MyModuleA-1.0-SNAPSHOT.jar、MyModuleB-1.0-SNAPSHOT.jar,分别定义同名包空间com.kikop下的Address类。
- 借助自定义类加载器,实现两个同名类的加载。
- 总结分析。
[图片上传失败...(image-903e64-1623601711557)]
2代码实战
2.1ClassLoaderUtil
package com.kikop.utils;
import java.io.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @author kikop
* @version 1.0
* @project Name: myjvmdemo
* @file Name: ClassLoaderUtil
* @desc 自定义加载器工具类
* @date 2021/6/13
* @time 18:00
* @by IDE: IntelliJ IDEA
*/
public class ClassLoaderUtil {
/**
* 返回类的字节码
*
* @param filePath
* @return
*/
public static byte[] getBytesByFilePath(String filePath) {
byte[] resultBytes = null;
InputStream inputStream = null;
// 借助 byteArrayOutputStream暂存字节流
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
File file = new File(filePath);
if (!file.exists()) {
return resultBytes;
}
inputStream = new FileInputStream(file);
int c = 0;
while ((c = inputStream.read()) != -1) {
byteArrayOutputStream.write(c);
}
resultBytes = byteArrayOutputStream.toByteArray();
} catch (FileNotFoundException ex) {
ex.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
byteArrayOutputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultBytes;
}
/**
* 返回类的字节码
*
* @param classpath
* @param jarName
* @param className
* @return
* @throws IOException
*/
public static byte[] getBytesByJarFile(String classpath, String jarName, String className) throws IOException {
byte[] resultBytes = null;
InputStream inputStream = null;
ByteArrayOutputStream byteArrayOutputStream = null;
try {
String tmp = className.replaceAll("\\.", "/");
JarFile jar = new JarFile(classpath + "/" + jarName);
JarEntry entry = jar.getJarEntry(tmp + ".class");
inputStream = jar.getInputStream(entry);
byteArrayOutputStream = new ByteArrayOutputStream();
int nextValue = inputStream.read();
while (-1 != nextValue) {
byteArrayOutputStream.write(nextValue);
nextValue = inputStream.read();
}
// byte[] buffer=new byte[2048];
// int len=0;
// while((len=in.read(buffer))!=-1){
// out.write(buffer,0,len);
// }
resultBytes = byteArrayOutputStream.toByteArray();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
byteArrayOutputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultBytes;
}
}
2.2自定义MyClassLoader
package com.kikop.myclazzloader;
import com.kikop.utils.ClassLoaderUtil;
import org.omg.CORBA.PRIVATE_MEMBER;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
/**
* @author kikop
* @version 1.0
* @project Name: myjvmdemo
* @file Name: MyClassLoader
* @desc 通过自定义加载器加载指定类
* @date 2021/6/13
* @time 18:00
* @by IDE: IntelliJ IDEA
*/
public class MyJarClassLoader extends ClassLoader {
// 自定义加载器的加载目录
private String classPath;
// 自定义加载器的jar名称
private String JarName;
// 自定义加载类前缀
private String customClazzPrefix;
private HashMap<String, Class<?>> loadedClasses = new HashMap<>();
public MyJarClassLoader(ClassLoader parent) {
super(parent);
}
public MyJarClassLoader(ClassLoader parent, String classPath, String JarName, String customClazzPrefix) {
super(parent);
this.classPath = classPath;
this.JarName = JarName;
this.customClazzPrefix = customClazzPrefix;
}
/**
* @param name 指定的类
* @return
* @throws ClassNotFoundException
*/
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> aClass = null;
if (!name.startsWith(this.customClazzPrefix)) { // do native
return super.loadClass(name);
}
if (loadedClasses.containsKey(name)) {
return loadedClasses.get(name);
}
try {
byte[] bytesByJarFile = ClassLoaderUtil.getBytesByJarFile(this.classPath, this.JarName, name);
if (null != bytesByJarFile) {
// defineClass方法将字节码转化为类
// defineClass(name,classDate,0,classDate.length);
aClass = defineClass(name, bytesByJarFile, 0, bytesByJarFile.length, null);
loadedClasses.put(name, aClass);
return aClass;
}
} catch (IOException e) {
e.printStackTrace();
}
return super.loadClass(name);
}
}
2.3MyModuleA::Address
package com.kikop;
/**
* @author kikop
* @version 1.0
* @project Name: MyModuleA
* @file Name: Address
* @desc
* @date 2021/6/13
* @time 18:00
* @by IDE: IntelliJ IDEA
*/
public class Address {
public String showVersion() {
return "V1.0";
}
}
2.5MyModuleB::Address
package com.kikop;
/**
* @author kikop
* @version 1.0
* @project Name: MyModuleB
* @file Name: Address
* @desc
* @date 2021/6/13
* @time 18:00
* @by IDE: IntelliJ IDEA
*/
public class Address {
public String showVersion() {
return "V2.0";
}
}
2.4测试
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
// E:\workdirectory\Dev\study\technicaltools
// System.out.println(System.getProperty("user.dir"));
String classPath = System.getProperty("user.dir") + File.separator + "myjvmdemo" + File.separator + "lib" + File.separator;
String customClazzPrefix = "com.kikop";
// 1.Jar包A测试
String jarNameA = "MyModuleA-1.0-SNAPSHOT.jar";
MyJarClassLoader myJarClassLoaderA = new MyJarClassLoader(
MyJarClassLoader.class.getClassLoader(),
classPath, jarNameA, customClazzPrefix);
Class<?> aClass = myJarClassLoaderA.loadClass("com.kikop.Address");
Class<?> aClass_copy = myJarClassLoaderA.loadClass("com.kikop.Address");
// 直接newInstance的话必须保证默认的构造方法正常存在,也就是没有被私有化!这是前提条件
Object addressInstanceA = aClass.newInstance();
Method showVersionMethodA = aClass.getDeclaredMethod("showVersion");
Object invokeResultA = showVersionMethodA.invoke(addressInstanceA);
System.out.println("Jar包A:" + aClass.getClassLoader() + ",showVersion:" + invokeResultA);
// 2.Jar包B测试
String jarNameB = "MyModuleB-1.0-SNAPSHOT.jar";
MyJarClassLoader myJarClassLoaderB = new MyJarClassLoader(MyJarClassLoader.class.getClassLoader(),
classPath, jarNameB, customClazzPrefix);
Class<?> bClass = myJarClassLoaderB.loadClass("com.kikop.Address");
// 使用灵活度强的 getConstructor获取指定的构造方法。
// 先获得需要被调用的构造器(private 修饰的构造方法)
Constructor<?> constructor = bClass.getConstructor();
// 若要访问私有的成员,得先申请一下,缺陷:对于此时的话,单例模式就不再安全了!反射可破之
constructor.setAccessible(true);
// 成功,通过私有的受保护的构造方法创建了对象
Object addressInstanceB = constructor.newInstance();
Method showVersionMethodB = bClass.getDeclaredMethod("showVersion");
Object invokeResultB = showVersionMethodB.invoke(addressInstanceB);
System.out.println("Jar包B:" + bClass.getClassLoader() + ",showVersion:" + invokeResultB);
boolean isSame = aClass.equals(bClass);
if (isSame) {
System.out.println("claxx一样,天理难容!");
} else {
System.out.println("不同的类加载器对象实例,所以方法区中的clazz也不一样!");
}
}
Jar包A:com.kikop.myclazzloader.MyJarClassLoader@7382f612,showVersion:V1.0
Jar包B:com.kikop.myclazzloader.MyJarClassLoader@77afea7d,showVersion:V2.0
不同的类加载器对象实例,所以方法区中的clazz也不一样!
3总结
通过上面的代码实战, 我们基本完成了自定义加载器的类加载。
完成了两个同名的class com.kikop.Address共存。
网友评论