当一个项目使用的jar包越来越多,代码经常在运行的时候抛出异常:java.lang.NoSuchMethodException,java.lang.NoSuchFieldError,基本是存在多个jar包包含相同的class类文件导致的,运行期可能用的class没有该方法等。
一、思路
为了提前找到存在相同class的jar包,我准备写一个小程序,由于是操作jar包等,需要用Java代码来处理,但写出来一般是一个class文件,使用者可能需要用命令行执行的方式来调用它,java的桌面程序我不擅长,于是想到了用.NET开发一个工具壳子,内部调用java代码的方式来执行,开始想到用.NET下的IKVM来处理jar包,它提供了java基础包的.NET实现。但加载它也会使程序增大几M。最后我换了个思路,代码由Java来编写,.NET用java命令的方式执行java编写的class文件,再把输出内容展现到.NET工具的界面上,这样也会大大减小工具的大小。
二、技术实现:
新增一个java的控制台程序,它有默认main方法入口,需要传递待分析的Jar包路径,参数是通过外界传入的,这里会从.net界面输入参数传递到java的main入口,如代码:
public static void main(String[] args) {
if(args.length < 1){
System.out.println("请输入jar包路径!");
return;
}
然后根据传入的args的路径,获取该路径下的所有jar包文件,然后轮询所有jar包,获取里面存在的class文件,然后把jar包和对应的class文件放到一个字典类里,轮询所有jar包后,就可以通过字典类获取存在一个class多个jar包的文件。字典类结构为:HashMap<String,HashSet<String>>,用class名称作为key,这里比如为com/apache/abc/demo.class,多个jar包的名称做为value,达到键唯一,值是集合的字典,方便后续从字典输出存在多个jar包值的class,结构如下:
Key | Value(HashSet) |
org/apache/taglibs/standard/lang/jstl/ImplicitObjects.class
| standard-1.1.2.jar |
jstl-1.2.jar | |
... | ... |
... |
主要实现代码如:
File file = new File(args[0]);
Map<String,HashSet<String> > jarMap = new HashMap<String,HashSet<String>>();
if(null != file && file.exists() && file.isDirectory()){
File[] jarFile = file.listFiles();
for(File f : jarFile){
if(f.isFile() && f.getName().endsWith(".jar")){
try {
JarFile jar = new JarFile(f);
Enumeration<JarEntry> enumJar = jar.entries();
while(enumJar.hasMoreElements()){
JarEntry je = enumJar.nextElement();
if(je.getName().endsWith(".class")){
if(jarMap.containsKey(je.getName())){
jarMap.get(je.getName()).add(f.getName());
}else{
HashSet<String> set = new HashSet<String>();
set.add(f.getName());
jarMap.put(je.getName(), set);
查找重复类代码如下:
for(String s : keys){
if(jarMap.get(s).size() > 1){
//存在重复
HashSet<String> hsfile = jarMap.get(s);
String sfiles = "";
for(String sf : hsfile){
sfiles = sfiles + sf + ",";
}
if(sfiles.length() > 1){
sfiles = sfiles.substring(0, sfiles.length() -1);
}
System.out.println(sfiles + "有重复类:" + s);
}
}
最后java程序输出
System.out.println("[[over]]");
这里的作用是打一个结束标记,.NET代码根据这个结束标记显示内容。
Java代码会把查找到的jar包和冲突的类输出到控制台,.NET可以根据执行后的控制台字符串稍作处理然后展现出来。.NET通过新启一个Process调用cmd执行命令的方式调用Java指令,代码如下:
public static string RunCmd(string command)
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
p.Start();
p.StandardInput.WriteLine(command);
p.StandardInput.WriteLine("exit");
string result = p.StandardOutput.ReadToEnd();
p.WaitForExit(); p.Close();
return result;
}
指令传入:java Checksame + “界面录入的路径”,软件界面如:
上面就完成了检测jar包存在多个class的问题,代码不复杂,换了一个实现思路帮助解决了实际问题。