maven实战应用

Dec 1, 2017


概要

  1. maven之基本知识
  2. maven之scope
  3. maven依赖管理
  4. maven模块化管理
  5. maven在实际项目中应用

基本知识


本篇主要讲解的是在项目中如何运用maven,下面只对maven的基本知识做下简要的分析:

1、作用:

  • 一开始以为只是简单的管理jar避免发生冲突、就不用自己下载jar然后引用到项目中
  • 免去了每次javac,maven为我们组织好了项目的结构
  • 使用maven profile还可以根据对应的环境将对应的环境中的配置打成包(jar、war)进行运行。
  • 其实在package之前还可以跑一遍单元测试,降低了程序错误率。
  • 其中还支持模块和继承
  • 根据pom文件中将对应范围(scope)的依赖可以统一打成一个war包,其中默认jar不会将其他依赖包打进去,但是可以使用 maven-assembly-plugin maven-shade-plugin进行将依赖的包打进去并且可以配置对应类的入口main方法和classpath 直接可以使用 java -jar projectname运行

2、maven项目的结构

props

其实在运行某个类的main的方法时会将src/main/java中的类文件放在当前项目下的target文件夹下,该文件夹中有 classes(存放主程序相关的class文件和配置文件)和test-classes(存放测试程序相关的类文件和测试用的配置文件)和打包好的jar或war包。其实只是运行main方法时,并不会将resources目录下的文件打包,只是简单的编译 其实这有个容易忽视的问题,当运行main方法时总是报说某个文件在classpath找不到的情况,其实resources下的文件没有放到classes目录下。只有当运行 mvn package时才会进行对resoures目录下的配置文件打包。其main/resources下的文件会放在 target/classes下面 test/resources下的文件放在 target/test-classes/下

3、maven仓库的概念

props

可以配置私服:一般公司内部开发的工具jar可以上传到私服里,同时也可以从中央仓库获取jar。 中央仓库: 一些开源的jar上传到中央仓库 例如 spring相关的jar 本地仓库:首先解析pom文件中的依赖时,从本地库中获取如果没有找到则到对应配置的私服中去获取,如果没有配置私服则到中央仓库中获取,最后存放在本地仓库

4、生命周期阶段

props

5、maven常用的命令

  • 创建Maven的普通java项目: mvn archetype:create -DgroupId=packageName -DartifactId=projectName
  • 创建Maven的Web项目: mvn archetype:create -DgroupId=packageName -DartifactId=webappName -DarchetypeArtifactId=maven-archetype-webapp
  • 编译源代码: mvn compile
  • 编译测试代码:mvn test-compile
  • 运行测试:mvn test
  • 产生site:mvn site
  • 打包:mvn package
  • 在本地repository中安装jar:mvn install
  • 清除产生的项目:mvn clean
  • 生成eclipse项目:mvn eclipse:eclipse
  • 生成idea项目:mvn idea:idea
  • 组合使用goal命令,如只打包不测试:mvn -Dtest package
  • 编译测试的内容:mvn test-compile
  • 只打jar包: mvn jar:jar
  • 只测试而不编译,也不测试编译:mvn test -skipping compile -skipping test-compile ( -skipping 的灵活运用,当然也可以用于其他组合命令)
  • 清除eclipse的一些系统设置:mvn eclipse:clean
  • mvn package -Dmaven.test.skip=true 打包时跳过单元测试阶段

scope的含义


        <dependency>
        <groupId>com.dynamo</groupId>
        <artifactId>sparrow</artifactId>
        <version>1.0.0</version>
        <scope>provided</scope>
        </dependency>

scope决定了该依赖的jar在那个阶段可以使用(编译classpath、测试classepath、主程序的运行、测试运行) 就是用来控制编译时classpath、测试classpath和运行classpath之间的关系

1、compile 这个是默认的scope对应编译时、测试、运行(打包时能包含到war包中,jar包则不能,必须使用对应的maven-assembly-plugin和maven-shade-plugin插件才可以将依赖包打包)

2、test 只能在 src/test/java下的类能使用和运行单元测试时使用,在编译classpat和运行时calsspath无法使用 例如junit easymock

3、provided 只在编译、测试时使用,但是不打到war包中 例如servlet-api jar这个jar包tomcat会自带,否则会出现版本不一致问题

4、runtime 编译classpath和测试classpath不能使用,只在运行时使用,会打到war包中,一般是通过动态加载或接口反射加载的情况比较多。也就是说程序只使用了接口 例如JDBC驱动

