是什么
如果在A项目中需要用到B项目的类,那么就必须把B项目打成jar包,然后引到lib下。我们称A依赖于B。引入jar包的方法十分繁琐,而maven就是用来管理这种依赖关系的。
Maven是基于项目对象模型(POM project object model),可以通过一小段描述信息(配置)来管理项目的构建,报告和文档的软件项目管理工具。通俗点讲,就是通过pom.xml文件的配置获取jar包,而不用手动去添加jar包。
- 总之,maven可以帮你构建工程、管理jar包、编译代码、自动运行单元测试、打包、生成报表、部署项目。
怎么做
如果需要使用pom.xml来获取jar包,那么首先该项目就必须为maven项目,maven项目可以这样去想,就是在java项目和web项目的上面包裹了一层maven,本质上java项目还是java项目,web项目还是web项目,但是包裹了maven之后,就可以使用maven提供的一些功能了。
具体操作,看一、二、四、七就行了,其中七是基本操作
添加外部依赖
plugin插件
maven本质上是一个插件框架,它的核心并不是执行任何具体的构建任务,所有这些任务都交给插件来完成,例如编译源代码是由maven-complier-plugin完成的。进一步说,每个插件会有一个或者多个目标,例如maven-complier-plugin的complie目标用来编译位于src/main/java目录下的主源码,testComplier目标用来编译位于src/test/java目录下的测试源码。
plugins和pluginManagement的区别
plugins和pluginManagement的区别,和我们前面研究过的dependencies和dependencyManagement的区别是非常类似的。plugins下的plugin是真实使用的,而pluginManagement下的plugins下的plugin则仅仅是一种声明,子项目中可以对pluginManagement下的plugin进行信息的选择、集成和覆盖等。
dependencyManagement
- 在父项目中统一管理依赖的版本号,可保证子模块中依赖的版本号一致,若子模块想不一致的话可以另外声明版本号
- 只是管理而不会引入依赖,所以子模块还需写dependency
project下的属性
- modelVersion:声明项目描述符遵循哪一个pom模型版本,通常是4.0.0。
- name:项目的名称。maven产生的文档用。
- url:项目主页的url,maven产生的文档用。
developers:项目开发者列表。
dependency下的属性
- groupId、artifactId、version是依赖的基本坐标,缺一不可。这三个可以不用讲,都知道。重要的是除了这三个之外的配置属性需要我们理解
- type:依赖的类型,比如是jar包还是war包等。
- 默认为jar,表示依赖的jar包
- 注意:
pom.lastUpdated 这个我们在上面添加servlet-jar的时候就遇到过,看到lastUpdated的意思是表示使用更新描述信息,占位符作用,通俗点讲,选择该类型,jar包不会被加载进来,只是将该jar包的一些描述信息加载进来,使别的jar包在引用他时,能够看到一些相关的提示信息,仅此而已,所以说他是个占位符,只要记住他的jar包不会被加载进来。
- optional:标记依赖是否可选。
- 默认值false
- 比如struts2中内置了log4j这个记录日志的功能,就是将log4j内嵌入struts2的jar包中,而struts2有没有log4j这个东西都没关系,有它,提示的信息更多,没它,也能够运行,只是提示的信息就相对而言少一些,所以这个时候,就可以对它进行可选操作,想要它就要,不想要,就设置为false。
- exclusions:排除传递依赖,解决jar冲突问题
- 依赖传递的意思就是,A项目 依赖 B项目,B项目 依赖 C项目,当使用A项目时,就会把B也给加载进来,这是传递依赖,依次类推,C也会因此给加载进来。这个有依赖传递有好处,也有坏处,坏处就是jar包的冲突问题,比如,A 依赖 B(B的版本为1),C 依赖 B(B的版本为2),如果一个项目同时需要A和C,那么A,C都会传递依赖将B给加载进来,问题就在这里,两个B的版本不一样,将两个都加载进去就会引起冲突,这时候就需要使用exclusions这个属性配置了。maven也会有一个机制避免两个都加载进去,maven 默认配置在前面的优先使用,但是我们还是需要使用exclusions来配置更合理。
- scope:依赖范围,意思就是通过pom.xml加载进来的jar包,来什么范围内使用生效,范围包括编译时,运行时,测试时。
- compile:默认值,如果选择此值,表示编译、测试和运行都使用当前jar
- test:表示只在测试时当前jar生效,在别的范围内就不能使用该jar包。例如:junit ,测试只能写在test/java包下。此处不写也不报错,因为默认是compile,compile包括了测试。
- runtime,表示测试和运行时使用当前jar,编译时不用该jar包。例如:JDBC驱动。JDBC驱动,在编译时(也就是我们写代码的时候都是采用接口编程,压根就没使用到JDBC驱动包内任何东西,只有在运行时才用的到,所以这个是典型的使用runtime这个值的例子),此处不写也不报错,理由同上。
- provided,表示编译和测试时使用当前jar,运行时不在使用该jar了。例如:servlet-api、jsp-api等。【必须填写】
- import:搭配
pom ,表示引用外部的pom文件。由于maven的继承和java一样,无法实现多继承,如果一个父模块包含10个、20个甚至更多个子模块,那么这个父模块的pom的dependencyManagement会包含大量的依赖。为了将这些依赖分类以更清晰地管理,我们需要把dependencyManagement放到单独的专门用来管理依赖的pom汇总,然后在需要使用依赖的模块中通过import来引入。
- 例如可以写一个管理依赖的pom
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.test.sample</groupId>
<!--官方的管理文档一般命名后缀为-bom(bill of material)-->
<artifactId>base-parent1</artifactId>
<packaging>pom</packaging>
<version>1.0.0-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactid>junit</artifactId>
<version>4.8.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactid>log4j</artifactId>
<version>1.2.16</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
- 然后通过非继承的方式来引入这段依赖管理配置
<dependencyManagement>
<dependencies>
<dependency>
<!--下面三行是上面pom的信息-->
<groupId>com.test.sample</groupId>
<artifactid>base-parent1</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--不需要定义版本了-->
<dependency>
<groupId>junit</groupId>
<artifactid>junit</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactid>log4j</artifactId>
</dependency>
repositories和repository
构件可以分成两类,一类是被其他构件依赖的构件(dependencies),这也是maven库中的主要构件;另一种是插件(plugins),这是一种特殊的构件。由于插件的特殊性,插件库独立于依赖库,使用
单独设置,但是和 中的设置基本一致。 <!--查找依赖和扩展的远程仓库列表;仅限本项目--> <repositories> <repository> <!--远程仓库唯一标识符。可以用来匹配在settings.xml文件里配置的远程仓库--> <id>spring-snapshot</id> <!--远程仓库名称--> <name>Spring Snapshot Repository</name> <!--远程仓库URL,按protocol://hostname/path形式--> <url>https://repo.spring.io/snapshot</url> </repository> </repositories>
maven中要配置库,可以直接在项目pom.xml中通过
配置,但仅限于当前项目;也可以通过 中的 配置在特定环境下的库,这可以在pom.xml中,也可以在setting.xml中配置。如果哦都不设置的话则会到下面这个默认库区获取: <repository> <snapshots> <enabled>false</enabled> </snapshots> <id>central</id> <name>Maven Repository Switchboard</name> <url>http://repo1.maven.org/maven2</url> </repository>
packaging
打包方式:pom/jar/war,默认是jar。父项目必须是pom,并且使用module指定子项目。
<packaging>pom</packaging> <modules> <module>simple-weather</module> //项目的子模块 <module>simple-webapp</module> </modules>
- pom指的是项目不像之前的项目那样创建一个jar或war,它仅仅是引用其他maven项目的POM。
- maven知道要去这些子模块的目录中寻找pom.xml文件。
子模块中也需要配置父项目的关联
<parent> <groupId>org.sonatype.mavenbook.ch06</groupId> <artifactId>simple-parent</artifactId> //刚才的父项目名 <version>1.0</version> <!-- <relativePath>../simple-parent/pom.xml</relativePath> --> //如果子项目不是标准地建在父项目的子模块里,而是跟父项目同级了的话,可以先到它们的上一级目录找到父项目,再找其pom文件;标准情况下不用该属性 </parent>
- 子模块的packaging默认是jar,可以改成war。
- 这样,子模块会自动继承父模块的依赖。
- 子项目的groupId和version如果和父项目的一样可以省略不写,会自动继承父项目的。
- 但是子项目之间的互相引用必须注明版本号,比如dao模块引用实体类模块的依赖,必须指明实体类模块的版本号,尽管实体类自己没有显式声明自己的版本号,只是继承父类的。
- 当maven执行一个带有子模块的项目时,maven首先载入父pom,然后定位所有子模块的pom(子模块的文件和父pom同级,也就是子pom必须在父pom的下一级文件中),随后将这些pom放入一个maven反应堆(reactor)的东西中,这个反应堆处理组件的排序,以确保相互独立的模块能以适当的顺序被编译和安装。
maven命令
- mvn compile 编译,没什么用了,现在都是自动编译
- mvn clean 清空target目录
- mvn test 测试,测试依赖能否找到,单元测试成功与否
- mvn package 打包,把项目打jar包或war包。
- mvn install 把项目安装到本地仓库,运行之后本地仓库就会有这个依赖
- 子模块编辑完后应该install安装到本地仓库,才能被其他模块引入。子模块修改了应该重新clean再install(除此之外不用其他操作),引入它的模块才能接收到更新。
- 有的时候你中有我我中有你,一起更新之后要重新install会出错,最好就是先解开一方对另一方的新内容的引用,不然谁也install不了
- 运行父模块发现总是失败可以重新install一下(先clean,否则异名旧包不会被清除),可能是最近的代码修改没有被识别到。
执行maven命令异常
- Plugin org.apache.maven.plugins:maven-resources-plugin:3.0.2 or one of its dependencies…
就是说它找不到这个依赖,在本地仓库下这个依赖下载失败,肯定有很多lastupdate文件在里面,而且这还不是个依赖,是个plugin,插件,一般来说我们创建maven项目都不会去添加什么插件的,但是有的时候他就是会自动添加插件,比如thz-parent项目的thz-manage-web模块下pom文件就有自动添加过插件
<build> <!--war包名--> <finalName>thz-manager-web</finalName> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.0.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.20.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.0</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build>
- 以上这些代码是某一次查看pom文件时突然多出来的,直接删去,再mvn test,问题解决
- Failed to execute goal org.apache.maven.plugins maven-surefire-plugin
- 看报错日志,可能是子模块忘记引入某个依赖了。
eclipse创建maven项目
- 不要用eclipse吧,没有成功过。
- 创建单纯的maven项目,不是web项目,也不整合任何框架
- 将项目转为web项目
多模块maven项目
- 代码实例:ideaProjects/thz。
- 运行子模块之前,先把父模块install一下。
- 被其他模块引用的模块改动之后要重新install,引用它的模块才能检索到它的更新。
- 创建单元测试时跨模块引用配置文件:
@ContextConfiguration(locations={"classpath*:config/applicationContext.xml"})
//classpath:在当前项目查找;classpath*:在所有依赖的jar包的classpath下找
- 在父模块执行maven命令,将对所有的子模块起作用。
- 如果是Spring的多模块maven项目,最好把配置文件集中写在dao模块,反正不要在web/controller这种被打包成war的模块中,否则在其他模块写单元测试时引入配置文件失败。原因是配置文件写在了war包中,而war包不能被当成依赖引入这个要建立单元测试的模块,因此配置文件无法引入,从而只能把测试类写在配置文件所在模块了。
- 可参见代码示例service模块下测试和web模块下相同测试。
- 如果同时有多个子模块都是web应用,它们需要同时运行协同测试,那么可以在debug configuration中添加两个maven命令,命令行都设为tomcat7:run,作用目录则分别选择相应的目录,然后两个都debug起来就行。
若父模块的pom中依赖都写在dependencyManager中,则子模块依然需要引入依赖;若写在dependencies中,则子模块不需要再引入。
父项目如果负责引入依赖而不只是管理依赖的话,那么子项目虽然继承了这些依赖,但是一开始写代码是检测不到这些依赖的,有时alt+enter可以直接把依赖add to classpath,但有时代码却只是飘下划线而不能直接add to classpath,此时可以手动import,就能在飘红的包名那里alt+enter把依赖add to classpath了。
- 网上推荐把.iml文件删除,然后点击以下图标重新生成,如此几次即可:
- 参考文章
- 代码示例:ideaProjects/shiro-chapter23/shiro-chapter23-client/ClirntShiroFilterFactoryBean/setApplicationContext(){this.applicationContext},这一段在未添加spring-beans依赖并将其add to classpath时飘红线。