macOS/Linux下maven环境及私有仓库配置折腾记

Posted by lijiahao on December 13, 2019

最近在接触公司内一个Maven项目需要配置公司内部的私有仓库,可以借此机会熟悉下Java开发大名鼎鼎的Maven,网上不少关于Maven的配置资料都是基于Windows环境,基于OSX和Linux的教程很少,因此在环境搭建及配置中也遇到不少问题,在成功搭建这个环境过程中也学到了不少东西,借此记录。

1. Maven安装及配置

  • 下载Maven,将下载文件解压后得到的文件夹(apache-maven-x.x.x)放在某个位置,如: /Users/lijiahao/apache-maven-3.3.9
  • 添加Maven环境变量,在~/.bash_profile内添加如下:
export M2_HOME=/Users/lijiahao/apache-maven-3.3.9
export PATH=$PATH:$M2_HOME/bin
  • 重新加载~/.bash_profile并生效,此时全局命令就可以使用mvn了:
$ source ~/.bash_profile
$ mvn -v
Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-11T00:41:47+08:00)
Maven home: /Users/lijiahao/apache-maven-3.3.9
Java version: 1.8.0_112, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre
Default locale: zh_CN, platform encoding: UTF-8
OS name: "mac os x", version: "10.14.6", arch: "x86_64", family: "mac"

2. 配置私有仓库

maven的配置文件存放在/Users/lijiahao/apache-maven-3.3.9/conf/settings.xml文件下,原始的xml文件内容很简单,每一项都有官方的详细注释:

<?xml version="1.0" encoding="UTF-8"?>

