从maven私服(nexus)拉取jar文件,解析项目pom依赖信息

一、前言

关于pom解析的方式,常见的我认为有两种:
一种是利用dom tree的结构特性,利用dom4j提供的xml解析工具将pom文件读取为dom tree结构,再层层解析出内容。
第二种方式更为简单高效,也是本文将使用的解析方式,即利用maven命令来将pom文件解析为依赖树文件,再直接读取该文件,利用whitesource公司提供的对pom解析的支持,完成整个解析。该方式的优势在于,利用了专门为解析pom文件而生的工具类,能自动识别pom文件中的标签,避免了手动处理xml文件中各标签的麻烦;同时,当待解析的pom存在父pom时,当加载进来的依赖隐式地导入了别的依赖时,不需要我们自己去处理这个复杂的关系,便能读取出所有的依赖。

二、准备工作

为了能在代码中使用maven命令,并解析依赖树文件,我们首先需要导入如下依赖:

<dependency>
    <groupId>org.apache.maven.sharedgroupId>
    <artifactId>maven-invokerartifactId>
    <version>3.0.1version>
dependency>
<dependency>
    <groupId>org.whitesourcegroupId>
    <artifactId>maven-dependency-tree-parserartifactId>
    <version>1.0.5version>
dependency>

1. pom解析出依赖树文件

在一个maven项目中,执行命令:mvn dependency:tree ,在控制台可以看到打印出的该项目的依赖树结构。那么,为了将依赖树输出到文件中,需要加上参数:-D outputFile=tree.txt,便能将依赖树文件保存到项目的根目录下(与pom.xml)同级。
在java代码中执行该命令的方式如下:

package com.example.demo;

import org.apache.maven.shared.invoker.*;
import java.io.File;
import java.util.Collections;

public class MavenRun {
    public static void main(String[] args) {
        InvocationRequest request = new DefaultInvocationRequest();
		// 待解析的pom文件路径,不写默认为解析当前项目pom文件
		request.setPomFile( new File( "./mypom.xml" ) );
		request.setGoals( Collections.singletonList( "dependency:tree -D outputFile=tree.txt") );
		
		Invoker invoker = new DefaultInvoker();
		// 你的maven安装路径
		invoker.setMavenHome(new File("xxx/apache-maven-3.5.4"));
		try {
		    invoker.execute(request);
		} catch (MavenInvocationException e) {
		    e.printStackTrace();
		}
    }
}

执行以上代码,打开解析出的依赖树文件,可看到输出的依赖关系如下:
从maven私服(nexus)拉取jar文件,解析项目pom依赖信息_第1张图片
这样的结构很直观地给我们展示了导入的依赖结构,不仅方便我们直接观看,同时利用whitesource公司提供的工具支持,能够非常方便地解析成java对象。

2. 读取依赖树文件到java对象

利用whitesource公司提供的工具,我们非常简单地就可以将依赖树文件解析得到Node对象。

package com.example.demo;

import fr.dutra.tools.maven.deptree.core.InputType;
import fr.dutra.tools.maven.deptree.core.Node;
import fr.dutra.tools.maven.deptree.core.Parser;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Reader;

public class MvnParse {
    public static void main(String[] args) throws Exception {
        InputType type = InputType.TEXT;
        Reader r = new BufferedReader(new InputStreamReader(new FileInputStream("tree.txt"), "UTF-8"));
        Parser parser = type.newParser();
        Node tree = parser.parse(r);
        System.out.println(tree);
    }
}

设置断点,我们可以看到Node的整个结构。依赖树文件被解析成了树形连接的Node节点,我们递归便能解析出所有的依赖。
从maven私服(nexus)拉取jar文件,解析项目pom依赖信息_第2张图片
有了上面的基础,我们已经知道了如何利用代码实现pom文件的解析,下面我们将实现一个更完整的解析过程。

三. 从Nexus仓库拉取jar文件,读取pom依赖信息

每个公司都有自己的maven私服(nexus仓库),存储了公司项目的所有jar文件。我们要实现的是一个完整的过程:利用一个dependency的坐标,到nexus仓库拉取下来jar文件,并读取出该jar的pom依赖信息。

要解析jar文件,需要传入jar文件的依赖坐标,形如:

