從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.shared</groupId>
    <artifactId>maven-invoker</artifactId>
    <version>3.0.1</version>
</dependency>
<dependency>
    <groupId>org.whitesource</groupId>
    <artifactId>maven-dependency-tree-parser</artifactId>
    <version>1.0.5</version>
</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();
		}
    }
}

執行以上代碼,打開解析出的依賴樹文件,可看到輸出的依賴關係如下:
在這裏插入圖片描述
這樣的結構很直觀地給我們展示了導入的依賴結構,不僅方便我們直接觀看,同時利用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節點,我們遞歸便能解析出所有的依賴。
在這裏插入圖片描述
有了上面的基礎,我們已經知道了如何利用代碼實現pom文件的解析,下面我們將實現一個更完整的解析過程。

三. 從Nexus倉庫拉取jar文件,讀取pom依賴信息

每個公司都有自己的maven私服(nexus倉庫),存儲了公司項目的所有jar文件。我們要實現的是一個完整的過程:利用一個dependency的座標,到nexus倉庫拉取下來jar文件,並讀取出該jar的pom依賴信息。

要解析jar文件,需要傳入jar文件的依賴座標,形如:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</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
 * <dependency>
 * <groupId>com.example</groupId>
 * <artifactId>demo</artifactId>
 * <version>0.0.1-SNAPSHOT</version>
 * </dependency>
 * 
 * @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依賴信息。

發佈了27 篇原創文章 · 獲贊 16 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章