<!--
 | This is the configuration file for Maven. It can be specified at two levels:
 |
 |  1. User Level. This settings.xml file provides configuration for a single user,
 |                 and is normally provided in ${user.home}/.m2/settings.xml.
 |
 |                 NOTE: This location can be overridden with the CLI option:
 |
 |                 -s /path/to/user/settings.xml
 |
 |  2. Global Level. This settings.xml file provides configuration for all Maven
 |                 users on a machine (assuming they're all using the same Maven
 |                 installation). It's normally provided in
 |                 ${maven.home}/conf/settings.xml.
 |
 |                 NOTE: This location can be overridden with the CLI option:
 |
 |                 -gs /path/to/global/settings.xml
 |
 | The sections in this sample file are intended to give you a running start at
 | getting the most out of your Maven installation. Where appropriate, the default
 | values (values used when the setting is not specified) are provided.
 |
 |-->
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <!-- localRepository
   | The path to the local repository maven will use to store artifacts.
   |
   | Default: ${user.home}/.m2/repository
  <localRepository>/path/to/local/repo</localRepository>
  -->

  <!-- interactiveMode
   | This will determine whether maven prompts you when it needs input. If set to false,
   | maven will use a sensible default value, perhaps based on some other setting, for
   | the parameter in question.
   |
   | Default: true
  <interactiveMode>true</interactiveMode>
  -->

  <!-- offline
   | Determines whether maven should attempt to connect to the network when executing a build.
   | This will have an effect on artifact downloads, artifact deployment, and others.
   |
   | Default: false
  <offline>false</offline>
  -->

  <!-- pluginGroups
   | This is a list of additional group identifiers that will be searched when resolving plugins by their prefix, i.e.
   | when invoking a command line like "mvn prefix:goal". Maven will automatically add the group identifiers
   | "org.apache.maven.plugins" and "org.codehaus.mojo" if these are not already contained in the list.
   |-->
  <pluginGroups>
    <!-- pluginGroup
     | Specifies a further group identifier to use for plugin lookup.
    <pluginGroup>com.your.plugins</pluginGroup>
    -->
  </pluginGroups>

  <!-- proxies
   | This is a list of proxies which can be used on this machine to connect to the network.
   | Unless otherwise specified (by system property or command-line switch), the first proxy
   | specification in this list marked as active will be used.
   |-->
  <proxies>
    <!-- proxy
     | Specification for one proxy, to be used in connecting to the network.
     |
    <proxy>
      <id>optional</id>
      <active>true</active>
      <protocol>http</protocol>
      <username>proxyuser</username>
      <password>proxypass</password>
      <host>proxy.host.net</host>
      <port>80</port>
      <nonProxyHosts>local.net|some.host.com</nonProxyHosts>
    </proxy>
    -->
  </proxies>

  <!-- servers
   | This is a list of authentication profiles, keyed by the server-id used within the system.
   | Authentication profiles can be used whenever maven must make a connection to a remote server.
   |-->
  <servers>
    <!-- server
     | Specifies the authentication information to use when connecting to a particular server, identified by
     | a unique name within the system (referred to by the 'id' attribute below).
     |
     | NOTE: You should either specify username/password OR privateKey/passphrase, since these pairings are
     |       used together.
     |
    <server>
      <id>deploymentRepo</id>
      <username>repouser</username>
      <password>repopwd</password>
    </server>
    -->

    <!-- Another sample, using keys to authenticate.
    <server>
      <id>siteServer</id>
      <privateKey>/path/to/private/key</privateKey>
      <passphrase>optional; leave empty if not used.</passphrase>
    </server>
    -->
  </servers>

  <!-- mirrors
   | This is a list of mirrors to be used in downloading artifacts from remote repositories.
   |
   | It works like this: a POM may declare a repository to use in resolving certain artifacts.
   | However, this repository may have problems with heavy traffic at times, so people have mirrored
   | it to several places.
   |
   | That repository definition will have a unique id, so we can create a mirror reference for that
   | repository, to be used as an alternate download site. The mirror site will be the preferred
   | server for that repository.
   |-->
  <mirrors>
    <!-- mirror
     | Specifies a repository mirror site to use instead of a given repository. The repository that
     | this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
     | for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
     |
    <mirror>
      <id>mirrorId</id>
      <mirrorOf>repositoryId</mirrorOf>
      <name>Human Readable Name for this Mirror.</name>
      <url>http://my.repository.com/repo/path</url>
    </mirror>
     -->
  </mirrors>

  <!-- profiles
   | This is a list of profiles which can be activated in a variety of ways, and which can modify
   | the build process. Profiles provided in the settings.xml are intended to provide local machine-
   | specific paths and repository locations which allow the build to work in the local environment.
   |
   | For example, if you have an integration testing plugin - like cactus - that needs to know where
   | your Tomcat instance is installed, you can provide a variable here such that the variable is
   | dereferenced during the build process to configure the cactus plugin.
   |
   | As noted above, profiles can be activated in a variety of ways. One way - the activeProfiles
   | section of this document (settings.xml) - will be discussed later. Another way essentially
   | relies on the detection of a system property, either matching a particular value for the property,
   | or merely testing its existence. Profiles can also be activated by JDK version prefix, where a
   | value of '1.4' might activate a profile when the build is executed on a JDK version of '1.4.2_07'.
   | Finally, the list of active profiles can be specified directly from the command line.
   |
   | NOTE: For profiles defined in the settings.xml, you are restricted to specifying only artifact
   |       repositories, plugin repositories, and free-form properties to be used as configuration
   |       variables for plugins in the POM.
   |
   |-->
  <profiles>
    <!-- profile
     | Specifies a set of introductions to the build process, to be activated using one or more of the
     | mechanisms described above. For inheritance purposes, and to activate profiles via <activatedProfiles/>
     | or the command line, profiles have to have an ID that is unique.
     |
     | An encouraged best practice for profile identification is to use a consistent naming convention
     | for profiles, such as 'env-dev', 'env-test', 'env-production', 'user-jdcasey', 'user-brett', etc.
     | This will make it more intuitive to understand what the set of introduced profiles is attempting
     | to accomplish, particularly when you only have a list of profile id's for debug.
     |
     | This profile example uses the JDK version to trigger activation, and provides a JDK-specific repo.
    <profile>
      <id>jdk-1.4</id>

      <activation>
        <jdk>1.4</jdk>
      </activation>

      <repositories>
        <repository>
          <id>jdk14</id>
          <name>Repository for JDK 1.4 builds</name>
          <url>http://www.myhost.com/maven/jdk14</url>
          <layout>default</layout>
          <snapshotPolicy>always</snapshotPolicy>
        </repository>
      </repositories>
    </profile>
    -->

    <!--
     | Here is another profile, activated by the system property 'target-env' with a value of 'dev',
     | which provides a specific path to the Tomcat instance. To use this, your plugin configuration
     | might hypothetically look like:
     |
     | ...
     | <plugin>
     |   <groupId>org.myco.myplugins</groupId>
     |   <artifactId>myplugin</artifactId>
     |
     |   <configuration>
     |     <tomcatLocation>${tomcatPath}</tomcatLocation>
     |   </configuration>
     | </plugin>
     | ...
     |
     | NOTE: If you just wanted to inject this configuration whenever someone set 'target-env' to
     |       anything, you could just leave off the <value/> inside the activation-property.
     |
    <profile>
      <id>env-dev</id>

      <activation>
        <property>
          <name>target-env</name>
          <value>dev</value>
        </property>
      </activation>

      <properties>
        <tomcatPath>/path/to/tomcat/instance</tomcatPath>
      </properties>
    </profile>
    -->
  </profiles>

  <!-- activeProfiles
   | List of profiles that are active for all builds.
   |
  <activeProfiles>
    <activeProfile>alwaysActiveProfile</activeProfile>
    <activeProfile>anotherAlwaysActiveProfile</activeProfile>
  </activeProfiles>
  -->
