. 背景

实际开发过程中,使用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<String> groupIdsToCheck = null;
  if (groupId != null) {
   String[] a = groupId.split(",");
   if (a.length > 0) {
    groupIdsToCheck = new HashSet<String>();
    for (int i = 0; i < a.length; i++) {
     groupIdsToCheck.add(a[i].trim());
    }
   }
  }
  Set<String> artifactIdsToCheck = null;
  if (artifactId != null) {
   String[] a = artifactId.split(",");
   if (a.length > 0) {
    artifactIdsToCheck = new HashSet<String>();
    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<ClzWrapper>> data = new HashMap<String, List<ClzWrapper>>();
  // get the final artifacts
  Set<Artifact> artifacts = this.getProject().getArtifacts();
  for (Iterator<Artifact> 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<JarEntry> 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<ClzWrapper> clzInfos = new ArrayList<ClzWrapper>();
          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<String> totalConflictJarNum = new HashSet<String>();
  int totalConflictClassNum = 0;
  Set<String> set = data.keySet();
  List<String> list = new ArrayList<String>(set);
  Collections.sort(list, new ClassConflictsComparator());
  Iterator<String> iter = list.iterator();
  List<JarConflictGroup> jarConflictGroups = new ArrayList<JarConflictGroup>();
  Set<String> jarConflictGroupKeys = new HashSet<String>();
  // key:jarConflictsGroupKey, value:conflitsClassNum
  Map<String, Integer> jarConglictGroupConflitsClassNumMap = new HashMap<String, Integer>();
  List<ClassConflict> classConflicts = new ArrayList<ClassConflict>();
  int classConflictNum = 1;
  while (iter.hasNext()) {
   String className = (String) iter.next();
   List<ClzWrapper> 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<ClzWrapper> 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<ClzWrapper> 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<JarConflictGroup> iterator = jarConflictGroups.iterator(); iterator.hasNext();) {
   JarConflictGroup jarConflictGroup = (JarConflictGroup) iterator.next();
   jarConflictGroup.setConflitsClassNum(jarConglictGroupConflitsClassNumMap.get(jarConflictGroup.getGroupKey()));
   int groupTotalClass = 0;
   List<ArtifactWrapper> artifactWrappers = jarConflictGroup.getArtifactWrappers();
   if (artifactWrappers != null && artifactWrappers.size() > 0) {
    for (Iterator<ArtifactWrapper> 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<JarConflict> jarConflicts = new ArrayList<JarConflict>();
   for (Iterator<JarConflictGroup> 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<LogConflict> 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<VersionConflict> 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!");

 }

京东云开发者
3.3k 声望5.4k 粉丝

京东云开发者(Developer of JD Technology)是京东云旗下为AI、云计算、IoT等相关领域开发者提供技术分享交流的平台。