5、system 系统范围,与provided类似,只是标记为该scope的依赖包需要明确指定基于文件系统的jar包路径。因为需要通过systemPath指定本地jar文件路径

6、import 为了解决maven单继承的问题,同时也避免10几个子项目依赖父项目,导致父项目的依赖过多不便管理,所以使用单独的pom进行管理依赖

props

此外,我们还能够创建多个这样的依赖管理pom,以更细化的方式管理依赖。这种做法与面向对象设计中使用组合而非继承也有点相似的味道

注意:import scope只能用在dependencyManagement里面,其中如果继承了父项目的pom时可以省去 import的定义

依赖管理


注:下面讲的项目就指经过打包好的jar和pom文件

**1、 传递依赖 **

props

传递性依赖是和 pom.xml包含的dependency中的scope和 (表示该项目继承了父项目,下面会提到)有关联的。 例如 A项目依赖B项目,maven首先会根据坐标(groupId+artifactId+version)到本地找对应的jar,找到了则引入否则到中央仓库中获取。其中maven也会读取对应坐标下的pom文件,然后继续 引入pom中的其他的依赖(除了scope=test或provided的jar)。如果该pom还继承了父项目的pom时则继续引入父项目中的中的依赖(除scope=test或provided的jar)。这种情况就属于传递性依赖

2、排除依赖 当我们引入第三方jar包的时候,难免会引入传递性依赖,有些时候这是好事,然而有些时候我们不需要其中的一些传递性依赖比如上例中的项目,我们不想引入传递性依赖commons-logging,我们可以使用exclusions元素声明排除依赖,exclusions可以包含一个或者多个exclusion子元素,因此可以排除一个或者多个传递性依赖。这样也是避免重复jar,防止jar冲突。其中exclusions包含在dependency标签中,其中exclusion中不指定version,表示任意一个版本都不引入

        <exclusions>
        <exclusion>
        <groupId>com.dynamo</groupId>
        <artifactId>sparrow</artifactId>
        </exclusion>
        </exclusions>

3、 依赖归类

为了升级引入jar包,只需要改动一处地方的version,连着属于相同项目下的子模块一起升级

        <properties>  
        <springframework.version>2.5.6</springframework.version>  
        </properties> 

         <dependency>  
         <groupId>org.springframework</groupId>  
         <artifactId>spring-core</artifactId>  
         <version>${springframework.version}</version>  
         </dependency>  
         <dependency>  
         <groupId>org.springframework</groupId>  
         <artifactId>spring-beans</artifactId>  
         <version>${springframework.version}</version>             
         </dependency> 

模块化管理


1、基本概念

  • maven支持子模块和继承的概念,首先在父项目中pom.xml声明包括了哪些子模块,同样在子模块也可以通过进行继承父项目中的jar(如果父项目中引入了jar)或 pom.xml文件中的配置

2、使用module执行过程

  • 父工程在各个子模块中起到了协调作用,直接在父工程下执行mvn clean package时,首先会按照jar的依赖关系顺序,处理执行顺序。如果本地库没有改依赖jar那么现场编译打包安装到本地库,完成依赖关系。如果自己在子项目进行mvn clean package如果依赖的jar已经安装到本地库,则打包成功否则失败 这里又牵扯到Maven如何查找依赖的问题,简单来说,Maven会先在本地Repository中查找依赖,如果依赖存在,则使用该依赖,如果不存在,则通过pom.xml中的Repository配置从远程下载依赖到本地Repository中。默认情况下,Maven将使用Maven Central Repository作为远端Repository。于是你又有问题了:“在pom.xml中为什么没有看到这样的配置信息啊?”原因在于,任何一个Maven工程都默认地继承自一个Super POM,Repository的配置信息便包含在其中

3、模块和继承区别

  • 如果保留webapp和core中对maven-multi-module的父关系声明,即保留 “... ”,而删除maven-multi-module中的子模块声明,即“...”,会发生什么情况?此时,整个工程已经不是一个多模块工程,而只是具有父子关系的多个工程集合。如果我们在maven-multi-module目录下执行“mvn clean install”,Maven只会在maven-multi-module本身上执行该命令,继而只会将maven-multi-module安装到本地Repository中,而不会在webapp和core模块上执行该命令,因为Maven根本就不知道这两个子模块的存在。另外,如果我们在webapp目录下执行相同的命令,由于由子到父的关系还存在,Maven会在本地的Repository中找到maven-multi-module的pom.xml文件和对core的依赖(当然前提是他们存在于本地的Repository中),然后顺利执行该命令。