</settings>

在原有settings.xml的基础上,为了能达到可以访问私有仓库的目的,几个关键字段如下: repositoriesmirrors,指定私服仓库:

    <repositories>
        <repository>
            <id>nexus</id>
            <url>https://127.0.0.0:8440/repository/maven-public/</url>
        </repository>
    </repositories>

Maven根据如上配置,会自动从私有仓库中下载构件,但是,如果私服中没有对应构件,还是会访问中央仓库,但是我们在Maven构件项目时,只是希望本地构件Maven项目时,仅仅只是访问Nexus,不希望访问中央仓库,访问中央仓库应该是Nexus的事,不是我们本地Maven配置的事,为了达到这个目标,我们还需要配置镜像:

<mirrors>
    <mirror>
      <id>nexus</id>
      <mirrorOf>*</mirrorOf>
      <url>https://127.0.0.0:8443/repository/maven-public/</url>
    </mirror> 
</mirrors>

通过镜像配置,创建一个匹配任何仓库的镜像,镜像的地址变为私服,这样Maven对于构件的请求都会转到Nexus中去,没有构件,私有仓库自己访问中央仓库缓存,本地只需要从私有仓库下载构件即可。

完整的settings.xml配置如下:

<?xml version="1.0" encoding="UTF-8"?>

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

<!-- 将maven的本地下载仓库重新指向新目录,默认值是 ${user.home}/.m2/repository -->
  <localRepository>/Users/lijiahao/apache-maven-3.3.9/repos</localRepository>

  <pluginGroups>
  </pluginGroups>
  <proxies>
  </proxies>

  <servers>
    <server>
      <id>nexus</id>
      <username>test</username>
      <password>test</password>
    </server>
  </servers>

  <mirrors>
    <mirror>
      <id>nexus</id>
      <!-- 所有的构建均从私有仓库中下载,*代表所有,也可以写成central -->
      <mirrorOf>*</mirrorOf>
      <url>https://127.0.0.0:8443/repository/maven-public/</url>
    </mirror> 

  </mirrors>


  <profiles>
    <profile>
      <id>nexus</id>
      <repositories>
        <repository>
          <id>central</id>
          <url>http://central</url>
          <releases>
            <enabled>true</enabled>
          </releases>
          <snapshots>
            <enabled>true</enabled>
          </snapshots>
        </repository>
      </repositories>
      <pluginRepositories>
        <pluginRepository>
          <id>central</id>
          <url>http://central</url>
          <releases>
            <enabled>true</enabled>
          </releases>
          <snapshots>
            <enabled>true</enabled>
          </snapshots>
        </pluginRepository>
      </pluginRepositories>
    </profile>
  </profiles>
  
  <!-- 激活 -->
  <activeProfiles>
    <activeProfile>nexus</activeProfile>
  </activeProfiles>
