本系列文章旨在记录和总结自己在 java Web开发之路上的知识点、经验、问题和思考,希望能帮助更多( Java )码农和想成为(Java)码农的人。
目录
- 介绍
- Maven 是什么
- Maven下载
- Maven安装
- 创建Maven工程/项目
- 使用Maven构建工程/项目
- 使用 maven 消除添加依赖库的重复劳动
- 总结
介绍
还是手动下载H2Database并配置到我们的工程中,这种重复性劳动已经让人不厌其烦了。
所以,本篇文章我们就介绍一种能够帮助我们干这些活的工具,这个工具就是目前很流行的Maven。
Maven是什么
依照惯例,我们还是先到Maven的官网()上看看吧。从官网URL可以看出,Maven属于Apache软件基金会的一个项目。
从官网首页中得知,Maven是一个软件项目管理和理解工具。这个“理解”工具有点不太好理解,英文单词是comprehension,含义是:
就这么理解吧:它是帮我们理解软件项目的。
就这么一句话有点太笼统了,后面还有:它是基于项目对象模型(project object model,简称 POM )这个概念的,它可以从一个 中央信息库 里面(获取资料)来管理项目的 构建 、 报告 和 归档 。
这么一说还是很模糊,到底Maven是如何来帮助我们管理项目的 构建 、 报告 和 归档 的呢?那就只能靠我们把它下载下来试验一番了。
当然,我们还可以继续看看Maven官网中更详细的介绍,比如官网左侧导航栏中的 ABOUT MAVEN 部分中的 What is Maven? 和 Features 这两章节。
Maven下载
在Maven官网很容易找到 Download 链接,点击即可打开下载页面:
可以看到,最新版本是3.6.2,该页面有Maven该版本的系统需求、下载文件、历史版本说明等内容。
我们根据自己的系统环境,直接找到下载文件的某个二进制发行版的链接即可,我这里下载的是 apache-maven-3.6.2-bin.zip ,点击就可以直接下载了。
Maven安装
Maven的安装就跟Eclipse和Tomcat的安装那样简单,直接解压即可。
解压之后的目录结构是这样的:
事实上,Maven官网中也很容易找到如何安装Maven的介绍,官网首页中可以直接搜索 Install 这个单词,或者点击左侧导航栏中的 Use 链接,展开后也有 Install 链接。打开的 Install 页面是这样的:
大体步骤是:
- 解压下载的Maven发行包;
- 先确保 JAVA_HOME 这个 环境变量 指向JDK的安装路径;
- 把Maven解压出来的目录添加到 PATH/Path 这个环境变量;
- 打开命令行界面窗口,使用 mvn -v 命令验证安装是否成功。
当然,解压、环境变量的设置在不同操作系统中是不太一样的。在Windows系统中设置环境变量的步骤如下:
- 右键点击开始菜单或桌面上的 计算机 图标,弹出右键菜单,点击 属性 ,弹出 系统 对话框(当然,熟悉快捷键的话则可以使用 [ Windows键 + Pause键 ] ):
- 然后,点击 系统 对话框中 高级系统设置 按钮,弹出 系统属性 对话框:
- 在 系统属性 对话框中点击 环境变量(N)… 按钮,弹出 环境变量 对话框:
- 在 环境变量 对话框中,就可以 新建 一个新的环境变量,或 编辑 已有的环境变量。需要说明的是,上面的用户变量是只针对当前登录Windows的用户才有效的;而下面的系统变量是对所有Windows用户都有效的。
我是新建了一个 JAVA_HOME 环境变量,它的值是我安装JDK的路径:
C:Javajdk1.8.0_192
然后,又将Maven解压缩后的目录中的 bin 目录添加到 Path 环境变量中(写到原值中的最前面,以分号结束即可):
E:csdnapache-maven-3.6.2bin;
最后,打开Windows的命令行工具(可以使用快捷键 [ Windows键 + R键 ] 并输入 cmd ),输入 mvn -v 验证安装是否成功,成功的话应该类似下图:
创建Maven工程/项目
既然Maven已经安装好了,那么我们又该如何使用它来消除前面所述的开发中遇到的添加依赖库这种重复性劳动呢?
那我们就一步一步来实践吧。需要提醒大家的是,我这里的内容都是来自官网的,如果你的英文好,并且想看权威的文档,那就直接在官网中寻找相关资料,相信也很容易找到的,我就不再赘述了。
上面已经在命令行工具中验证过Maven已经正确安装了,现在首先将命令行窗口中的当前目录转到你想要存放Maven工程/项目的目录,我是新建了一个 maven-projects 目录:
然后,输入以下命令:
mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false
回车之后,就可以看到大量不断翻滚的信息,其中大部分信息都是说从 中央(central) 下载某些东西,它们实际上是Maven的 插件(plugin) 和其他文件, 所以,一定要保证我们的电脑是能够连接上互联网的。
因为我们是刚安装好Maven,其各种插件还没有,所以需要花费一点时间先下载它们,大家可以先去喝杯咖啡慢慢等候,当然也可以干别的事情。如果中间遇到网络问题中断了,那就只能再从头执行了。
不管怎样,最后成功的画面是这样的:
可以看到,我这花了将近九分钟呢。
而这一步的作用就是创建了一个Maven工程/项目,我们进一步来解构一下上面那个很长的命令:
- mvn:这个就不用说了,显然是Maven的某个可执行文件,当然是放在安装路径的 bin 目录中的,不过这个目录已经被我们添加到 Path 环境变量了,所以命令行工具能够找到它。
- archetype:generate:这个就是Maven中的 插件(plugin) 和 目标(goal) 的概念了,可以这么理解,一个目标就是一个要执行的任务,而插件是多个目标的集合,当然,这些目标肯定是为了一个通用的功能才把它们放到一个插件中。为什么要搞这么复杂呢?当然是为了更好的扩展性啊,这样一来,我们也可以开发出自己的插件,实现几个目标,交给Maven去执行啊。archetype实际上就是Maven自身提供的一个插件,它专门用来实现Maven工程/项目的模板功能,即我们可以用它来创建各种Maven工程;generate就是该插件的其中一个目标吧,就是用来创建Maven工程的。
- 后面以 -D 开头的部分:就是传给目标的参数了, groupId 是此工程/项目的团队,一般使用类似于Java包名的命名规则; artifactId 是此工程/项目的名称;archetypeArtifactId表明要使用archetype中的何种模板,这里使用的是最简单的一个模板quickstart,其实还有Web工程/项目的模板;archetypeVersion应该是模板的版本了吧;interactiveMode表示执行命令的过程中是否采用询问用户的方式。
groupId和artifactId是Maven定义的用于标识所有工程/项目(实际上就是我们所说的组件/构件/库,或者应用,只要是提供给别人使用的都可以一种物理形态比如JAR文件、WAR文件等存在)的概念,这又体现了对象的唯一标识符问题。Maven把所有工程/项目统称为 依赖 (英文单词是 dependency )。
那么,我们现在去看看创建的Maven项目是怎样的,可以到将命令行窗口中的当前目录(前面已经将其转到你想要存放Maven工程/项目的目录,我这里是E:csdnmaven-projects)中去看看发生了什么变化。
可以发现该目录中多了一个子目录,是以artifactId这个参数的值命名的,这里就是 my-app 。继续进入该目录查看里面有什么,可以发现 my-app 的整个目录结构是这样的:
很明显,my-app这个目录就是此次创建的Maven工程/项目的存储目录了,里面还有:
- pom.xml:这可就是前面提到的Maven所基于的项目对象模型即POM文件了,Maven就是读取这个文件来执行项目的构建、报告和归档等各种动作的。从文件后缀也可以看出它是一个XML文件,关于XML大家可以参考 。我们可以用文本编辑工具打开它来看看:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="" xmlns:xsi="" xsi:schemaLocation=" "> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <version>1.0-SNAPSHOT</version> <name>my-app</name> <!-- FIXME change it to the project's website --> <url> <properties> <project. build .sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId> junit </groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> <build> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <!-- clean lifecycle, see #clean_Lifecycle --> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- default lifecycle, jar packaging: see #Plugin_bindings_for_jar_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-jar-plugin</artifactId> <version>3.0.2</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> <!-- site lifecycle, see #site_Lifecycle --> <plugin> <artifactId>maven-site-plugin</artifactId> <version>3.7.1</version> </plugin> <plugin> <artifactId>maven-project-info-reports-plugin</artifactId> <version>3.0.0</version> </plugin> </plugins> </pluginManagement> </build> </project>
- src/main/java目录:这个就是我们的工程源代码目录了,里面的结构就是Java的包结构,然后是Java类的源码文件,这里Maven帮我们生成了一个App.java,代码相当简单,就是打印 Hello World! :
package com.mycompany.app; /** * Hello world! * */public class App { public static void main( String[] args ) { System.out.println( "Hello World!" ); } }
- src/test/java目录:这个是存放(单元)测试代码的目录,里面的结构跟上面的源码目录结构是一样的,只不过这里生成的是一个AppTest.java:
package com.mycompany.app; import static org.junit.Assert.assertTrue; import org.junit.Test; /** * Unit test for simple App. */public class AppTest { /** * Rigorous Test :-) */ @Test public void shouldAnswerWithTrue() { assertTrue( true ); } }
可以看到,这个类依赖于 junit 这个第三方库,我们也可以在POM文件中找到它:
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies>
它是一个专门用于单元测试的框架,由此可知,我们如果想要为我们的工程/项目添加其他依赖库,也可以在POM文件的这个地方添加。
使用Maven构建工程/项目
既然项目已经创建好了,那如何来编译(构建这个词比较正式或恰当一点,以后就用构建这个词吧)它呢?
首先,我们要把命令行窗口的当前目录转到我们的工程目录 my-app,然后执行一下命令:
mvn package
Maven又是一顿执行,信息又是显示了一大堆,不过最后是这样的:
从最后的信息来看,应该是构建成功了,好像还运行了测试代码。
不管怎样,我们到工程目录下看看有什么变化。工程目录下多了一个 target 目录,再进去看看里面有什么,除了好多目录外,最显眼的应该就是还有一个JAR包,它的名字是 my-app-1.0-SNAPSHOT.jar 。
不用说,应该就是Maven帮我们构建好项目之后,并将它打包成了一个JAR包,我们可以这样来运行(关于java命令的介绍,还可以参考 ):
java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App
- -cp :实际上是指定了JVM搜索类的字节码文件的位置,这里是从一个JAR包里去搜索,大家可以只键入 java 命令即可查看该选项的说明。
- com.mycompany.app.App:就是我们入口类的全限定名(即包含所有包名的类名)。
OK,结果能够正常打印出 Hello World! ,说明构建成功了。
创建工程的命令指定的是某个插件的某个目标,那么,这次的 package 是什么意思呢?
这里又涉及到Maven的另外一个重要概念,就是 生命周期 (英文是 lifecycle )。Maven把工程/项目研发过程中的一些相关的动作定义为一个生命周期,比如,它定义了 Clean生命周期 、 Default生命周期 、 Site生命周期 。每一个生命周期中包含的那些动作就称为 阶段 (英文是 phase )。
我们这里指定的 package 实际上就是 Default生命周期 的其中一个阶段,即让Maven把工程/项目构建到这个阶段,里面就包括了编译等动作。
所以,我们就这样成功编译我们的Java源码了啊。
使用Maven消除添加依赖库的重复劳动
现在,终于要到正题了。我们使用Maven不就是想省事嘛!
前面提到,我们可以在POM文件中的这个位置添加我们的依赖库:
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies>
那这个依赖库的 groupId 和 artifactId 等我们是如何得知呢?事实上,我们可以在这个网站上找到:
我们在搜索栏中输入要找的依赖库,比如H2Database,点击搜索:
点击进入H2Database依赖库的页面:
选择某个版本,进入该版本的页面:
这里我们就可以看到这个依赖库的 groupId 和 artifactId ,我们可以直接把它拷贝到工程POM文件中,不过,我这里做了一个修改,就是将 <scope> 标签的内容修改为 compile (暂时不做解释),最后保存POM文件:
<dependencies> <!-- --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.200</version> <scope>compile</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies>
现在,需要修改我们的src/main/java/com/mycompany/app/App.java,以验证H2Database是否可以使用(验证代码可以参考 ):
package com.mycompany.app; import java.sql.*; /** * Hello world! * */public class App { public static void main( String[] args ) throws SQLException, ClassNotFoundException { System.out.println( "Hello World!" ); Connection conn = DriverManager.getConnection("jdbc:h2:~/h2db/test", "sa", ""); Statement statement = conn.createStatement(); statement.execute("drop table if exists user_table"); statement.execute("create table user_table(id integer primary key, name varchar(100), sex varchar(2))"); statement.executeUpdate("insert into user_table values(1, '张三', '男') "); statement.executeUpdate("insert into user_table values(2, '李四', '女') "); ResultSet resultSet = statement.executeQuery("select * from user_table"); while (resultSet.next()) { System.out.println(resultSet.getInt("id") + ", " + resultSet.getString("name") + ", " + resultSet.getString("sex")); } statement.close(); conn.close(); } }
需要关注的有三点:
- 别忘记了导入JDBC的相关类,我使用了 import java.sql.* ;
- main()方法需要使用 throws 关键字抛出我们不想处理的相关异常;
- 就是我们的代码里有中文,一般都会产生中文乱码的问题,我们这里是直接编译出错,原因是POM文件里指定了一个属性
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
而我们用Maven生成的各类项目文件包括Java源码文件都是继承操作系统的编码(我的是Windows系统,其默认编码是 GBK ,可以在命令行窗口中输入 chcp 命令得到 活动代码页 为936,即对应GBK字符集编码,或者在命令行窗口的标题栏中右击弹出菜单,选择 属性 弹出其属性对话框,上面也可看到 当前代码页 )。
因此,有两种方法可以解决这个问题:
- 一种是将POM文件的属性改为 GBK;
- 一种是将App.java这个文件另存为 UTF-8 格式,我使用的是UltraEdit这个文本编辑器工具,在另存为对话框中选择“ UTF-8 – 无BOM ”。
最后,我们再次执行:
mvn package
构建应该能够成功,此时再执行命令运行应用:
java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App
结果却是令人意外的出错了:
提示说,JDBC没有找到合适的驱动(driver),不是说Maven会帮我下载H2Database的依赖库吗?为什么还会找不到呢?
因为 java 命令只能寻找标准库,或者加入到CLASSPATH环境变量中的目录下的库。那么我们就应该手动的把H2Database库添加到命令中,就像
target/my-app-1.0-SNAPSHOT.jar
添加这个JAR包来寻找我们的入口类一样:
com.mycompany.app.App
那Maven帮我们下载的H2Database库在哪呢?默认情况下,是在我们登录系统的用户的家目录中的 .m2/repository 中,然后是在与该库的 groupId 和 版本 相对应的目录底下,比如H2Database库的groupId是com.h2database,版本是1.4.200,所以下载的H2Database库就在:
.m2repositorycomh2databaseh21.4.200
这个目录下。
OK,我们的执行命令就应该改为:
java -cp target/my-app-1.0-SNAPSHOT.jar;C:Users<这里是用户名>.m2repositorycomh2databaseh21.4.200h2-1.4.200.jar com.mycompany.app.App
执行结果如下:
终于大功告成!以后我们就再也不需要自己去下载依赖库了!
总结
- Maven帮助我们构建项目/工程,消除了添加依赖库的重复劳动;
- 依赖、插件和目标、生命周期和阶段是Maven的核心概念;
- Maven基于POM;
- Maven使用archetype:generate创建工程/项目;
- Maven使用package构建、打包项目;
- Maven工程/项目添加依赖在POM中的<dependencies>标签;
- 一个依赖库可以上 搜索它的groupId等;
- 中文乱码肯定是字符集编码的配置不当引起的;
- java 命令需要把下载的依赖库加上才能找到相关类;
- 我们也要善于创造新概念,新思想、新模型、新工具,凡是有重复的地方基本上都可以抽象;
- 插件其实就是可插拔的思维、解耦的思维的体现;
- 万事万物都有生命周期,生命周期思维也是很重要的一个思维。
Maven的内容还有很多,以后再讨论。
上面虽然使用Maven创建和构建了工程/项目,但是使用命令行并不怎么友好,效率也不见得提高多少。
我们现在使用的是Eclipse这个集成开发环境,如果能在Eclipse中使用Maven岂不妙哉?!