使用Maven复制文件的最佳实践

213
我有一些配置文件和各种文档,希望使用Maven2将它们从开发环境复制到dev-server目录。奇怪的是,Maven在这个任务上似乎不够强大。
一些选项:
- 简单地在Maven中使用任务。
<copy file="src/main/resources/config.properties" tofile="${project.server.config}/config.properties"/>
  • 使用Ant插件执行copy

    • 构建类型为zip的artifact,与POM的“main”artifact通常为jar的类型并行,然后将该artifact从存储库解压缩到目标目录中。

    • maven-resources插件,如下所述。

    • Maven Assembly插件 - 但这似乎需要大量手动定义,当我想简单和“传统”地做事情时。

    • 此页面甚至展示了如何构建插件来进行复制!

    • maven-upload插件,如下所述。

    • maven-dependency-plugincopy,如下所述。


所有这些都似乎是不必要的:Maven应该擅长在没有麻烦和烦恼的情况下完成这些标准任务。

有什么建议吗?


3
Maven建立在生命周期和阶段概念基础之上,将随机复制文件到远程服务器这一任务并不符合此概念。始终将您的项目作为一个整体来考虑。 - André
3
这些似乎过于临时应急了:Maven 的优势在于毫不费力地完成这些标准任务。而你正在做的并不是一个标准任务。如果你的构件是 war/ear 文件,那么使用 cargo 插件(cargo.codehaus.org/Maven2+plugin#Maven2plugin-get…)就可以简单地完成。你所描述的听起来是高度针对你的部署方式而非标准的 Java 应用程序容器部署。Maven 实际上并不适合处理向实时服务器进行部署活动 - 它更适合构建/开发活动。 - whaley
76
@André: 我一遍又一遍听到这个论点,但抱歉,那是胡说八道。考虑整体项目没有问题,但任何像样的构建系统的一部分应该是让我能够以简单明了的方式完成任务X的功能,例如复制文件,而Maven做不到这一点。近来出现了许多采用构建脚本即代码范例的项目(如Gradle、SBT或Buildr)之所以如此,是有原因的。 - mxk
我建议为构建工件和部署给定工件分别拥有一个pom.xml。 - Thorbjørn Ravn Andersen
以上所有建议似乎仍然不能让我将不同项目/构件中的特定文件复制到一个maven项目中。我有一些文件在一个构件的src/main/folder下,这个构件变成了一个jar包。我尝试使用dependency-copy maven插件,但是我没有找到一种方法来指定我想要复制哪些文件,每次都会在assembly文件中得到整个jar文件。所有其他建议,如资源,似乎都不允许我指定一个构件而不是项目内的资源。 - Alexandre Thenorio
14个回答

143
<build>
    <plugins>
        ...
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <version>2.3</version>
        </plugin>
    </plugins>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include> **/*.properties</include>
            </includes>
        </resource>
    </resources>
    ...
</build>

谢谢@Peter,这很有用。我现在使用resources-plugin的copy-resources目标而不是antrun。后者实际上更简单和直观地定义,但我无法让它(版本1.3)将所有Maven自定义属性(在<properties>部分中定义)传递给antrun,所以我切换到了resources-plugin。 - Cornel Masson
2
我曾经认为这是正确的答案……直到我意识到资源插件没有跳过配置。Antrun才是解决之道。 - Mike Post
创建跳过配置文件不应该很难。我没有使用antrun,所以无法说哪个更容易/更好。 - Vivek Chavda

132

不要回避Antrun插件。仅仅因为有些人认为Ant和Maven是对立的,其实它们并不是。如果需要执行一些无法避免的一次性自定义操作,请使用复制任务:

<project>
  [...]
  <build>
    <plugins>
      [...]
      <plugin>
        <artifactId>maven-antrun-plugin</artifactId>
        <executions>
          <execution>
            <phase>deploy</phase>
            <configuration>
              <target>

                <!--
                  Place any Ant task here. You can add anything
                  you can add between <target> and </target> in a
                  build.xml.
                -->

              </target>
            </configuration>
            <goals>
              <goal>run</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  [...]
</project>
在回答这个问题时,我关注了您所询问的细节。如何复制文件?这个问题和变量名称引导我去考虑一个更大的问题:“有没有更好的方法来处理服务器配置?”使用Maven作为构建系统来生成可部署的工件,然后在单独的模块或其他地方执行这些自定义操作。如果您分享一些构建环境的详细信息,可能会有更好的方法 - 有插件可以为许多服务器进行配置。您能否附加一个在服务器根目录中解压的程序集?您使用哪个服务器? 再次强调,我相信有更好的方法。

任务描述符现在已经过时了吗? - Matt
4
是的,task参数现已被弃用(Antrun插件)。你应该使用target代替(自1.5版本起)。不幸的是,有些例子混淆了这一点;例如 target参数和version<1.5。 - cuh
3
这怎么能是被接受的答案呢?明显应该向Maven提交更改请求,让复制变得更加简单。 - Wolfgang Fahl

58

要复制一个文件,请使用:

        <plugin>
            <artifactId>maven-resources-plugin</artifactId>
            <version>3.1.0</version>
            <executions>
                <execution>
                    <id>copy-resource-one</id>
                    <phase>install</phase>
                    <goals>
                        <goal>copy-resources</goal>
                    </goals>

                    <configuration>
                        <outputDirectory>${basedir}/destination-folder</outputDirectory>
                        <resources>
                            <resource>
                                <directory>/source-folder</directory>
                                <includes>
                                    <include>file.jar</include>
                                </includes>
                            </resource>
                        </resources>
                    </configuration>
                </execution>
           </executions>
        </plugin>

为了复制带有子文件夹的文件夹,请使用以下配置:

           <configuration>
              <outputDirectory>${basedir}/target-folder</outputDirectory>
              <resources>          
                <resource>
                  <directory>/source-folder</directory>
                  <filtering>true</filtering>
                </resource>
              </resources>              
            </configuration>  

2
在Maven中,过滤指的是字符串插值,因此我会省略<filtering>以防止对使用${...}变量的脚本文件造成不必要的更改。 - Gerold Broser

24

对于简单的复制任务,我可以推荐copy-rename-maven-plugin。它很直观且易于使用:

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>com.coderplus.maven.plugins</groupId>
        <artifactId>copy-rename-maven-plugin</artifactId>
        <version>1.0</version>
        <executions>
          <execution>
            <id>copy-file</id>
            <phase>generate-sources</phase>
            <goals>
              <goal>copy</goal>
            </goals>
            <configuration>
              <sourceFile>src/someDirectory/test.environment.properties</sourceFile>
              <destinationFile>target/someDir/environment.properties</destinationFile>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

如果您想复制多个文件,请将<sourceFile>...</destinationFile>部分替换为

<fileSets>
  <fileSet>
    <sourceFile>src/someDirectory/test.environment.properties</sourceFile>
    <destinationFile>target/someDir/environment.properties</destinationFile>
  </fileSet>
  <fileSet>
    <sourceFile>src/someDirectory/test.logback.xml</sourceFile>
    <destinationFile>target/someDir/logback.xml</destinationFile>
  </fileSet>                
</fileSets>

此外,如果需要,您可以在多个阶段指定多个执行,第二个目标是“重命名”,它只是按照字面意思做,而其余配置保持不变。有关更多用法示例,请参阅使用页面

注意:此插件仅复制文件,不能复制目录。(感谢@james.garriss找到此限制。)


2
尽管我喜欢这个插件,但令人惊讶的是它无法复制目录。 - james.garriss
3
@james.garriss,我之前不知道这个限制,但很遗憾你是对的。我会将这句话编辑到我的回答中,以便可能节省一些人找这个信息所需的时间。 - morten.c
应当注意到,该插件无法插值变量。我想利用这个插件将POM中的项目版本存储到项目中的各个配置文件中,但由于这个限制,这个插件对我来说是无用的。 - rawcode
该插件也不支持通配符。 - Sven Döring

21

maven的依赖插件为我节省了很多时间,避免了折腾ant任务:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
        <execution>
            <id>install-jar</id>
            <phase>install</phase>
            <goals>
                <goal>copy</goal>
            </goals>
            <configuration>
                <artifactItems>
                    <artifactItem>
                        <groupId>...</groupId>
                        <artifactId>...</artifactId>
                        <version>...</version>
                    </artifactItem>
                </artifactItems>
                <outputDirectory>...</outputDirectory>
                <stripVersion>true</stripVersion>
            </configuration>
        </execution>
    </executions>
</plugin>

dependency:copy是有文档记录的,还有更多有用的目标,例如unpack。


3
多年来我都没有使用 Ant,而且我也不想因为这么简单的事情而重新开始使用它。非常感谢你的回答。 - Gustave

7

上面提供的蚂蚁解决方案最容易配置,但我在使用Atlassian的maven-upload-plugin时也取得了成功。我找不到好的文档,这是我的使用方法:

<build>
  <plugin>
    <groupId>com.atlassian.maven.plugins</groupId>
    <artifactId>maven-upload-plugin</artifactId>
    <version>1.1</version>
    <configuration>
       <resourceSrc>
             ${project.build.directory}/${project.build.finalName}.${project.packaging}
       </resourceSrc>
       <resourceDest>${jboss.deployDir}</resourceDest>
       <serverId>${jboss.host}</serverId>
       <url>${jboss.deployUrl}</url>
     </configuration>
  </plugin>
</build>

上面提到的类似"${jboss.host}"的变量是在我的~/.m2/settings.xml文件中定义的,并使用maven配置文件激活。这个解决方案并不局限于JBoss,这只是我为变量命名的方式。我有一个用于开发、测试和生产环境的配置文件。因此,要将我的ear文件上传到测试环境中的JBoss实例,我需要执行以下操作:

mvn upload:upload -P test

这是来自settings.xml的一段代码片段:
<server>
  <id>localhost</id>
  <username>username</username>
  <password>{Pz+6YRsDJ8dUJD7XE8=} an encrypted password. Supported since maven 2.1</password>
</server>
...
<profiles>
  <profile>
    <id>dev</id>
    <properties>
      <jboss.host>localhost</jboss.host> 
      <jboss.deployDir>/opt/jboss/server/default/deploy/</jboss.deployDir>
      <jboss.deployUrl>scp://root@localhost</jboss.deployUrl>
    </properties>
  </profile>
  <profile>
    <id>test</id>
    <properties>
       <jboss.host>testserver</jboss.host>
       ...

注意事项: 安装此插件的 Atlassian maven 仓库在这里:https://maven.atlassian.com/public/

建议下载源代码并查看内部文档,以了解插件提供的所有功能。

`


6
嗯,Maven并不擅长进行细粒度任务,它不像Bash或Ant那样是一种脚本语言,而是声明式的 - 你说 - 我需要一个war包或ear包,然后你就会得到它。然而,如果你需要自定义war包或ear包内部的内容,那么你就会遇到问题。它不像Ant那样是过程性的,而是声明式的。
这在开始时有一些优点,在结束时可能有很多缺点。
我想最初的概念是拥有良好的插件,让它们“只工作”,但实际情况与此不同,如果你做非标准的事情,会有很多问题。
然而,如果你在pom文件和少量自定义插件上付出足够的努力,你将获得比Ant更好的构建环境(当然,这取决于你的项目,对于更大的项目,这变得越来越真实)。

4

我对 copy-maven-plugin 有很好的使用经验。相比于 maven-resources-plugin,它具有更方便、更简洁的语法。


9
抱歉,copy-maven-plugin 与 maven 3.1.x 不兼容。 - Hakan
3
跟踪 Maven 3.1 兼容性的问题在这里:https://github.com/evgeny-goldin/maven-plugins/issues/10 - koppor
1
忘记这个插件吧...寻找它的分支。 - Kukeltje

4

一种通用的复制任意文件的方法是利用Maven Wagon传输抽象。它可以通过像fileHTTPFTPSCPWebDAV等协议处理各种目标。

有几个插件提供了通过使用Wagon复制文件的功能。其中最著名的是:

  • Out-of-the-box Maven Deploy Plugin

    There is the deploy-file goal. It it quite inflexible but can get the job done:

    mvn deploy:deploy-file -Dfile=/path/to/your/file.ext -DgroupId=foo 
    -DartifactId=bar -Dversion=1.0 -Durl=<url> -DgeneratePom=false
    

    Significant disadvantage to using Maven Deploy Plugin is that it is designated to work with Maven repositories. It assumes particular structure and metadata. You can see that the file is placed under foo/bar/1.0/file-1.0.ext and checksum files are created. There is no way around this.

  • Wagon Maven Plugin

    Use the upload-single goal:

    mvn org.codehaus.mojo:wagon-maven-plugin:upload-single
    -Dwagon.fromFile=/path/to/your/file.ext -Dwagon.url=<url>
    

    The use of Wagon Maven Plugin for copying is straightforward and seems to be the most versatile.

在上面的示例中,<url> 可以是任何支持的协议。请参阅现有的Wagon Providers列表。例如:
  • 将文件复制到本地:file:///copy/to
  • 将文件复制到运行 SSH 的远程主机:scp://host:22/copy/to
上面的示例在命令行中传递插件参数。或者,插件可以直接在POM中配置。然后,调用将简单地像这样: mvn deploy:deploy-file@configured-execution-id。或者它可以绑定到特定的构建阶段。
请注意,对于像SCP 这样的协议,您需要在您的POM中定义一个扩展程序以使其工作:
<build>
  [...]
  <extensions>
    <extension>
      <groupId>org.apache.maven.wagon</groupId>
      <artifactId>wagon-ssh</artifactId>
      <version>2.12</version>
    </extension>
  </extensions>


如果您复制到的目标需要身份验证,可以通过服务器设置提供凭据。传递给插件的repositoryId/serverId必须与设置中定义的服务器匹配。


3
我只能假设你的 ${project.server.config} 属性是自定义定义的,并且不在标准目录结构之内。
如果是这样,那么我会使用复制任务。

假设我注意将文件放入标准目录布局中,Maven能否将它们原封不动地复制到目标位置,而不是压缩成zip/jar格式? - Joshua Fox

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接