<dependency>
    <groupId>com.examplegroupId>
    <artifactId>demoartifactId>
    <version>0.0.1-SNAPSHOTversion>
dependency>

解析jar文件的步骤主要有如下三步:

  1. 根据jar坐标,解析出编译jar的下载地址。
  2. 下载编译jar,获取到pom文件。
  3. 解析出pom文件中的依赖。

用代码整合这三步如下,下面将进行代码的拆分:

public static List<Dependency> downloadPomFile(String pomXml, String workBasePath) {
	//获取到编译jar下载地址
	String classJarFileUrl = getClassJarFileUrl(pomXml);
	String classFileName = classJarFileUrl.substring(classJarFileUrl.lastIndexOf(APPEND_CHAR) + 1);
	try {
	    //下载编译jar,存到本地
	    File classFile = downLoadFile(classJarFileUrl, workBasePath, classFileName);
	    if (sourceFile.length() == 0 || classFile.length() == 0) {
	        log.error("pom对应的目标文件为空");
	        throw new RuntimeException("pom对应的目标文件为空");
	    }
	    
	    //获取到pom文件的内容
	    String nexusPomUrl = classJarFileUrl.substring(0, classJarFileUrl.lastIndexOf(".jar")) + ".pom";
	    String pomContent = cn.hutool.core.io.FileUtil.readString(new URL(nexusPomUrl), "UTF-8");
	    String pomFileName = classFileName.substring(0, classFileName.lastIndexOf(".jar"));
	    //解析出pom文件中的依赖
		return parseAllDependency(pomContent, pomFileName);
	}
1. 根据jar坐标,解析出编译jar下载地址
//公司的nexus仓库下载snapshot版本jar的根地址
private static final String SNAP_DEPOT = "http://xxx/nexus/content/repositories/snapshots/";
//公司的nexus仓库下载正式jar的根地址
private static final String PUBLIC_DEPOT = "http://xxx/nexus/content/repositories/public/";

/**
 * 获取编译jar地址
 * @param pomXml
 * @return
 */
public static String getClassJarFileUrl(String pomXml) {
    Dependency dependency = parseDependencyInfo(pomXml);
    return getJarFileUrlFromDependency(dependency);
}

/**
 * 将pom信息转换为dependency
 * 
 * com.example
 * demo
 * 0.0.1-SNAPSHOT
 * 
 * 
 * @param pomXml
 * @return
 */
public static Dependency parseDependencyInfo(String pomXml) {
    Document doc = null;
    try {
        doc = DocumentHelper.parseText(pomXml);
    } catch (DocumentException e) {
        throw new RuntimeException("pom格式有误");
    }
    Element rootEle = doc.getRootElement();
    Element groupEle = rootEle.element(Constants.GROUP_ID);
    Element artifactEle = rootEle.element(Constants.ARTIFACT_ID);
    Element versionEle = rootEle.element(Constants.VERSION);
    if (groupEle == null || artifactEle == null || versionEle == null) {
        throw new RuntimeException("pom格式有误");
    }
    Dependency dependency = new Dependency();
    dependency.setGroupId(groupEle.getStringValue());
    dependency.setArtifactId(artifactEle.getStringValue());
    dependency.setVersion(versionEle.getStringValue());
    return dependency;
}

/**
 * 将dependency中解析为jar对应的url
 * @param dependency
 * @return
 */
public static String getJarFileUrlFromDependency(Dependency dependency) {
    String version = dependency.getVersion();
    boolean isSnapshot = version.toUpperCase().contains(SNAPSHOT);
    StringBuilder urlBuilder = new StringBuilder();
    urlBuilder.append(isSnapshot ? SNAP_DEPOT : PUBLIC_DEPOT);
    urlBuilder.append(dependency.getGroupId().trim().replace(".", "/")).append("/");
    urlBuilder.append(dependency.getArtifactId()).append("/");
    urlBuilder.append(version).append("/");
    if (isSnapshot) {
        //找到最新版快照包
        return getLatestClass(urlBuilder.toString());
    } else {
        urlBuilder.append(dependency.getArtifactId()).append("-").append(version).append(JAR_SUFFIX);
    }
    return urlBuilder.toString();
}
2. 下载编译jar,获取到pom文件
/**
 * 从远程url获取文件源并在本地创建文件
 *
 * @param remoteUrl 远程url地址
 * @param savePath 本地文件绝对路径
 * @param fileName 本地文件名
 * @return
 */
public static File downLoadFile(String remoteUrl, String savePath, String fileName) throws IOException {
    if (StringUtils.isEmpty(remoteUrl)) {
        throw new RuntimeException("远程url地址为空");
    }
    if (StringUtils.isEmpty(savePath) || StringUtils.isEmpty(fileName)) {
        throw new RuntimeException("文件保存路径或文件名为空");
    }
    File file = new File(savePath + fileName);
    URL url = new URL(remoteUrl);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    conn.setConnectTimeout(5 * 1000);
    //设置用户代理(防止屏蔽程序抓取而返回403错误)
    conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows XP; DigExt)");
    //通过输入流获取图片数据
    InputStream inputStream = conn.getInputStream();
    FileUtils.copyInputStreamToFile(inputStream, file);
    return file;
}
3. 解析出pom文件中的依赖
//pom下载的本地存储路径
private static final String POM_DOWNLOAD_PATH = "/tmp/pom/";
//本地maven安装地址
private static final String MAVEN_HOME = "xxx/apache-maven-3.5.4"

