Home > Archives > Maven项目中不同环境设置不同的依赖范围(scope)

Maven项目中不同环境设置不同的依赖范围(scope)

Published on

在Spark相关的项目中,Spark相关的依赖(spark-core, spark-sql)都可以在spark-submit提交时由spark提供,所以可以设置成provided,避免在使用maven-shade-plugin打成fat jar的时候包太大。但另外一个问题马上出现,provided依赖在编译的时候没有问题,但是在运行的时候就会出现问题。

所以,我们需要解决的问题是: 如何在本地开发时使用默认的compile依赖,然后在打包的时候变换成provided依赖。比较直观的一种做法就是 – 打包时动态更改pom.xml,输出到build.xml中,通过mvn相关命令打包,与此同时显示指定pom文件的地址,即动态修改后并输出的pom文件。

pom.xml文件修改

pom.xml文件修改实际上就是对于XML节点的处理,Scala提供了scala.xml.Node对其进行抽象。

abstract class Node extends NodeSeq { 

  // Node的标签
  def label: String
  
  // 命名空间
  def namespace = getNamespace(this.prefix)

  // 命名空间相关的绑定 xmlns:xx, xmlns:yy;默认是没有的,除了预定义xml
  // case class NamespaceBinding(prefix: String, uri: String, parent: NamespaceBinding)
  def scope: NamespaceBinding = TopScope   

}
<!-- XML node ->
<dependency
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://maven.apache.org/POM/4.0.0">
  <groupId>org.apache.spark</groupId>
  <artifactId>spark-sql_2.10</artifactId>
  <version>1.6.2</version>
</dependency>

上面的XML Node中命名空间(xmlns后面不带任何东西)为http://maven.apache.org/POM/4.0.0,标签为dependency。命令空间绑定的父子关系是通过书写的顺序来形成的,比如说上面的xmlns:xsi就是xmlns的父NamespaceBinding,这一点在下面我们清除命令空间绑定的时候会使用到。

所以,我们需要锁定pom.xml中的dependency Node然后,为它的子Node添加一个Node,即<scope>provided</scope>,通过Scala的模式匹配,可以很容易做到这一点。

val xml = XML.load("/xx/xx/pom.xml")
val newPom =
  xml match {
    case e @ Elem(_, _, _, scope, nodes @ _*) => {
      val changedSubNodes =
        nodes.map { node =>
          if (node.label == "dependencies") {
            node match {
              case <dependencies>{ dependencies @ _* }</dependencies> => {
                val changedChild =
                  dependencies.flatMap { dependencyNode =>
                    val artifactId = (dependencyNode \ "artifactId").text
                    dependencyNode match {
                      case elem: Elem =>
                        val toBeAdded =
                          if (artifactIds.contains(artifactId)) {
                            elem.child ++ appendedNode
                          } else {
                            elem.child
                          }
                        Some(elem.copy(child = toBeAdded))
                      case other => Some(other)
                    }
                  }
                <dependencies>{ changedChild }</dependencies>
              }
            }
          } else {
            node
          }
        }
      // 清除子Node命名空间
      e.copy(child = changedSubNodes.map(clearScope))
    }
    case _ => xml
  }
newPom

生成之后,通过XML.save输出到文件中(build.xml)。

构建进程对象并执行

由于更改后的pom文件(build.xml)已经产生,所以可以直接运行mvn clean package -f /xx/build.xml,但既然已经在程序中将pom文件生成了何不直接通过Process对象执行相关的命令,有如下几点需要注意:

def mvnPackage(pomPath: String) = {
  val cmds = List("mvn", "clean", "-DskipTests", "package", "-f", pomPath)
  val processBuilder = new ProcessBuilder(cmds.toArray: _*).redirectErrorStream(true)
  
  var process: Process = null
  try {
    process = processBuilder.start()
    // mvn package命令运行之后的日志输出
    val bufferedSource = Source.fromInputStream(process.getInputStream())
    bufferedSource.getLines().foreach { line =>
      println(line)
    }
  } catch {
    case ex: Exception =>
      throw ex
  } finally {
    if (process != null) {
      process.destroyForcibly()
    }
  }
}

虽然,本文中提到的方法实用性并不是太高,但这种解决问题的方式个人觉得还是挺有意思的,在此过程中,进一步熟悉了Scala XML操作、Java中代码中操作Process等知识点。完整版代码,请参照Build.scala

参考

> W3C Xml教程

> Maven依赖详解

> Maven打包时指定输出文件夹路径

声明: 本文采用 BY-NC-SA 授权。转载请注明转自: Allen写字的地方