这时,如果我们要发布webapp,那么我们需要先在maven-multi-module目录下执行“mvn clean install”将最新的父pom安装在本地Repository中,再在core目录下执行相同的命令将最新的core模块安装在本地Repository中,最后在webapp目录下执行相同的命令完成最终war包的安装。麻烦。

  • 如果保留maven-multi-module中的子模块声明,而删除webapp和core中对maven-multi-module的父关系声明,又会出现什么情况呢?此时整个工程只是一个多模块工程,而没有父子关系。Maven会正确处理模块之间的依赖关系,即在webapp模块上执行Maven命令之前,会先在core模块上执行该命令,但是由于core和webapp模块不再继承自maven-multi-module,对于每一个依赖,他们都需要自己声明,比如我们需要分别在webapp和core的pom.xml文件中声明对Junit依赖

core和webapp只是在逻辑上属于同一个总工程,那么我们完全可以只声明模块关系,而不用声明父子关系。如果core和webapp分别处理两个不同的领域,但是它们又共享了很多,比如依赖等,那么我们可以将core和webapp分别继承自同一个父pom工程

实际项目中运用


1、maven打包插件

前言

java项目中肯定少不了spring,其中spring运行又要加载spring的xml配置文件,其中配置文件头部会声明命名空间对应的xsd文件例如xsd文件 http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ,所以需要找到对应的xsd文件。然后会根据当前的classpath下的META-INF目录下找到对应的spring.schemas(里面包含了xsd文件对应存放位置,key为xsd文件 http://www.springframework.org/schema/aop/spring-aop-3.0.xsd value=META-INFA/spring-aop-3.0.xsd),spring.handlers(key=xxx.xsd value=com.dynamo.xxxHandler)文件定义了将命名空间对应的元素以及属性转换成对应的对象的处理类

一共有两种方式可以进行一起打包 maven-assembly-plugin和maven-shade-plugin两种方式

  • maven-assembly-plugin

          1. <plugin>  
          2.     <artifactId>maven-assembly-plugin</artifactId>  
          3.     <configuration>  
          4.         <appendAssemblyId>false</appendAssemblyId>  
          5.         <descriptorRefs>  
          6.             <descriptorRef>jar-with-dependencies</descriptorRef>  
          7.         </descriptorRefs>  
          8.         <archive>  
          9.             <manifest>  
          10.                 <mainClass>com.dynamo.examples.Main</mainClass>  
          11.             </manifest>  
          12.         </archive>  
          13.     </configuration>  
          14.     <executions>  
          15.         <execution>  
          16.             <id>make-assembly</id>  
          17.             <phase>package</phase>  
          18.             <goals>  
          19.                 <goal>assembly</goal>  
          20.             </goals>  
          21.         </execution>  
          22.     </executions>  
          23. </plugin>  
    

mainClass指定是程序执行的入口类,并执行该类对应的main方法 java -jar XXX.jar进行运行 根据上面的前言中描述,首先在本地找若找不到机会到www.springframework.org中找,如果此时网络断了或访问不通则启动报错,本地找不到的原因是:由于需要导入依赖的包也有可能依赖spring的jar,版本不同,而maven-assembly-plugin 只会任选一个版本进行打包,这就会spring中xml文件引入的是高版本的xsd,而打包的是低版本。为了解决这种情况直接访问本地获取xsd文件,就应该使用 maven-shade-plugin打包时可以把所有spring jar包下的META-INF中的 spring.schemas文件进行合并成一个文件进行打包

maven-shade-plugin打包形式

	1. <plugin>  
	2.     <groupId>org.apache.maven.plugins</groupId>  
	3.     <artifactId>maven-shade-plugin</artifactId>  
	4.     <version>1.4</version>  
	5.     <executions>  
	6.         <execution>  
	7.             <phase>package</phase>  
	8.             <goals>  
	9.                 <goal>shade</goal>  
	10.             </goals>  
	11.             <configuration>  
	12.                 <transformers>  
	13.                     <transformer  
	14.                         implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">  
	15.                         <resource>META-INF/spring.handlers</resource>  
	16.                     </transformer>  
	17.                     <transformer  
	18.                         implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">  
	19.                         <mainClass>com.chenzhou.examples.Main</mainClass>  
	20.                     </transformer>  
	21.                     <transformer  
	22.                         implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">  
	23.                         <resource>META-INF/spring.schemas</resource>  
	24.                     </transformer>  
	25.                 </transformers>  
	26.             </configuration>  
	27.         </execution>  
	28.     </executions>  
	29. </plugin>  

上面这段配置意思是把spring.handlers和spring.schemas文件以append方式加入到一个文件中最后构建的jar包中或war包,这样就不会存在出现xsd找不到的情况。

2、实际项目中的maven结构

如果比较大的项目,需要将该项目进行拆分成多个子项目,如果多个子项目存在依赖多个相同的其他项目时可以使用继承父项目。其中在打包时可以单出来一个模块,专门将各个子模块集合在一起,打成一个jar。 其实会使用到以上的 模块和继承、和scope的概念

父工程下的pom.xml文件大致为:

   <modelVersion>4.0.0</modelVersion>
	<groupId>com.dynamo</groupId>
	<artifactId>rpc-simple</artifactId>
	<packaging>pom</packaging>
	<version>1.0.0</version>
	<name>${project.artifactId}</name>
	<url>http://maven.apache.org</url>

	<modules>
		<module>sparrow-remoting</module>
		<module>sparrow-rpc</module>
		<module>sparrow</module>
	</modules>
<properties>
       <spring_version>3.2.16.RELEASE</spring_version>
		<javassist_version>3.20.0-GA</javassist_version>
		<netty_version>3.2.5.Final</netty_versio>
		</properties>
			<dependencyManagement>
	<dependencies>
		<!-- Common libs -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-framework-bom</artifactId>
			<version>${spring_version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
		<dependency>
			<groupId>org.javassist</groupId>
			<artifactId>javassist</artifactId>
			<version>${javassist_version}</version>
				</dependency>
				</dependencies>
				</dependencyManagement>
				<dependencies>
			<dependency>//此时使用了以上 <dependencyManagement>定义的verion和scope
				<groupId>junit</groupId> 
				<artifactId>junit</artifactId>
			</dependency>
	</dependencies>

子项目中pom.xml

<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.dynamo</groupId>
		<artifactId>rpc-simple</artifactId>
		<version>1.0.0</version>
	</parent>

<artifactId>sparrow-rpc</artifactId>
<packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>The rpc module of sparrow project</description>

<properties>
	<skip_maven_deploy>true</skip_maven_deploy>
</properties>
<modules>
	<module>sparrow-rpc-api</module>
	<module>sparrow-rpc-default</module>
</modules>

子项目的子项目 pom.xml

<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.dynamo</groupId>
		<artifactId>sparrow-rpc</artifactId>
		<version>1.0.0</version>
	</parent>

<artifactId>sparrow-rpc-default</artifactId>
<packaging>jar</packaging>
<name>rpc-api-default</name>
<description>The rpc module of sparrow project</description>

<properties>
	<skip_maven_deploy>true</skip_maven_deploy>
</properties>
<dependencies>
	<dependency>
		<groupId>com.dynamo</groupId>
		<artifactId>sparrow-common</artifactId>
		<version>${project.parent.version}</version>
	</dependency>
		<dependency>
		 <dependency>//此时并不需要指定version,因为在父项目中的dependencyManagement中已经定义了此依赖
        <groupId>org.jboss.netty</groupId>
        <artifactId>netty</artifactId>
    </dependency>
	</dependency>
</dependencies>

单独将各个子模块中的类合并成一个jar的pom.xml,其中也是父项目的子模块又继承了父项目模块、如果在下面artifactSet中没有指定 include包含的类时,当其他项目中依赖此jar时,会将此jar依赖的其他的jar也会引入进来

	<parent>
			<groupId>com.dynamo</groupId>
			<artifactId>rpc-simple</artifactId>
			<version>1.0.0</version>
		</parent>
		<artifactId>sparrow</artifactId>
		<packaging>jar</packaging>
		<name>${project.artifactId}</name>
		
		<dependencies>
	<dependency>
		<groupId>com.dynamo</groupId>
		<artifactId>sparrow-remoting-netty</artifactId>
		<version>${project.parent.version}</version>
		<exclusions>
			<exclusion>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>jetty</artifactId>
			</exclusion>
		</exclusions>
	</dependency>
	<dependency>
		<groupId>com.dynamo</groupId>
		<artifactId>sparrow-rpc-default</artifactId>
		<version>${project.parent.version}</version>
	</dependency>

</dependencies>

<artifactSet>
			<includes>
		  <include>com.dynamo:sparrow-remoting-api</include>
		 <include>com.dynamo:sparrow-remoting-netty</include>
	    <include>com.dynamo:sparrow-rpc-api</include>
		 <include>com.dynamo:sparrow-rpc-default</include>
			</includes>
			</artifactSet>
			<transformers> 
				<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
			    <resource>META-INF/sparrow/internal/com.dynamo.sparrow.remoting.Codec2</resource>
				</transformer>