前言

在开发一个基础工具包给业务组的小伙伴们使用的时候,发现一个小问题,就是在反射的时候在自己电脑上运行的正常,但是打成jar包后,就class not fuond,有点奇怪。
如果能借助Spring这个都不是事,关键是不能用。

场景复现

下面的目的是,找到这个指定包下的所有类,对我指定了注解的类进行加载,其实就是一个可以灵活装配、拆卸的策略模式。这样业务的类可以通过指定注解来选择是否成为业务的一部分。
下面这段代码在IDE中运行正常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
* 类加载工厂
*
* @author liukai
* @since 2015/9/9.
*/
public class GeneratorFactory {

public static Map<String, TransactionCreator> creators = new HashMap<>();

static {
// 基于 IDE 运行
reflectByClass();
}

public static void reflectByClass() {
try {
ArrayList<Class> creatorsClass = new ArrayList<>();
ArrayList<File> classFiles = new ArrayList<>();
// 拿到关键接口
Class<?> interfaceClass = Class.forName("com.liukai.test.generate.TransactionCreator");
String packageName = interfaceClass.getPackage().getName();
// 拿到 ClassLoader
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
String path = packageName.replace(".", "/");
System.out.println("path: " + path);
Enumeration<URL> resources = contextClassLoader.getResources(path);
System.out.println("Enumeration<URL> resources: " + resources);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
System.out.println("url.getFile(): " + url.getFile());
classFiles.add(new File(url.getFile()));
}
for (File file : classFiles) {
creatorsClass.addAll(findClass(file, packageName));
}

for (Class clazz : creatorsClass) {
System.out.println("Class clazz : creatorsClass");
Creator declaredAnnotation = (Creator) clazz.getAnnotation(Creator.class);
if (declaredAnnotation != null) {
System.out.println("creators: add" + declaredAnnotation.type());
creators.put(declaredAnnotation.type(), (TransactionCreator) clazz.newInstance());
}
}
} catch (ClassNotFoundException | IOException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}

public static TransactionCreator getGenerator(String type) {
System.out.println("creators: " + creators.size());
return creators.get(type);
}

private static ArrayList<Class> findClass(File file, String packagename) {
ArrayList<Class> list = new ArrayList<>();
if (!file.exists()) {
return list;
}
File[] files = file.listFiles();
for (File file2 : files) {
if (file2.isDirectory()) {
assert !file2.getName().contains(".");
ArrayList<Class> arrayList = findClass(file2, packagename + "." + file2.getName());
list.addAll(arrayList);
} else if (file2.getName().endsWith(".class")) {
try {
list.add(Class.forName(packagename + '.' + file2.getName().substring(0,
file2.getName().length() - 6)));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
return list;
}

}

解决方式:
由于打成jar包后,类路径多了一层jar,所以加载时,要考虑到jar包路径,以下这个demo,实际可以通过获取运行路径变量替换写死的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
* 类加载工厂
*
* @author liukai
* @since 2015/9/9.
*/
public class GeneratorFactory {

public static Map<String, TransactionCreator> creators = new HashMap<>();

static {
// 基于 jar 包反射
reflectByJar();
}

public static void reflectByJar() {
String path = "/Users/liukai/workspaces/java/test/build/libs/";
String fileName = "test.jar";
try {
JarFile jarFile = new JarFile(path + fileName);
Enumeration<JarEntry> e = jarFile.entries();
JarEntry entry;
while (e.hasMoreElements()) {
entry = (JarEntry) e.nextElement();
if (entry.getName().indexOf("META-INF") < 0 && entry.getName().indexOf(".class") >= 0) {
String classFullName = entry.getName();
//去掉后缀.class
String className = classFullName.substring(0, classFullName.length() - 6).replace("/", ".");
Class<?> clazz;
try {
try {
if (!className.contains("com.liukai.test")) {
continue;
}
System.out.println("Class clazz : creatorsClass:" + className);
clazz = Class.forName(className);
Creator declaredAnnotation = (Creator) clazz.getAnnotation(Creator.class);
if (declaredAnnotation != null) {
System.out.println("creators: add" + declaredAnnotation.type());
creators.put(declaredAnnotation.type(), (TransactionCreator) clazz.newInstance());
}
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
}

} catch (Exception e1) {

}

}
}
} catch (IOException e) {
e.printStackTrace();
}
}

总结

感觉还是不智能,应该有一个通用的方法,无论我是什么场景,只需要一套代码就解决,而不是用户来抽像,这个JDK应该得提供。