今天我们来谈一谈架构探险中自定义的类加载器,一般我们若想实现自定义的类加载器,可以继承ClassLoader类,然后实现findClass方法即可,详细介绍可以看以下链接:
https://www.cnblogs.com/doit8791/p/5820037.html
本文主要谈一下架构探险中的实现方式。在getClassLoader方法中我们拿到的实际上时当前线程的ClassLoader。然后loadClass则直接调用Class.forName()方法,loadClass方法实现了类加载。核心的方法是怎么扫描一个包文件下的所有class文件。具体实现请看代码:
public static ClassLoader getClassLoader() {
//这里我们获取当前线程的类加载器
return Thread.currentThread().getContextClassLoader();
}
/**
* 加载类
* @param className
* @param isInitialized
* @return
*/
public static Class<?> loadClass(String className, boolean isInitialized) {
Class<?> cls = null;
try {
cls = Class.forName(className, isInitialized, getClassLoader());
} catch (ClassNotFoundException ex) {
logger.error("ClassNotFoundException", className);
}
return cls;
}
/**
* 获取指定包名下的所有类
* @param packageName
* @return
*/
public static Set<Class<?>> getClassSet(String packageName) {
Set<Class<?>> classSet = new HashSet<>();
try {
Enumeration<URL> urlEnumeration = getClassLoader().getResources(packageName.replace(".", "/"));
while (urlEnumeration.hasMoreElements()) {
URL url = urlEnumeration.nextElement();
if(url != null) {
String protocol = url.getProtocol();
if(protocol.equals("file")) {
String packagePath = url.getPath().replace("%20", "");
addClass(classSet, packagePath, packageName);
} else if(protocol.equals("jar")) {
// ??
// 可能永远执行不到,经测试如果直接把jar文件,copy到src路径下,则读取到jar时所用的protocol依然是file.
// JarUrlConnection 必须使用URL url = new Url("jar:file:/xxx.jar!/");
// ??
// 如果包含jar 我们需要提取jar里的class文件, 这里用的时JarURLConnection
// 这里还有其他选择,我们可以使用fastjson来提取jar文件
JarURLConnection jarUrlConn = (JarURLConnection) url.openConnection();
if(jarUrlConn != null) {
JarFile jarFile = jarUrlConn.getJarFile();
Enumeration<JarEntry> entities = jarFile.entries();
while (entities.hasMoreElements()) {
JarEntry jarEntity = entities.nextElement();
String jarEntityName = jarEntity.getName();
if(jarEntityName.endsWith(".class")) {
String className = jarEntityName.substring(0, jarEntityName.lastIndexOf(".")).replace("/", ".");
doAddClass(classSet, className);
}
}
}
}
}
}
} catch (IOException ex) {
logger.error("get class set failure", ex);
throw new RuntimeException(ex);
}
return classSet;
}
private static void addClass(Set<Class<?>> classSet, String packagePath, final String packageName) {
final File[] files = new File(packagePath).listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return (file.exists() && file.getName().endsWith(".class")) || file.isDirectory();
}
});
for(File file : files) {
String fileName = file.getName();
if(file.isFile()) {
String className = fileName.substring(0, fileName.lastIndexOf("."));
if(StringUtils.isNotEmpty(packageName)) {
className = String.format("%s.%s", packageName, className);
}
doAddClass(classSet, className);
} else {
// 如果是目录,继续扫描
String subPackagePath = fileName;
if(StringUtils.isNotEmpty(packagePath)) {
//作为新的扫描路径
subPackagePath = String.format("%s/%s", packagePath, fileName);
}
String subPackageName = fileName;
if(StringUtils.isNotEmpty(packageName)) {
//扫描新的包,因为加载类需要全路径,所以我们需要不断拼接
subPackageName = String.format("%s.%s", packageName, subPackageName);
}
addClass(classSet, subPackagePath, subPackageName);
}
}
}
private static void doAddClass(Set<Class<?>> classSet, String className) {
Class<?> clazz = loadClass(className, false);
classSet.add(clazz);
}
代码关于如何获取jar文件中的class文件,笔者在注释中写出来自己的一些疑问,欢迎大家分享自己的见解。