Maven是一个项目管理工具。它可以帮助程序员构建工程,管理jar包,编译代码,完成测试,项目打包等等。
- Maven工具是基于POM(Project Object Model,项目对象模型)实现的。在Maven的管理下每个项目都相当于是一个对象。
- Maven标准化了项目的构建。即对项目结构,构建命令等进行了标准化定义。
- Maven提供了一个免费的中央仓库,在其中几乎可以找到任何的流行开源类库。
- Maven是跨平台的,在Windows、Linux、Mac上,都可以使用同样的命令。
我们的项目往往都要经历编译、测试、运行、打包、安装 ,部署等一系列过程,这些过程称之为构建。通过Maven工具,可以使用简单的命令轻松完成构建工作。
传统的Web项目中,我们必须将工程所依赖的jar包复制到工程中,导致了工程的变得很大。如果一个公司具有相同架构的项目有十个,那么就需要将这一份jar包复制到十个不同的工程中,非常浪费资源。
同样的项目内容,传统Web项目大小如下:
而使用Maven构建的项目大小如下:
这说明了传统Web项目中大部分磁盘空间是被jar包占据,而Maven项目中一定没有jar包,那没有jar包的项目是怎么运行的呢?
maven工程中不直接将jar包导入到工程中,而是有一个专门存放jar包的仓库,仓库中的每个jar包都有自己的坐标。maven工程中只要配置jar包坐标即可,运行项目需要使用jar包时,根据坐标即可从maven仓库中拿到jar包即可运行。
POM工程
POM工程是逻辑工程,Maven并不会对该类型工程做打包处理,这些工程往往不包含具体的业务,而是用来整合其他工程的。
JAR工程
普通Java工程,在打包时会将项目打成jar包。
WAR工程
JAVA Web工程,在打包时会将项目打成war包。
接下来我们通过一个WAR工程学习Maven工程的结构
文件目录结构:
项目结构:
使用maven完成项目的构建的过程中,包括:验证、编译、测试、打包、部署等过程,maven将这些过程规范为项目构建的生命周期。
生命周期 | 所做工作 |
---|---|
验证 validate | 验证项目是否正确 |
编译 compile | 源代码编译 |
测试 Test | 使用适当的单元测试框架(例如junit)运行测试。 |
打包 package | 创建JAR/WAR包 |
检查 verify | 对集成测试的结果进行检查,以保证质量达标。 |
安装 install | 安装打包的项目到本地仓库,以供其他项目使用。 |
部署 deploy | 拷贝最终的工程包到远程仓库中,以共享给其他开发人员和工程。 |
maven有三套相互独立的生命周期。分为是构建生命周期,clean生命周期(清理构建后的文件)、site生命周期(生成项目报告)。作为开发人员我们一般重点学习构建生命周期即可。
在Maven构建项目的每一步都可以使用一句简单的命令完成,接下来我们学习这些命令:
命令 | 作用 |
---|---|
mvn clean | 清除编译的class文件,即删除target目录。 |
mvn validate | 验证项目是否正确 |
mvn compile | 编译maven项目 |
mvn test | 编译maven项目及运行测试文件 |
mvn package | 编译maven项目及运行测试文件,并打包 |
mvn install | 编译maven项目及运行测试文件并打包,并发布到本地仓库 |
mvn deploy | 部署项目到远程仓库 |
mvn tomcat7:run | 使用tomcat运行项目 |
Maven依赖插件来执行命令,比如clean、validate等命令是maven自带的,tomcat7命令是引入的第三方插件。
本地仓库指用户计算机中的文件夹。用来存储从远程仓库或中央仓库下载的jar包,只有下载到本地仓库的jar包才能使用,项目使用jar包时优先从本地仓库查找。
远程仓库一般指私服,它是架设在局域网的仓库服务,可以从中央仓库下载资源,供局域网使用,从而减少每个程序员都从中央仓库下载浪费的带宽。
如果项目需要的jar包本地仓库没有,则会去远程仓库下载,下载到本地仓库即可使用。
远程仓库不是必须配置的,如果本地仓库没有jar包,也没有配置远程仓库,则会直接从中央仓库下载。
中央仓库是互联网上的服务器,是Maven提供的最大的仓库,里面拥有最全的jar包资源。
如果项目需要的jar包,本地仓库和远程仓库都没有,则会去中央仓库下载,下载到本地仓库使用。
Maven中央仓库访问页面:https://mvnrepository.com/
中央仓库访问速度较慢,我们一般都会配置镜像代理中央仓库的下载请求,如阿里镜像、华为镜像等。
本地仓库的默认位置是${user.dir}/.m2/repository
,${user.dir}
表示 windows用户目录,我们可以通过修改${MAVEN_HOME}\conf\settings.xml
,修改本地仓库的位置。
在
中添加如下标签:
<localRepository>F://repositorylocalRepository>
由于中央仓库访问速度较慢,可以配置镜像代理中央仓库的下载请求。在
下的
中添加如下标签即可配置镜像:
<mirror>
<id>nexus-aliyunid>
<mirrorOf>centralmirrorOf>
<name>Nexus aliyunname>
<url>http://maven.aliyun.com/nexus/content/groups/publicurl>
mirror>
创建maven项目的时候,默认使用的JDK是1.5版本,验证语法、编译、运行时都会按照JDK1.5操作,这样就有很多语法无法使用。我们本机安装的JDK是JDK11,可以配置maven按照JDK11创建项目。
在
下的
中添加如下标签即可配置JDK版本:
<profile>
<id>jdk11id>
<activation>
<activeByDefault>trueactiveByDefault>
<jdk>11jdk>
activation>
<properties>
<maven.compiler.source>11maven.compiler.source>
<maven.compiler.target>11maven.compiler.target>
<maven.compiler.compilerVersion>11maven.compiler.compilerVersion>
properties>
profile>
pom文件最上方是项目基本信息:
groupId
groupId一般定义项目组名,命名规则使用反向域名。例如com.java
artifactId
artifactId一般定义项目名,命名使用小写字母。项目发布后,它的坐标是groupId+artifactId。
version
version定义版本号。版本号一般有三段,第一段:革命性的产品升级。第二段:新功能版本。第三段:修正一些bug。
packaging
packaging定义打包方式。
中定义一些配置信息:
中定义依赖的jar包坐标:
由于项目是web项目,需要写Servlet和JSP,所以需要引入Servlet和JSP的依赖。查找依赖坐标的网站:https://mvnrepository.com/
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.11version>
<scope>testscope>
dependency>
<dependency>
<groupId>javax.servlet.jspgroupId>
<artifactId>jsp-apiartifactId>
<version>2.2version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.0.1version>
dependency>
dependencies>
为什么之前的web项目中没有引入jsp和servlet的jar包?
因为之前项目中使用的是tomcat中的jsp和servlet中的jar包,在项目中没有引入。
中定义第三方插件:
web项目依赖tomcat运行,所以添加tomcat7插件
<plugins>
<plugin>
<groupId>org.apache.tomcat.mavengroupId>
<artifactId>tomcat7-maven-pluginartifactId>
<version>2.1version>
<configuration>
<port>8080port>
<path>/path>
<uriEncoding>UTF-8uriEncoding>
<server>tomcat7server>
configuration>
plugin>
plugins>
接下来编写Servlet和Jsp代码:
@WebServlet("/demo1")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher("hello.jsp").forward(req,resp);
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
测试
你好,程序员!
运行项目,按照如图步骤,配置使用maven中的tomcat7插件运行项目:
点击OK后配置完成,点击Idea的运行符号即可使用maven中的tomcat7插件运行项目。
访问servlet后,发现报500异常,这是为什么呢?
这是由于项目中引入了Servlet和Jsp的jar包,而在项目运行时,Tomcat要使用它自带的Servlet和Jsp的jar包,这样就造成了jar包冲突。但如果项目不引入Servlet和Jsp的jar包,则根本无法通过编译。
此时可以设置依赖的作用范围解决该问题,设置Servlet和Jsp的jar包只在编译期起作用,运行时不起作用,这样不仅可以通过编译,还能在运行时避免jar包冲突。
通过在
中添加
,可以设置依赖的作用范围,有以下取值:
compile
默认范围。表示该依赖在编译和运行时生效,项目打包时也会将该依赖打包进去。
provided
使用此依赖范围的Maven依赖,编译和测试时有效,但在运行时无效。典型的例子是servlet-api,在运行时Web容器已经提供依赖,就不需要Maven重复地引入一遍。
runtime
runtime范围表明编译时不需要生效,而只在运行时生效。典型的例子是JDBC驱动包,编译时只需要JDK的JDBC接口即可,只有运行项目时才需要具体的JDBC驱动。
test
test范围表明使用此依赖范围的依赖,只在编译和运行测试代码的时生效,程序的正常运行不需要此类依赖。典型的例子就是JUnit,它只有在编译测试代码及运行测试的时候才需要。
system
如果有些你依赖的jar包没有Maven坐标的,它完全不在Maven体系中,这时候你可以把它下载到本地硬盘,然后通过system来引用。(不推荐使用)
所以对于Servlet和Jsp依赖,我们添加依赖范围为provided即可。
<dependency>
<groupId>javax.servlet.jspgroupId>
<artifactId>jsp-apiartifactId>
<version>2.2version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.0.1version>
<scope>providedscope>
dependency>
重启项目,即可正常访问Servlet和Jsp
测试即在交付前对写好的代码进行评测,分为黑盒测试和白盒测试:
单元测试是指对软件中的最小可测试单元进行检查和验证,Java里单元测试指一个类的功能。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。
Junit是Java编程语言的单元测试框架,用于编写和运行可重复的自动化测试。从分类中属于白盒测试。
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
我们定义一个计算器工具类。
public class Calculator {
// 加法
public int add(int a,int b){
return a+b;
}
// 除法
public int div(int a,int b){
return a/b;
}
}
创建src/test/java包,并将改包设置为测试包。
在src/test/java中创建测试类的包,包名一般与被测试包名一致。
定义测试类,类名一般为被测试类+Test
测试类中编写测试方法。
public class CalculatorTest {
/**
* 测试方法是可以独立运行的,写法如下:
* 1.方法名一般为test+被测试方法名
* 2.方法上方添加@Test
* 3.测试方法没有参数和返回值
*/
@Test
public void testAdd(){
Calculator calculator = new Calculator();
int add = calculator.add(1, 2);
System.out.println(add);
}
@Test
public void testDiv(){
Calculator calculator = new Calculator();
int div = calculator.div(2,0);
System.out.println(div);
}
}
点击测试方法边的三角运行测试方法,如果出现绿色对钩,证明方法能正常运行;如果出现红色感叹号,证明方法抛出异常,需要修改方法。
当然,不是能正常运行就代表方法一定没有问题,也有可能方法的结果和预期结果不一致,这时就需要使用断言操作。
断言操作:Assert.assertEquals(参数1,参数2);
参数1:预期结果,参数2:实际结果
@Test
public void testAdd(){
Calculator calculator = new Calculator();
int add = calculator.add(1, 2);
/**
* 断言
* 参数1:预期结果,参数2:实际结果
*/
Assert.assertEquals(2,add);
}
如果真实结果和预期结果不一致,则会抛出以下异常:
在测试类中,@Before修饰的方法会在测试方法之前自动执行,@After修饰的方法会在测试方法执行之后自动执行。之后的学习中,我们可以设置前置方法为获取资源,后置方法为释放资源。
@Before
public void before(){
System.out.println("开始测试");
}
@After
public void after(){
System.out.println("结束测试");
}
假设你的项目依赖jar包A,jar包A又依赖jar包B。当添加jar包A时,Maven会把jar包B也自动加入到项目中。比如刚刚我们添加了junit依赖,junit又依赖hamcrest,所以Maven会将junit和hamcrest都加入项目中。
这时就可能会产生依赖冲突问题,比如依赖A会引入依赖C,依赖B也会引入依赖C。如果不进行调解则会引入两个依赖C,那么Maven是如何解决依赖冲突问题的呢?
我们以Spring依赖为例,spring-webmvc依赖spring-aop,spring-context也依赖spring-aop,如果两个同时引入,会引入哪个版本的spring-aop呢?
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.12.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>4.2.4.RELEASEversion>
dependency>
最短路径优先原则不能解决所有问题,比如这样的依赖关系:A–>B–>C(1.0)
和D–>E–>C(2.0)
,同时引入A和D之后,C(1.0)和C(2.0)的依赖路径长度都为2。此时第一原则将不能解决问题
Maven调解依赖冲突的第二原则是最先声明原则:
在依赖路径长度相等的前提下,在POM中依赖声明的顺序靠前的会被解析使用。比如:以上案例中,spring-webmvc和spring-context到spring-core的路径都为1。谁声明在上方,spring-core会按照谁的版本引入。
如果不想使用Maven默认的冲突调解方式,有两种方式可以手动进行冲突调解。
比如以上案例中,想使用spring-webmvc的spring-aop包,那么可以让spring-context引入时排除引入spring-aop包,这样就可以使用spring-webmvc的spring-aop包了,写法如下:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>4.2.4.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.12.RELEASEversion>
<exclusions>
<exclusion>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
exclusion>
exclusions>
dependency>
在Maven中为某个jar包配置锁定版本后,不考虑依赖的声明顺序和依赖路径,以锁定的版本的为准添加到工程中,此方法在企业开发中常用。以下可以直接配置spring-aop锁定的版本。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>4.2.4.RELEASEversion>
dependency>
dependencies>
之前我们在Idea中开发时会将项目的所有包放在同一个工程当中。
- domain:定义实体类
- dao:操作数据库
- service:具体的业务逻辑,需要调用dao的方法。
- controller:前后端交互,需要调用service的方法。
- webapp:存放前端资源
假如我们现在写了两个项目分别是电商卖家端和电商买家端,两个项目都需要调用serive层查询订单的方法。原来的写法如下:
重复编码造成开发效率降低。
而使用maven后,我们可以把之前的项目按需拆分成一个个小项目,之后将小项目发布到仓库中,小项目之间也可以互相引用,并且在我们的项目中引入需要的小项目即可。
Maven将一个大项目分成一个个小项目开发,最后打包时会将这些小的项目打成一个完整的war包独立运行。
Maven中的继承是针对于父工程和子工程。父工程定义的依赖和插件子工程可以直接使用。注意父工程类型一定为POM类型工程。
在Maven中对于继承采用的也是单继承,也就是说一个子项目只能有一个父项目。但我们可以在
配置多继承。写法如下:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.javagroupId>
<artifactId>parent_aartifactId>
<version>1.0-SNAPSHOTversion*>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.javagroupId>
<artifactId>parent_bartifactId>
<version>1.0-SNAPSHOTversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>