关于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>
在一个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对象。
利用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文件的解析,下面我们将实现一个更完整的解析过程。
每个公司都有自己的maven私服(nexus仓库),存储了公司项目的所有jar文件。我们要实现的是一个完整的过程:利用一个dependency的坐标,到nexus仓库拉取下来jar文件,并读取出该jar的pom依赖信息。
要解析jar文件,需要传入jar文件的依赖坐标,形如:
<dependency>
<groupId>com.examplegroupId>
<artifactId>demoartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
用代码整合这三步如下,下面将进行代码的拆分:
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);
}
//公司的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();
}
/**
* 从远程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;
}
//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依赖信息。