</settings>

3. 解决私有仓库构件无法下载问题

基本上安装了maven和配置了settings.xml之后就可以直接在maven项目的执行相关操作了,但是具体操作时发现,执行mvn命令时会报错:

$ mvn clean
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.706 s
[INFO] Finished at: 2019-12-12T15:16:05+08:00
[INFO] Final Memory: 9M/123M
[INFO] ------------------------------------------------------------------------
[ERROR] Plugin org.apache.maven.plugins:maven-clean-plugin:2.5 or one of its dependencies could not be resolved:
...
...

根据报错信息可以得知是相关依赖缺失,导致编译失败,检查settings.xml下的localRepository配置的目录,发现依赖确实没有下载下来,那是什么原因呢?我先把镜像换成maven官方地址:

    <mirror>
        <id>central</id>
        <name>Maven Repository Switchboard</name>
        <url>http://repo1.maven.org/maven2/</url>
        <mirrorOf>central</mirrorOf>
    </mirror>

重新执行mvn clean,发现可以正常安装构件,再仔细对比官方地址与私有仓库的地址,发现私有仓库地址为https开头,之后通过查询maven官方得到答案:如果远程服务器为HTTPS,还需要额外的验证信息,也就是需要导入私有仓库的证书,才能从私有仓库下载构件。在使用NEXUS进行私有仓库搭建时会有一个nexus.crt的证书,我们接下来需要把证书导入到我们本地的Java环境中: 我当前的jdk版本为1.8.0:

$ echo $JAVA_HOME
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home

使用keytool导入证书到jdk安装目录下的cacerts中,这里我对证书使用了一个名为server的别名:

$ sudo keytool -import -file ./nexus.crt -keystore $JAVA_HOME/jre/lib/security/cacerts -alias server

Enter keystore password:

注:

  • 是否需要sudo根据jdk文件权限而定,如果没有直接读写权限,则需要使用sudo;
  • osx下在提示输入秘钥库口令时,输入的秘钥不是root的密码,而是changeit,否则会提示keytool 错误: java.io.IOException: Keystore was tampered with, or password was incorrect错误,Linux下暂时还没验证此问题。

导入证书后,查询证书是否正确导入:

$ keytool -list -keystore $JAVA_HOME/jre/lib/security/cacerts | grep server
输入密钥库口令:  changeit
server, 2019-12-12, trustedCertEntry,

导入证书后再重新执行mvn clean,发现依赖已经可以正常安装,mvn命令已经可以正常使用。

总结

通过此次实践,我对Maven的理解是:Maven是Java开发的项目管理工具,跟nodejs中最常用的NPM类似,都是通过项目下一个配置文件(node环境里就是package.json,Java里就是pom.xml)来管理项目下需要使用的依赖,区别在于:

  • Java中的Maven仓库在开发者电脑上是全局的,所有项目的依赖都集中存放在本地仓库中,其实跟Python的PIP管理很像;
  • Node中的依赖如果你不写package.json,那么依赖的就是全局的库;如果写了package.json,就会把所有依赖下载到node_modules文件夹,在node项目中可以轻松修改第三方依赖的源码,可以很容易该出自己特制的三方依赖;
  • 如果Java项目中需要其他特有版本的依赖,需要单独在指定版本,至于如果需要修改构件源码,这个需要后续进一步学习了,或许对于Java而言,第三方构件属于比较严谨的库,一般不会进行修改,这可能也是Maven当初设计的初衷。

(完)


原创不易,如果觉得这篇文章对你有帮助,不如赏杯咖啡吧
微信
支付宝