/**
 * 从pom中解析出所有依赖
 * @param pomContent pom的内容
 * @param pomFileName 不带.pom后缀
 * @return
 */
public static List<Dependency> parseAllDependency(String pomContent, String pomFileName, Constants constants) {
    //在本地创建pom的临时文件
    FileWriter fileWriter = null;
    String pomFilePath = POM_DOWNLOAD_PATH + pomFileName + ".pom";
    File pomFile = new File(pomFilePath);
    try {
        //判断路径是否存在,不存在则创建文件路径
        File file = new File(pomFilePath.substring(0, pomFilePath.lastIndexOf('/')));
        if (!file.exists()) {
            file.mkdirs();
        }
        fileWriter = new FileWriter(pomFile, false);
        fileWriter.write(pomContent);
    } catch (IOException e) {
        e.printStackTrace();
        throw new RuntimeException("pom解析异常");
    } finally {
        try {
            if (fileWriter != null) {
                fileWriter.flush();
                fileWriter.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //将pom文件解析成依赖树,将结果存入txt文件
    InvocationRequest request = new DefaultInvocationRequest();
    request.setPomFile(pomFile);
    String dependencyTreeFilePath = POM_DOWNLOAD_PATH + pomFileName + ".txt";
    request.setGoals(Collections.singletonList("dependency:tree -D outputFile=" + dependencyTreeFilePath));

    Invoker invoker = new DefaultInvoker();
    invoker.setMavenHome(new File(MAVEN_HOME));
    try {
        invoker.execute(request);
    } catch (MavenInvocationException e) {
        e.printStackTrace();
        throw new RuntimeException("pom解析异常");
    }

    //解析依赖树文件,得到所有的依赖
    List<Dependency> dependencyList = Lists.newLinkedList();
    InputType type = InputType.TEXT;
    try {
        Reader r = new BufferedReader(new InputStreamReader(new FileInputStream(dependencyTreeFilePath), "UTF-8"));
        Parser parser = type.newParser();
        Node root = parser.parse(r);
        parseTree(root, dependencyList);

    } catch (IOException | ParseException e) {
        e.printStackTrace();
        throw new RuntimeException("pom解析异常");
    }

    return dependencyList;
}

/**
* 递归解析依赖树文件,得到所有的依赖
 * @param root
 * @param dependencyList
 */
private static void parseTree(Node root, List<Dependency> dependencyList) {
    Dependency dependency = new Dependency();
    dependency.setGroupId(root.getGroupId());
    dependency.setArtifactId(root.getArtifactId());
    dependency.setVersion(root.getVersion());
    dependencyList.add(dependency);

    LinkedList<Node> childNodes = root.getChildNodes();
    if (CollectionUtils.isNotEmpty(childNodes)) {
        childNodes.forEach(child -> parseTree(child, dependencyList));
    }
}

至此,便完成了从maven私服(nexus)拉取jar文件,解析项目pom依赖信息。

你可能感兴趣的:(Java)