Maven
文章目录
1、Maven 概述
一个对 Maven 比较正式的定义是这么说的: Maven 是一个项目管理工具,它包含了一个项目对象模型 (POM: Project Object Model),一组标准集合,一个项目生命周期(Project Lifecycle),一个依赖管理系统(Dependency Management System),和用来运行定义在生命周期阶段(phase)中插件(plugin)目标(goal)的逻辑。
maven 能解决什么问题?
- 我们需要引用各种 jar 包,尤其是比较大的工程,引用的 jar 包往往有几十个乃至上百个, 每用到一种 jar 包,都需要手动引入工程目录,而且经常遇到各种让人抓狂的 jar 包冲突,版本冲突。
- 我们辛辛苦苦写好了 Java 文件,可是只懂 0 和 1 的白痴电脑却完全读不懂,需要将它编译成二进制字节码。好歹现在这项工作可以由各种集成开发工具帮我们完成, Eclipse、 IDEA 等都可以将代码即时编译。当然,如果你嫌生命漫长,何不铺张,也可以用记事本来敲代码,然后用 javac 命令一个个地去编译,逗电脑玩。
- 世界上没有不存在 bug 的代码, 计算机喜欢 bug 就和人们总是喜欢美女帅哥一样。为了追求美为了减少 bug,因此写完了代码,我们还要写一些单元测试,然后一个个的运行来检验代码质量。
- 再优雅的代码也是要出来卖的。我们后面还需要把代码与各种配置文件、资源整合到一起,定型打包,如果是 web 项目,还需要将之发布到服务器,供人访问。
试想,如果现在有一种工具,可以把你从上面的繁琐工作中解放出来,能帮你构建工程,管理 jar 包,编译代码,还能帮你自动运行单元测试,打包,生成报表,甚至能帮你部署项目,生成 Web 站点,你会心动吗? Maven 就可以解决上面所提到的这些问题。
1.1、Maven 的优势举例
前面我们通过 Web 阶段项目,要能够将项目运行起来,就必须将该项目所依赖的一些 jar 包添加到工程中,否则项目就不能运行。 试想如果具有相同架构的项目有十个,那么我们就需要将这一份 jar 包复制到十个不同的工程中。 我们一起来看一个 CRM 项目的工程大小。
使用传统 Web 项目构建的 CRM 项目如下:
原因主要是因为上面的 WEB 程序要运行,我们必须将项目运行所需的 Jar 包复制到工程目录中,从而导致了工程很大。
同样的项目,如果我们使用 Maven 工程来构建,会发现总体上工程的大小会少很多。如下图:
小结: 可以初步推断它里面一定没有 jar 包,继续思考,没有 jar 包的项目怎么可能运行呢?
2、Maven 的两个经典作用
2.1、Maven 的依赖管理
Maven 的一个核心特性就是依赖管理。当我们涉及到多模块的项目(包含成百个模块或者子项目),管理依赖就变成一项困难的任务。 Maven 展示出了它对处理这种情形的高度控制。
传统的 WEB 项目中,我们必须将工程所依赖的 jar 包复制到工程中,导致了工程的变得很大。那么 maven 工程是如何使得工程变得很少呢?分析如下:
通过分析发现:maven 工程中不直接将 jar 包导入到工程中,而是通过在 pom.xml
文件中添加所需 jar 包的坐标,这样就很好的避免了 jar 直接引入进来,在需要用到 jar 包的时候,只要查找 pom.xml
文件,再通过 pom.xml
文件中的坐标,到一个专门用于 ”存放 jar 包的仓库”(maven 仓库)中根据坐标从而找到这些 jar 包,再把这些 jar 包拿去运行。
那么问题来了:
第一:”存放 jar 包的仓库” 长什么样?
第二:通过读取 pom.xml
文件中的坐标,再到仓库中找到 jar 包,会不会很慢?从而导致这种方式不可行!
第一个问题:存放 jar 包的仓库长什么样,这一点我们后期会分析仓库的分类,也会带大家去看我们的本地的仓库长什么样。
第二个问题:通过 pom.xml
文件配置要引入的 jar 包的坐标,再读取坐标并到仓库中加载 jar 包,这样我们就可以直接使用 jar 包了,为了解决这个过程中速度慢的问题, maven 中也有索引的概念,通过建立索引,可以大大提高加载 jar 包的速度,使得我们认为 jar 包基本跟放在本地的工程文件中再读取出来的速度是一样的。这个过程就好比我们查阅字典时,为了能够加快查找到内容,书前面的目录就好比是索引,有了这个目录我们就可以方便找到内容了,一样的在 maven 仓库中有了索引我们就可以认为可以快速找到 jar 包。
2.2、项目的一键构建
我们的项目,往往都要经历编译、 测试、 运行、 打包、 安装 ,部署等一系列过程。
什么是构建?指的是项目从编译、测试、运行、打包、安装 ,部署整个过程都交给 maven 进行管理,这个过程称为构建。
一键构建:指的是整个构建过程,使用 maven 一个命令可以轻松完成整个工作。
Maven 规范化构建流程如下:
3、Maven 的使用
3.1、Maven 的安装
-
下载
Apache-maven-3.5.2 下载地址: http://archive.apache.org/dist/maven/maven-3/
-
安装
Maven 下载后,将 Maven 解压到一个没有中文没有空格的路径下,比如
D:\software\maven
下面。
3.2、Maven 仓库
3.2.1、Maven 仓库的分类
maven 的工作需要从仓库下载一些 jar 包,如下图所示,本地的项目 A、项目 B 等都会通过 maven 软件从远程仓库(可以理解为互联网上的仓库)下载 jar 包并存在本地仓库,本地仓库就是本地文件夹,当第二次需要此 jar 包时则不再从远程仓库下载,因为本地仓库已经存在了,可以将本地仓库理解为缓存,有了本地仓库就不用每次从远程仓库下载了。
下图描述了 maven 中仓库的类型:
-
本地仓库:用来存储从远程仓库或中央仓库下载的插件和 jar 包,项目使用一些插件或 jar 包,优先从本地仓库查找
默认本地仓库位置在
${user.dir}/.m2/repository
,${user.dir}
表示 windows 用户目录。自定义本地仓库的位置:
在
MAVE_HOME/conf/settings.xml
文件中配置本地仓库位置(maven 的安装目录下):<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> --> <!-- 在这个标签里边修改本地仓库的位置 --> <localRepository>G:\maven_repository</localRepository>
-
远程仓库:如果本地需要插件或者 jar 包,本地仓库没有, 默认去远程仓库下载。远程仓库可以在互联网内也可以在局域网内。
-
中央仓库:在 maven 软件中内置一个远程仓库地址 https://repo1.maven.org/maven2 ,它是中央仓库,服务于整个互联网,它是由 Maven 团队自己维护,里面存储了非常全的 jar 包,它包含了世界上大部分流行的开源项目构件。
3.2.2、全局 setting 与用户 setting
maven 仓库地址、私服等配置信息需要在 setting.xml
文件中配置,分为全局配置和用户配置。
在 maven 安装目录下的有 conf/setting.xml
文件,此 setting.xml
文件用于 maven 的所有 project 项目,它作为 maven 的全局配置。
如需要个性配置则需要在用户配置中设置,用户配置的 setting.xml
文件默认的位置在: ${user.dir} /.m2/settings.xml
目录中,${user.dir}
指 windows 中的用户目录。
maven 会先找用户配置,如果找到则以用户配置文件为准,否则使用全局配置文件。
3.3、Maven 工程的认识
3.3.1、Maven 工程的目录结构
作为一个 maven 工程,它的 src
目录和 pom.xml
是必备的。
进入 src 目录后,我们发现它里面的目录结构如下:
注意:如果是普通的 java 项目,那么就没有 webapp 目录。
4、Maven 常用命令
我们可以在 cmd 中通过一系列的 maven 命令来对我们的 maven-helloworld 工程进行编译、测试、运行、打包、安装、部署。
4.1、clean
clean
是 maven 工程的清理命令,执行 clean
会删除 target 目录及内容。
4.2、compile
compile
是 maven 工程的编译命令,作用是将 src/main/java
下的文件编译为 class 文件输出到 target
目录下。
4.3、test
test
是 maven 工程的测试命令 mvn test
,会执行 src/test/java
下的单元测试类。
不仅仅会编译 src/test/java
下的代码,同样会编译 src/main/java
下的代码。
4.4、package
package
是 maven 工程的打包命令,对于 java 工程执行 package
打成 jar 包,对于 web 工程打成 war 包(在 target 目录下)。
4.5、install
install
是 maven 工程的安装命令,执行 install
将 maven 打成 jar 包或 war 包发布到本地仓库。
从运行结果中,可以看出:
当后面的命令执行时,前面的操作过程也都会自动执行。
5、Maven 指令的生命周期
maven 对项目构建过程分为三套相互独立的生命周期,请注意这里说的是 “三套”,而且 “相互独立”,这三套生命周期分别是:
- 清理生命周期(Clean Lifecycle):在进行真正的构建之前进行一些清理工作
- 默认生命周期(Default Lifecycle):构建的核心部分,编译,测试,打包,部署等等
- 站点生命周期(Site Lifecycle):生成项目报告,站点,发布站点
6、maven 的概念模型
Maven 包含了一个项目对象模型(Project Object Model),一组标准集合,一个项目生命周期(Project Lifecycle),一个依赖管理系统(Dependency Management System),和用来运行定义在生命周期阶段(phase)中插件(plugin)目标(goal)的逻辑。
-
项目对象模型(Project Object Model)
一个 maven 工程都有一个
pom.xml
文件,通过pom.xml
文件定义项目的坐标、项目依赖、项目信息、插件目标等。 -
依赖管理系统(Dependency Management System)
通过 maven 的依赖管理对项目所依赖的 jar 包进行统一管理。
比如:项目依赖 junit4.9,通过在
pom.xml
中定义 junit4.9 的依赖即使用 junit4.9,如下所示是 junit4.9 的依赖定义:<!-- 依赖关系 --> <dependencies> <!-- 此项目运行使用 junit,所以此项目依赖 junit --> <dependency> <!-- junit 的项目名称 --> <groupId>junit</groupId> <!-- junit 的模块名称 --> <artifactId>junit</artifactId> <!-- junit 版本 --> <version>4.9</version> <!-- 依赖范围:单元测试时使用 junit --> <scope>test</scope> </dependency> </dependencies>
-
一个项目生命周期(Project Lifecycle)
使用 maven 完成项目的构建,项目构建包括:清理、编译、测试、部署等过程, maven 将这些过程规范为一个生命周期。
maven 通过执行一些简单命令即可实现上边生命周期的各个过程,比如执行
mvn compile
执行编译、执行mvn clean
执行清理。 -
一组标准集合
maven 将整个项目管理过程定义一组标准,比如:通过 maven 构建工程有标准的目录结构,有标准的生命周期阶段、依赖管理有标准的坐标定义等。
-
插件(plugin)目标(goal)
maven 管理项目生命周期过程都是基于插件完成的。
7、IDEA 开发 maven 项目
7.1、idea 的 maven 配置
7.2、使用骨架创建 maven 的 java 工程
点击下一步:
创建完之后的 java 工程:
目录并不完整,需要我们手动添加资源目录:
7.3、不使用骨架创建 maven 的 java 工程
创建 java 工程后的目录结构:
7.4、使用骨架创建 maven 的 web 工程
点击下一步:
创建 web 工程后的目录结构:
目录结构仍然不完整,需要我们手动添加:
8、maven 对 web 工程的一些操作
8.1、导入项目依赖的 jar 包
在 pom.xml 中导入项目运行所需要的 jar 包:
<!-- 项目运行所依赖的jar包 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
新建一个 Servlet 取名为 MyServlet:
添加依赖需要指定依赖 jar 包的坐标,但是很多情况我们是不知道 jar 包的的坐标,可以通过如下方式查询:
https://mvnrepository.com/
MySevlet 代码为:
@WebServlet("/MyServlet")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("hello.jsp").forward(request, response);
}
}
8.2、jar 包冲突解决
A 依赖 B,需要在 A 的 pom.xml
文件中添加 B 的坐标,添加坐标时需要指定依赖范围,依赖范围包括:
- compile:编译范围,指 A 在编译时依赖 B,此范围为默认依赖范围。 编译范围的依赖会用在编译、测试、运行,由于运行时需要所以编译范围的依赖会被打包。
- provided: provided 依赖只有在当 JDK 或者一个容器已提供该依赖之后才使用, provided 依赖在编译和测试时需要,在运行时不需要,比如: servlet api 被 tomcat 容器提供。
- runtime: runtime 依赖在运行和测试系统的时候需要,但在编译的时候不需要。 比如: jdbc 的驱动包。由于运行时需要所以 runtime 范围的依赖会被打包。
- test: test 范围依赖 在编译和运行时都不需要,它们只有在测试编译和测试运行阶段可用,比如: junit。由于运行时不需要所以 test 范围依赖不会被打包。
- system: system 范围依赖与 provided 类似,但是你必须显式的提供一个对于本地系统中 JAR 文件的路径, 需要指定 systemPath 磁盘路径, system依赖不推荐使用。
在 maven-web 工程中测试各各 scop。测试总结:
- 默认引入的 jar 包 ------- compile【默认范围可以不写】(编译、测试、运行 都有效 )
- servlet-api 、jsp-api ------- provided(编译、测试有效, 运行时无效防止和 tomcat 下 jar 冲突)
- jdbc 驱动 jar 包 ---- runtime(测试、运行有效)
- junit ----- test(测试有效)
依赖范围由强到弱的顺序是: compile > provided > runtime > test
只在运行时起作用:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
只在测试时起作用:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
8.3、maven 工程环境的修改
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat9-maven-plugin</artifactId>
<version>2.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
9、maven 的 java 工程取 mysql 数据库数据
9.1、数据库数据
我们作为示例,取 192.168.1.1 数据库服务器 db4 数据库 user 表中的 id 和 username 字段。
9.2、配置 pom.xml
我们需要数据库连接的 jar 包和单元测试的 jar 包。
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.15</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
配置完 pom.xml 一定要记得点击 “刷新”。
9.2、创建一个实体类
package com.li;
public class Items {
private Integer id;
private String username;
public Integer getId() {
return id;
}
public String getUsername() {
return username;
}
public void setId(Integer id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return "Items{" +
"id=" + id +
", username='" + username + '\'' +
'}';
}
}
9.3、创建 Dao 类
package com.Dao;
import com.li.Items;
import java.util.List;
public interface ItemsDao {
public List<Items> findAll();
}
接着创建一个实现类:
package com.Dao.impl;
import com.Dao.ItemsDao;
import com.li.Items;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class ItemsDaoImpl implements ItemsDao {
@Override
public List<Items> findAll() throws SQLException {
//1. 先获取connection对象
Connection connection = null;
//2. 获取真正操作数据的对象
PreparedStatement preparedStatement = null;
//3. 执行数据库操作
ResultSet resultSet = null;
List<Items> list = new ArrayList<>();
try{
//加载驱动类
Class.forName("com.mysql.jdbc.Driver");
//1. 先获取connection对象
connection = DriverManager.getConnection("jdbc:mysql://192.168.1.11/db4", "root", "Opfordream@0518");
//2. 获取真正操作数据的对象
preparedStatement = connection.prepareStatement("select id, username from user");
//3. 执行数据库操作
resultSet = preparedStatement.executeQuery();
//4. 把数据库结果集转成java的List集合
while (resultSet.next()){
Items items = new Items();
items.setId(resultSet.getInt("id"));
items.setUsername(resultSet.getString("username"));
list.add(items);
}
}catch (Exception e){
e.printStackTrace();
}finally {
connection.close();
preparedStatement.close();
resultSet.close();
}
return list;
}
}
9.4、测试
package com.li.test;
import com.Dao.ItemsDao;
import com.Dao.impl.ItemsDaoImpl;
import com.li.Items;
import org.junit.Test;
import java.sql.SQLException;
import java.util.List;
public class ItemsTest {
@Test
public void findAll() throws SQLException {
ItemsDao itemsDao = new ItemsDaoImpl();
List<Items> itemsList = itemsDao.findAll();
for (Items items:
itemsList) {
System.out.println(items);
}
}
}
Items{id=1, username='zhangsan'}
Items{id=2, username='lisi'}