. 背景
实际开发过程中,使用maven管理jar给我们开发带来了很多便利,不需要自己一个一个的jar包下载了,只需要配置个pom配置文件就可以了,写上对应坐标和仓库地址就可以了。但是jar冲突没问题没有解决,有冲突的jar包maven不会给我们检查出来还是会根据我们的配置进行下载,等到编译才会报错,并且报错信息很晦涩,需要面向百度查一会可能才能定位出问题。
这时候我们迫切需要有个东东可以提前告诉我们我的工程里有内奸,需要及时剔除,否则会影响军心。
2. 功能设计
1、maven插件,通过参数指令传给插件
2、工程里所有jar包冲突预警
3、工程中不能共存jar预警
4、自定义规则预警
3. 功能技术方案
3.1 详细技术方案
1、maven插件开发,继承AbstractMojo,入口方法execute,命令参数可以通过注解方式传入,例如:@Parameter(property = "groupId"),边亮可以获取groupId对应传入的参数值。
2、获取工程中所有的jar包及类:通过Maven获取MavenProject,拿到工程后就可以拿到所有的jar及其所有依赖树,把所有jar包放到一个大map中。
3、jar包冲突算法:
A、相同jar包版本冲突:通过自定义规则配置到属性文件中,定义某jar的哪些版本水火不相容。规则示例:xxxgroupId:xxxArtifactId>5.1.8,那么程序则认为这个坐标的jar包大于我要发(5.1.8)时就是内奸,需要除掉。
B、相同jar包版本冲突:俩jar包中有class个数不同或者个数相同但是MD5后结果不同或者俩jar包中有相同类大小不同这三种情况认为此山不容俩jar包。具体代码此处省略一万行。
C、不相同jar包冲突:通过自定义规则配置到属性文件中,定义某jar的某个版本和别的某jar包某个版本互看不顺眼。规则示例:if 王宝强groupId:王宝强ArtifactId >=2.2.2 then 经纪人groupId:经纪人ArtifactId>3.3.3,此规则代表王宝强jar包版本222与经纪人jar包版本333不能共存,否则会产生绿帽子,给出绿帽告警。
已知的会产生绿帽告警的有如下:
log4j-over-slf4j 和 slf4j-log4j12 不能共存。
jcl-over-slf4j 和 slf4j-jcl 不能共存。
jcl-over-slf4j 和 commons-logging 不能共存。
出处:https://www.slf4j.org/codes.html#version_mismatch。
部分核心代码展示
private void execute() throws MojoExecutionException {
this.getLog().info("FindConflicts is working...");
// preparing before collecting classes & artifacts
LogConflictsCollector logDepCollector = new LogConflictsCollector();
VersionConflictCollector versionConflictCollector = new VersionConflictCollector();
if (versionCheckConfig != null) {
try {
versionConflictCollector.init(versionCheckConfig);
} catch (FileNotFoundException e) {
this.getLog().info("versionCheckConfig:" + versionCheckConfig + " doesn't exist.");
} catch (Exception e) {
}
}
Set groupIdsToCheck = null;
if (groupId != null) {
String[] a = groupId.split(",");
if (a.length > 0) {
groupIdsToCheck = new HashSet();
for (int i = 0; i < a.length; i++) {
groupIdsToCheck.add(a[i].trim());
}
}
}
Set artifactIdsToCheck = null;
if (artifactId != null) {
String[] a = artifactId.split(",");
if (a.length > 0) {
artifactIdsToCheck = new HashSet();
for (int i = 0; i < a.length; i++) {
artifactIdsToCheck.add(a[i].trim());
}
}
}
int totalJarNum = 0;
int totalClassNum = 0;
// key:the id of an artifact, value:the classNum
Map<String, Integer> totalClassNumMap = new HashMap<String, Integer>();
// data is used to store the the information of class, key: the className , value is the class information of its.
Map<String, List> data = new HashMap<String, List>();
// get the final artifacts
Set artifacts = this.getProject().getArtifacts();
for (Iterator iterator = artifacts.iterator(); iterator.hasNext();) {
Artifact artifact = (Artifact) iterator.next();
if (!artifact.isOptional()) {
if ("jar".equals(artifact.getType())) {
if (groupIdsToCheck != null && !groupIdsToCheck.contains(artifact.getGroupId())) {
continue;
}
if (artifactIdsToCheck != null && !artifactIdsToCheck.contains(artifact.getArtifactId())) {
continue;
}
totalJarNum++;
ArtifactWrapper artifactWrapper = new ArtifactWrapper();
artifactWrapper.artifact = artifact;
artifactWrapper.originFrom = this.getOriginFrom(artifact);
logDepCollector.collect(artifactWrapper);
versionConflictCollector.collect(artifactWrapper);
JarFile jf;
try {
jf = new JarFile(artifact.getFile());
Enumeration jfs = jf.entries();
while (jfs.hasMoreElements()) {
JarEntry jfn = jfs.nextElement();
String fileName = jfn.getName();
if (fileName.endsWith(".class")) {
// ignore inner class 忽略内部类
if (fileName.indexOf("$") == -1) {
ClzWrapper clzWrapper = new ClzWrapper();
clzWrapper.className = fileName;
clzWrapper.artifactWrapper = artifactWrapper;
clzWrapper.size = jfn.getSize();
if (data.get(fileName) == null) {
List clzInfos = new ArrayList();
clzInfos.add(clzWrapper);
data.put(fileName, clzInfos);
} else {
data.get(fileName).add(clzWrapper);
}
logDepCollector.collect(clzWrapper);
String id = Util.getId(artifact);
if (totalClassNumMap.get(id) == null) {
totalClassNumMap.put(id, 1);
} else {
totalClassNumMap.put(id, totalClassNumMap.get(id) + 1);
}
totalClassNum++;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// iterator each conflicts 迭代器每次冲突
Set totalConflictJarNum = new HashSet();
int totalConflictClassNum = 0;
Set set = data.keySet();
List list = new ArrayList(set);
Collections.sort(list, new ClassConflictsComparator());
Iterator iter = list.iterator();
List jarConflictGroups = new ArrayList();
Set jarConflictGroupKeys = new HashSet();
// key:jarConflictsGroupKey, value:conflitsClassNum
Map<String, Integer> jarConglictGroupConflitsClassNumMap = new HashMap<String, Integer>();
List classConflicts = new ArrayList();
int classConflictNum = 1;
while (iter.hasNext()) {
String className = (String) iter.next();
List clzInfos = data.get(className);
if (clzInfos.size() == 1) {
// no conflicts
continue;
}
long clzSize = clzInfos.get(0).size;
boolean isConflicts = false;
// only conflicts if the size of class is not equal,
for (Iterator iterator = clzInfos.iterator(); iterator.hasNext();) {
ClzWrapper clzInfo = (ClzWrapper) iterator.next();
if (clzInfo.size != clzSize) {
isConflicts = true;
break;
}
}
if (isConflicts) {
JarConflictGroup jarConflictGroup = new JarConflictGroup();
for (Iterator iterator = clzInfos.iterator(); iterator.hasNext();) {
ClzWrapper clzInfo = (ClzWrapper) iterator.next();
// jar conflicts
jarConflictGroup.add(clzInfo.artifactWrapper);
totalConflictJarNum.add(Util.getId(clzInfo.artifactWrapper.artifact));
// class conflicts
ClassConflict classConflict = new ClassConflict();
classConflict.setClassName(clzInfo.className);
classConflict.setGroupId(clzInfo.artifactWrapper.artifact.getGroupId());
classConflict.setArtifactId(clzInfo.artifactWrapper.artifact.getArtifactId());
classConflict.setVersion(clzInfo.artifactWrapper.artifact.getVersion());
classConflict.setOriginFrom(Util.formatOriginFrom(clzInfo.artifactWrapper.originFrom));
classConflict.setNumber(classConflictNum);
classConflicts.add(classConflict);
totalConflictClassNum++;
}
classConflictNum++;
String jarConflictsGroupKey = jarConflictGroup.getGroupKey();
if (jarConglictGroupConflitsClassNumMap.get(jarConflictsGroupKey) == null) {
jarConglictGroupConflitsClassNumMap.put(jarConflictsGroupKey, clzInfos.size());
} else {
jarConglictGroupConflitsClassNumMap.put(jarConflictsGroupKey, clzInfos.size() + jarConglictGroupConflitsClassNumMap.get(jarConflictsGroupKey));
}
if (!jarConflictGroupKeys.contains(jarConflictsGroupKey)) {
jarConflictGroupKeys.add(jarConflictsGroupKey);
jarConflictGroups.add(jarConflictGroup);
}
}
}
// jarConflicts
for (Iterator iterator = jarConflictGroups.iterator(); iterator.hasNext();) {
JarConflictGroup jarConflictGroup = (JarConflictGroup) iterator.next();
jarConflictGroup.setConflitsClassNum(jarConglictGroupConflitsClassNumMap.get(jarConflictGroup.getGroupKey()));
int groupTotalClass = 0;
List artifactWrappers = jarConflictGroup.getArtifactWrappers();
if (artifactWrappers != null && artifactWrappers.size() > 0) {
for (Iterator iterator_1 = artifactWrappers.iterator(); iterator_1.hasNext();) {
ArtifactWrapper artifactWrapper = (ArtifactWrapper) iterator_1.next();
Artifact artifact = artifactWrapper.artifact;
groupTotalClass += totalClassNumMap.get(Util.getId(artifact));
}
jarConflictGroup.setTotalClassNum(groupTotalClass);
}
}
if (jarConflictGroups.size() > 0) {
Collections.sort(jarConflictGroups, new JarConflictGroupComparator());
int number = 1;
List jarConflicts = new ArrayList();
for (Iterator iterator = jarConflictGroups.iterator(); iterator.hasNext();) {
JarConflictGroup jarConflictGroup = (JarConflictGroup) iterator.next();
jarConflictGroup.setNumber(number++);
jarConflicts.addAll(jarConflictGroup.getJarConflicts());
}
this.getLog().warn("*****Jar Conflicts****");
this.getLog().warn((new TableGenerator()).generateTable(jarConflicts));
this.getLog().info("Jar Conflicts Total: jar conflicts ratio:" + totalConflictJarNum.size() + "/" + totalJarNum + "=" + NumberFormat.getPercentInstance().format((float) totalConflictJarNum.size() / totalJarNum));
this.getLog().info("Jar Conflicts Solution Hint: choose one artifact of the conflicts, and exclude other artifacts at pom.xml according to originFrom.");
} else {
this.getLog().info("No jar conflicts found!");
}
if (showClassConflicts) {
if (classConflicts.size() > 0) {
this.getLog().warn("*****Class Conflicts****");
this.getLog().warn((new TableGenerator()).generateTable(classConflicts));
this.getLog().info("Class Conflicts Total: class conflicts ratio:" + totalConflictClassNum + "/" + totalClassNum + "=" + NumberFormat.getPercentInstance().format((float) totalConflictClassNum / totalClassNum));
this.getLog().info("Class Conflicts Solution Hint: choose one artifact of the conflicts, and exclude other artifacts at pom.xml according to originFrom.");
} else {
this.getLog().info("No class conflicts found!");
}
}
List logConflicts = logDepCollector.getLogConflicts();
if (logConflicts != null && logConflicts.size() > 0) {
this.getLog().warn("*****Log Conflicts****");
this.getLog().warn((new TableGenerator()).generateTable(logConflicts));
this.getLog().info("Log Conflicts Solution Hint: choose one artifact of the conflicts, and exclude other artifacts at pom.xml according to originFrom.");
this.getLog().info("As for the conflicts of SLF4J, you can refer to this offical article:https://www.slf4j.org/codes.html#version_mismatch");
} else {
this.getLog().info("No log conflicts found!");
}
List versionConflicts = versionConflictCollector.getVersionConflict();
if (versionConflicts != null && versionConflicts.size() > 0) {
this.getLog().warn("*****Version Conflicts****");
this.getLog().warn((new TableGenerator()).generateTable(versionConflicts));
this.getLog().info("Version Conflicts Solution Hint: update the version of the artifact according to requiredVersion");
} else {
this.getLog().info("No version conflicts found!");
}
this.getLog().info("FindConflicts finished!");
}