“ 最近在OA项目上和第三方做集成,我需要提供一些接口给供第三方调用,在这个过程中觉得自己测试接口很麻烦,所以想写一个JSP界面来界面化测试自己写的一些接口。”
在我的实际项目上,当我将接口部署到测试环境的时候,我们需要先自己测试一下接口,然后才会让第三方进行调用,这个时候测试就是一个很麻的事情,因为通常来说接口跟流程绑定,我们需要通过走流程去测试接口,自己觉得很麻烦,不如自己写一个简单的测试界面,供自己测试使用,这里就需要用到类加载器和反射的相关知识了。
PS:网上找的代码编辑器最近排版效果很差,下面展示的代码大家大致的看下,然后可以去小程序中下载代码到本地去浏览,主要聊一下思路。
01
—
效果
我们先看一下效果,然后在叙述过程,
接口
提交前界面
点击提交后的界面:
其中hello world!就是返回的数据。
这中间经历了什么呢?
我传入了接口的路径,名称,方法,参数,点击提交时,后台逻辑首先根据路径,通过类加载器获取所有的Class的物理路径,然后通过File来将Class文件存入到集合,此时我们通过传入的名称取到对应Class文件,紧接着再找到指定方法名执行对应方法,再将接口返回的数据展示到界面上。
02
—
获取Class
根据包路径获取Class离不开类加载器,在加载资源时的ClassLoader可以有多种选择
1. 系统类加载器SystemClassLoader,可通过ClassLoader.getSystemClassLoader()获得; 2. 当前ClassLoader:加载了当前类的ClassLoader; 3. 线程上下文类加载器ContextClassLoader:Thread.currentThread().getContextClassLoader(); 4. 自定义类加载器;
因为SystemClassLoader只能加载classpath路径下的资源,有局限性。加载了当前类的ClassLoader也不满足当前需求,ContextClassLoader没有局限性,可以在应用程序中将其设为任意ClassLoader,加载任意目录下的类和资源,所以这里我们选用ContextClassLoader获取资源。
public static Set<Class<?>> getClasses(String pack) {
// 第一个class类的集合
Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
// 是否循环迭代
boolean recursive = true;
// 获取包的名字 并进行替换
String packageName = pack;
String packageDirName = packageName.replace('.', '/');
// 定义一个枚举的集合 并进行循环来处理这个目录下的things
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread()
.getContextClassLoader()
.getResources(
packageDirName);
}
catch(IOException e){
e.printStackTrace();
}
获取到集合目录时,我们开始迭代获取Class的物理文件路径,file是class文件存储形式,如果存在jar包我们还需要特殊处理,这里酒不沾湿了,完整代码可进入进入小程序查看。
while (dirs.hasMoreElements()) {
// 获取下一个元素
URL url = dirs.nextElement();
System.out.println(url);
// 得到协议的名称
String protocol = url.getProtocol();
// 如果是以文件的形式保存在服务器上
if ("file".equals(protocol)) {
System.err.println("file类型的扫描");
// 获取包的物理路径
String filePath = URLDecoder.decode(
url.getFile(), "UTF-8");
// 以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(
packageName, filePath,
recursive, classes);
}
}
其中findAndAddClassesInPackageByFile方法是将物理路径的class文件放入到Set集合中。
// 循环所有文件
for (File file : dirfiles) {
// 如果是目录 则继续扫描
if (file.isDirectory()) {
findAndAddClassesInPackageByFile(
packageName + "."
+ file.getName(),
file.getAbsolutePath(),
recursive,
classes);
} else {
// 如果是java类文件 去掉后面的.class 只留下类名
String className = file.getName().substring(0,
file.getName().length() - 6);
try {
// 添加到集合中去
// classes.add(Class.forName(packageName + '.'
// +className));
// 这里用forName有一些不好,会触发static方法,
//没有使用classLoader的load干净
classes.add(Thread.
currentThread().
getContextClassLoader()
.loadClass(packageName + '.'
+ className));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
实际上,在findAndAddClassesInPackageByFile中我们就可以对于class文件进行过滤了,而不用二次循环,这里我就不处理了。我们看下最后如何执行方法:
getClasses方法就是上面第一个方法。callMethod.getParameterCount()这个方法是获取参数个数,防止wrong number of arguments的错误。
public String exe(String packageName,
String objName,
String methodName,
Object... param) {
Object o = "";
Set<Class<?>> classSet = getClasses(packageName);
for (Class<?> class1 : classSet) {
if (objName.equals(class1.getSimpleName())) {
try {
Method[] methods =
class1.getDeclaredMethods();
Method callMethod = null;
for (Method method : methods) {
if (method.
getName().
equals(methodName)) {
callMethod = method;
break;
}
}
//如果方法add是私有的private方法,
// 按照上面的方法去调用则会产生异常NoSuchMethodException,
// 这时必须改变其访问属性
int number = callMethod.getParameterCount();
if (number > 0) {
o = callMethod.invoke(
class1.newInstance(), param);
} else {
o = callMethod.invoke(
class1.newInstance());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
return o.toString();
}
完整的代码示例大家可以去小程序代码库中进行复制: