目录
问题
工作中遇到一个需求,客户端将第三方的 jar 包上传到服务器中,系统需要解析出上传的 jar 中所有类以及类下的方法(方法名,方法输入参数类型,方法返回值类型),并将这些类以及方法提供给配置人员选择,由配置人员自由配置需要用到的方法。
类加载器
Java 虚拟机在加载类文件时,默认情况下使用自带的三个类加载器
- BootstrapClassLoader:加载 %JRE_HOME%\jre\lib 下的 jar 和 class
- ExtentionClassLoader:加载 %JRE_HOME%\jre\lib\ext 下的 jar 和 class
- AppClassLoader:加载当前应用路径下的 class
在 JDK 1.8 的 rt.jar 中,Launcher 类里定义了这三个类加载器
可以从源码中看到,三个加载器分别指定了不同的加载路径来加载类
BootstrapClassLoader
private static String bootClassPath = System.getProperty("sun.boot.class.path");
ExtentionClassLoader
System.getProperty("java.class.path")
AppClassLoader
System.getProperty("java.ext.dirs")
三个加载器通过双亲委派机制来加载类文件,每个加载器都只负责加载自己负责的加载路径,显然,如果要自定义加载路径,从而实现加载外部 jar 包,就必须自定义加载器来实现。
在 JDK 1.8 中,可以看到 AppClassLoader 和 ExtClassLoader 都是继承 URLClassLoader 来实现的。若要加载外部的 jar 包,只需拿到外部 jar 包的 URL 路径,用 URLClassLoader 便可以加载。
获取外部 jar 包中的类以及方法
public class JarUtils {
/**
* 这些默认方法不打印
*/
private static List<String> DEFAULT_METHODS = Arrays.asList("wait", "equals", "notify", "notifyAll", "toString", "hashCode" , "getClass");
public static void parseJar(File file) throws Exception {
URL url = file.toURI().toURL();
try (URLClassLoader myClassLoader = new URLClassLoader(new URL[]{url}, Thread.currentThread().getContextClassLoader())) {
//通过jarFile和JarEntry得到所有的类
JarFile jar = new JarFile(file);
//返回zip文件条目的枚举
Enumeration<JarEntry> enumFiles = jar.entries();
JarEntry entry;
//测试此枚举是否包含更多的元素
while (enumFiles.hasMoreElements()) {
entry = enumFiles.nextElement();
if (entry.getName().indexOf("META-INF") < 0) {
String classFullName = entry.getName();
if (classFullName.endsWith(".class")) {
//去掉后缀.class
String className = classFullName.substring(0, classFullName.length() - 6)
.replace("/", "."); //类名
Class<?> myclass = myClassLoader.loadClass(className);
System.out.println(String.format("类名:%s", className));
//得到类中包含的属性
Method[] methods = myclass.getMethods();
for (Method method : methods) {
String methodName = method.getName();
if (DEFAULT_METHODS.contains(methodName)) {
continue;
}
//方法名
System.out.print(String.format("方法名:%s; ", methodName));
Class<?>[] parameterTypes = method.getParameterTypes(); //方法参数类型
System.out.print(String.format("方法参数类型:%s; ", Arrays.stream(parameterTypes)
.map(Class::getSimpleName).collect(joining(","))));
System.out.println(String.format("方法返回类型:%s; ", method.getReturnType().getSimpleName())); //方法返回值类型
}
System.out.println("***************************");
}
}
}
} catch (IOException e) {
throw e;
}
}
}
调用外部 jar 包中的方法
public class JarUtils {
public static void executeMethod(File f, String className, String methodName, Class[] classes, Object[] objects) {
//通过URLClassLoader加载外部jar
try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{f.toURI().toURL()})) {
//获取外部jar里面的具体类对象
Class<?> targetClass = urlClassLoader.loadClass(className);
//创建对象实例
Object instance = targetClass.newInstance();
//获取实例当中的方法名为show,参数只有一个且类型为string的public方法
Method method = targetClass.getMethod(methodName, classes);
//传入实例以及方法参数信息执行这个方法
Object returnObj = method.invoke(instance, objects);
System.out.println(returnObj);
} catch (Exception e) {
e.printStackTrace();
}
}
}