> 文档中心 > 一文教你精通Maven,从传统Java和JavaWeb项目快速切换到Maven项目,Maven实战案例教程

一文教你精通Maven,从传统Java和JavaWeb项目快速切换到Maven项目,Maven实战案例教程


一、Maven概念

1.1 Maven简介

Maven是一个项目管理工具,它包含了一个项目对象模型(Project Object Model),一组标准集合,一个项目生命周期(Project Lifecycle),一个依赖管理系统(Dependency Management System),和用来运行定义在生命周期阶段(phase)中插件(plugin)目标(goal)的逻辑。当你使用 Maven 的时候,你用一个明确定义的项目对象模型来描述你的项目,然后 Maven 可以应用横切的逻辑,这些逻辑来自一组共享的(或者自定义的)插件。

Maven翻译为“专家”,“内行”。Maven是 Apache 下的一个纯 Java 开发的开源项目,它是一个项目管理工具,可以使用 Maven 对 Java 项目进行构建、依赖管理。当前使用 Maven 的项目在持续增长。

Maven官网:https://maven.apache.org/

Maven功能:清理、编译、测试(自己写单元测试)、报告、打包(导出jar、war)、部署。

1.2 什么是项目构建

    项目构建是一个项目从编写源代码到编译、测试、打包、部署、运行的过程。

1.2.1 传统项目构建过程

传统的使用Eclipse构建项目的过程如下:

     构建过程如下:  
        1、在Eclipse中创建一个JavaWeb工程。
        2、在工程中编写源代码及配置文件等。
        3、对源代码进行编译,Java文件编译成class文件。
        4、执行Junit单元测试。
        5、将工程打成war包部署至Tomcat运行。

1.2.2 Maven项目构建过程

    Maven将项目构建的过程进行标准化,每个阶段使用一个命令完成,下图展示了构建过程的一些阶段,后面章节详细介绍每个阶段,这里先大概了解下:

    上图中部分阶段对应命令如下:
        清理阶段对应的 Maven 命令是clean,清理输出的class文件。
        编译阶段对应的 Maven 命令是compile,将Java代码编译成class文件。
        打包阶段对应的 Maven 命令是package,Java工程可以打成jar包,web工程可以打成war包。
        运行一个Maven工程(web工程)需要一个命令:tomcat:run。

    Maven工程构建的优点:
        1、一个命令完成构建、运行,方便快捷。
        2、Maven对每个构建阶段进行规范,非常有利于大型团队协作开发。

1.3 什么是依赖管理

    什么是依赖? 一个Java项目可能要使用一些第三方的jar包才可以运行,那么我们说这个Java项目依赖了这些第三方的jar包。举个例子:一个crm系统,它的架构是SSH框架,该crm项目依赖SSH框架,具体它依赖Hibernate、Spring、Struts2相关jar包。

    什么是依赖管理? 就是对项目所有依赖的jar包进行规范化管理。

1.3.1 传统项目的依赖管理

    传统的项目工程要管理所依赖的jar包完全靠人工进行,程序员从网上下载jar包添加到项目工程中,如下图:程序员手工将Hibernate、Struts2、Spring的jar添加到工程中的WEB-INF/lib目录下。

     手工拷贝jar包添加到工程中的问题是:
        1、没有对jar包的版本统一管理,容易导致版本冲突。
        2、从网上找jar包非常不方便,有些jar找不到。
        3、jar包添加到工程中导致工程过大。

1.3.2 Maven项目的依赖管理

    Maven项目管理所依赖的jar包不需要手动向工程添加jar包,只需要在pom.xml(Maven工程的配置文件)添加jar包的坐标,自动从Maven仓库中下载jar包、运行,如下图:

     使用Maven依赖管理添加jar的好处:
        1、通过pom.xml文件对jar包的版本进行统一管理,可避免版本冲突。
        2、Maven团队维护了一个非常全的Maven仓库,里边包括了当前使用的jar包,Maven工程可以自动从Maven仓库下载jar包,非常方便。

1.4 Maven概念模型

    在上述概念中,我们提到:Maven 是一个项目管理工具,它包含了一个项目对象模型(Project Object Model),一组标准集合,一个项目生命周期(Project Lifecycle),一个依赖管理系统(Dependency Management System),和用来运行定义在生命周期阶段(phase)中插件(plugin)目标(goal)的逻辑。

1.4.1 项目对象模型(Project Object Model)

    Maven项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的项目管理工具软件。

    Maven除了以程序构建能力为特色之外,还提供高级项目管理工具。由于 Maven 的缺省构建规则有较高的可重用性,所以常常用两三行 Maven 构建脚本就可以构建简单的项目。 

    一个Maven工程都有一个pom.xml文件,通过pom.xml文件定义项目的坐标、项目依赖、项目信息、插件目标等。

我们先看一下pom.xml:

    4.0.0    com.maven    maven-helloworld    0.0.1-SNAPSHOT    war    第一个maven工程    第一个maven工程              junit     junit     4.12     test       javax.servlet.jsp     jsp-api     2.1     provided       javax.servlet     javax.servlet-api     3.0.1     provided                       org.apache.maven.plugins  maven-compiler-plugin  3.2        1.8      1.8      utf-8                   org.apache.tomcat.maven  tomcat7-maven-plugin  2.2              9090            /mgr      UTF-8      tomcat7            

第一部分:工程的基本信息

4.0.0com.mavenmaven-helloworld0.0.1-SNAPSHOTwar第一个maven工程第一个maven工程

第二部分:被dependencies包围起来的是项目中需要用到的jar包

     junit junit 4.12 test         javax.servlet.jsp jsp-api 2.1 provided         javax.servlet javax.servlet-api 3.0.1 provided    

第三部分:被build包围起来的是项目中需要用到的插件

               org.apache.maven.plugins     maven-compiler-plugin     3.2       1.8  1.8  utf-8             org.apache.tomcat.maven     tomcat7-maven-plugin     2.2         9090    /mgr  UTF-8  tomcat7          

    在上面已经详细介绍了,分为三个部分,分别为:
        1、项目自身信息。
        2、项目运行所依赖的jar包信息。    
        3、项目运行环境信息,比如:jdk、tomcat信息。

1.4.2 依赖管理系统(Dependency Management System)

    通过Maven的依赖管理对项目所依赖的jar包进行统一管理。项目通过在pom.xml中定义依赖来管理jar包。

    图片上的 central 是指中央仓库,b2b 是指远程仓库,local 是指本地仓库,这里着重介绍组成Dependency的三大部分:
        1、公司组织的名称。
        2、项目名。
        3、版本号。

下面分析一个具体的Dependency:

    javax.servlet    javax.servlet-api    3.0.1    provided

    其中 javax.servlet 为公司组织(父项目)的名称,javax.servlet-api 为项目名,3.0.1 为版本号。

    为什么要进行依赖管理?
        依赖管理:
            1、可连接互联网来自动下载。
            2、对我们自己开发的模块做依赖管理。

1.4.3 项目生命周期(Project Lifecycle)

    图片下半部分的 Build lifecycle 是指 Maven 的生命周期,下面的Plug-in是指插件,即每一个Maven的命令都对应着Maven的底层插件。

    通过Maven完成项目的构建:清理、编译、测试、部署等,Maven将这些工程规范为一个生命周期。

     Maven通过执行一些简单命令就可以实现上面生命周期各个过程。

1.4.4 一组标准集合

    Maven将整个项目管理过程定义一组标准,比如Maven构建工程有标准的目录结构,有标准的生命周期阶段,依赖管理有标准的坐标定义等。
    
    Maven提倡使用一个共同的标准目录结构,方便不同开发者的协作,也方便了一些统一的自动化脚本的实现。

    以前创建的java项目:
        src    -> 源码目录,但这不是标准,我们可以随便创建一个文件夹,让其成为源码目录。
        libs -> 第三方jar包,但是该目录可以是任意的名称。 
    
    以前创建的web项目:
        src    -> 源码目录。
        web -> 通过IDEA工具创建的,用来存放静态资源、配置文件、第三方jar的目录。
        WebContent/WebRoot -> 通过Eclipse工具创建的,用来存放静态资源、配置文件、第三方jar的目录。

    我们之前创建的项目,不存在标准。

1.4.5 插件目标

    Maven管理项目生命周期是基于插件完成的。

1.5 为什么使用Maven

    1、一个项目就是一个工程
        如果项目非常庞大,就不适合使用package来划分模块,最好是每一个模块对应一个工程,利于分工协作。借助于Maven就可以将一个项目拆分成多个工程。

    2、项目中使用jar包,需要“复制”、“粘贴”项目的lib中,不方便管理
        当我们的项目比较大的时候,我们会将一个大的项目分成很多小的项目,每个小项目由几个开发负责,比如一个电商项目分为:账户相关的项目、订单相关的项目、商品相关的项目,这些项目的结构都是类似的,用到的技术都是一样的:SSM(Spring、Springmvc、Mybatis),然后每个项目都需要把这些jar拷贝一份到自己的项目目录中,最后10个项目只是jar就复制了10份,后来,我们发现项目中有些jar需要升级版本,打算替换一下,此时我们需要依次去替换10个项目,也是相当痛苦。
        借助于Maven,可以将jar包保存在“仓库”中,不管在哪个项目只要使用引用即可就行。

    3、jar包需要的时候每次都要自己准备好或到官网下载
        比如我们项目中需要用到fastjson,此时我们会去百度上检索fastjson相关jar包,然后下载下来,放到项目的lib下面,然后加到项目的classpath下面,用着用着发现这个jar的版本太老了,不是我们想要的,然后又重新去找,痛苦啊。
        Maven给每个jar定义了唯一的标志,这个在maven中叫做项目的坐标,通过这个坐标可以找到你需要用到的任何版本的jar包。

    4、jar包版本不一致的风险
        项目中用到了a.jar,a.jar依赖于c.jar的1.5版本,然后我们把这2个jar拷贝到项目中,后面又用到了b.jar,但是b.jar又依赖于c.jar的1.0版本,此时你把b.jar和c-1.0.jar引进来了,会发现c.jar有2个版本,发生冲突了,启动的时候会报错,这种情况你要着手去解决jar冲突的问题,也是非常痛苦的。
        Maven可以很容易的解决不同版本之间的jar冲突的问题。

    5、一个jar包依赖其他的jar包需要自己手动的加入到项目中
        jar包一般都不是独立存在的,一般一些jar也会用到其他的jar,比如spring-aop.jar会用到spring-core.jar,这种依赖可能比较简单,有些依赖可能有很多级,比如a.jar依赖于b.jar,而b.jar依赖c.jar,而c.jar又依赖于d.jar,当你用到a.jar的时候,你需要把其他3个也进入才可以,所以你用到一个jar的时候,你必须明确知道这些jar还会依赖于哪些jar,把他们都引入进来,否则项目是无法正常运行的,当项目用到很多jar的时候,我们是很难判断缺少哪些jar的,只有在项目运行过程报错了,才知道,这种也是相当痛苦的,浪费了大量时间。
        Maven会自动解决jar依赖的问题,比如你用到了a-1.0.jar,而a-1.0.jar依赖于b-1.1.jar和c-1.5.jar,当我们通过Maven把a-1.0.jar引入之后,b-1.1.jar和c-1.5.jar会自动被引入进来。

    6、统一的项目结构
        很久之前,我们使用eclipse搭建一个项目的时候,Java源码的位置、资源文件的位置、测试文件的位置、静态资源位置、编译之后的class文件位置,都是可以随意放的,这些是由各自公司的架构师搭建项目时定好的,根据他们的经验来定义的,导致每个公司可能项目结构都不太一样,来新人之后,看到项目结构一脸闷逼,根本不知道哪是哪,需要人指导,无形的增加了成本,如果大家都按照某种规范采用同一种项目结构,这样岂不是很方便么,大家按照某种约定,项目使用同样的结构,比如:Java文件、资源文件、测试用例、静态资源、编译之后的class、打包之后jar的位置等等各种文件的位置,这些东西,如果所有做Java开发的公司都约定好的,这样拿到一个项目之后,就可以省去很多事情了。
        使用Maven搭建的项目架构,都需要遵循同样的结构,Java源文件、资源文件、测试用例类文件、静态资源文件这些都是约定好的,大家都按照这个约定来,所有如果你们的项目是使用Maven创建的,招新人来接手,如果他们懂Maven,根本不需要培训,上来就可以看懂整个项目的结构。

    7、跨平台
        Maven的跨平台,可在Window、Linux上使用。

二、Maven安装与配置

2.1 安装JDK,并配置JDK的环境变量

    Maven的安装必须基于JAVA_HOME配置的环境变量。

    系统变量:
        变量名:JAVA_HOME
        变量值:JDK安装根路径 (C:\Java\jdk1.8.0_311)

    Path路径引入:
        %JAVA_HOME%\bin

    注:Maven3.3+版本,jdk至少是1.7及以上版本

2.2 Maven安装

2.2.1 Maven下载

    Maven的官网下载地址:https://maven.apache.org/download.cgi,可以下载最新版本。
        
    注:Maven无需要求最新版,但至少3.3+版本。

2.2.2 Maven安装

    Maven本身是绿色版不需要安装,解压即可。将Maven解压到一个不含有中文和空格的目录中。

2.2.3 配置Maven环境变量

    1、计算机右键 -> 属性 -> 高级系统设置 -> 高级选项卡 -> 环境变量。    

    2、新建系统变量:
        变量名:MAVEN_HOME
        变量值:D:\Tools\apache-maven-3.8.5(Maven解压路径)

     3、在Path系统变量中加入%MAVEN_HOME%\bin

2.2.4 测试

    打开cmd命令行窗口,通过mvn -version命令测试配置是否成功。

2.3 Maven目录分析

    bin目录:Maven运行的脚本。
        mvn(以run方式运行项目)
        mvnDebug(以debug方式运行项目)

    boot目录:Maven运行需要的类加载器。

    conf目录:
        settings.xml,整个Maven工具核心配置文件。 

    lib目录:Maven运行依赖jar包。

2.4 全局settings与用户settings

    Maven仓库地址、私服等配置信息需要在settings.xml文件中配置,分为全局配置和用户配置。

    在Maven安装目录下的有 conf/settings.xml文件,此settings.xml文件用于Maven的所有project项目,它作为Maven的全局配置。

    如需要个性配置则需要在用户配置中设置,用户配置的setting.xml文件默认的位置在:${user.home}/.m2/settings.xml目录中,${user.home} 指 windows 中的用户目录。(个人推荐)

    Maven会先找用户配置,如果找到则以用户配置文件为准,否则使用全局配置文件。

    作用:Maven的本地仓库位置,中央仓库镜像等配置。

    maven全局配置文件settings.xml详解:https://cloud.tencent.com/developer/article/1014577

2.5 配置阿里云镜像仓库

    所有使用Maven构建的项目,都会去Maven仓库下载,会出现两个问题:
        1、下载速度过慢。
        2、官方镜像节点黑名单机制。
    
    注意:查看对外访问的ip地址ip138.com。

    基于以上问题,我们需要配置国内的镜像节点,推荐使用阿里云镜像(记得注释掉默认的镜像节点)。

    注:“镜像(Mirroring)是一种文件存储形式,是冗余的一种类型,一个磁盘上的数据在另一个磁盘上存在一个完全相同的副本即为镜像。”

方式一:全局配置

    可以添加阿里云的镜像到maven的setting.xml配置中,这样就不需要每次在pom中,添加镜像仓库的配置,在mirrors节点下面添加子节点:

    <!-- mirror     | Specifies a repository mirror site to use instead of a given repository. The repository that     | this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used     | for inheritance and direct lookup purposes, and must be unique across the set of mirrors.     |          mirrorId      repositoryId      Human Readable Name for this Mirror.      http://my.repository.com/repo/path         -->    <!--          maven-default-http-blocker      external:http:*      Pseudo repository to mirror external repositories initially using HTTP.      http://0.0.0.0/      true     -->          alimaven  aliyun maven  http://maven.aliyun.com/nexus/content/groups/public/  central     

方式二:用户配置

    把conf/settings.xml复制一份到.m2文件夹下面。

    注:如果创建.m2文件夹失败,可以考虑使用命令行来创建,进入到用户目录,执行mkdir .m2。

注:一定要把镜像配置放到下面

2.6 Maven卸载

    Maven由于安装的时候只是解压,配置环境变量,设置本地仓库,所以卸载的时候也很简单。
        1、删除解压的maven文件夹;
        2、删除设置的环境变量MAVEN_HOME,删除path里添加的“%MAVEN_HOME%\bin;”;
        3、删除本地仓库;

三、Maven约定

Java项目与JavaWeb项目中src即源码目录。

    Maven项目的源码目录有四个,分别为:
        src/main/java:存放项目的源码(.java)文件。
        src/main/resources:存放项目配置资源文件和资源文件,如Spring、Mybatis配置文件,db.properties数据库资源文件。
        src/test/java:存放测试代码,如Junit测试类。
        src/test/resources:存放测试中使用的配置文件。

src/main/webapp:Maven Web项目的目录,类似eclipse中Web项目的WebContent/WebRoot目录。

target:项目输出位置,编译后的class文件会输出到此目录。

pom.xml:当前项目的配置管理、依赖管理、插件管理。

结构如下:

Project
    |---src
    |----|---main
    |----|----|---java            存放项目的.java文件
    |----|----|---resources        存放项目资源文件,如spring, hibernate配置文件
    |----|----|---webapp        webapp目录是web工程的主目录
    |----|----|---|---WEB-INF
    |----|----|---|---web.xml
    |----|---test
    |----|----|---java            存放所有测试.java文件,如JUnit测试类
    |----|----|---resources        测试资源文件
    |---target                    目标文件输出位置例如.class、.jar、.war文件
    |---pom.xml                    maven项目核心配置文件

四、手动搭建Maven工程实现

4.1 手动搭建Maven Java项目

    我们必须知道:Maven本身是一个平台无关的开源工程,也就是说:Maven可以为创建在任何开发平台上的Maven项目提供jar文件支持,也就是说:即使脱离了Eclispe、IDEA这样的开发平台,Maven也是可以直接运行,为项目提供jar文件支持的。

    那么下面我们手动创建并编译、运行一个独立的Maven项目,并经由这个项目,补充一些关于Maven本身的其他知识点。

4.1.1 创建Maven项目的文件夹目录

首先我们来看一个目录结构:

MavenProjectName:
    |---src
    |----|---main
    |----|----|---java
    |----|----|---resources
    |----|---test
    |----|----|---java
    |----|----|---resources
    |---pom.xml

    那么我们首先按照这个最简结构,在自己的磁盘上手动创建一个用于保存Maven项目Java文件和测试文件的文件夹结构,并且在这个文件夹中编写代码、添加POM文件,并逐步将这个项目进行编译和运行。

4.1.2 配置Maven工程的pom.xml文件

    Maven工程的 pom.xml 文件是Maven工程的灵魂,一个Maven项目下所有重要的信息和对jar文件的支持都是通过这个文件进行配置的,下面我们来手动配置这个pom.xml文件(注意编码格式)。

1、在项目的根文件夹下创建pom.xml配置文件,并创建如下基本结构:

    4.0.0

上述配置属于Maven项目中pom.xml文件的基本配置方式,其中:

    标签:标示的是当前Maven项目使用的描述规范版本号。这个标签大家不必纠结,目前比较主流的Maven项目使用的描述规范版本号是4.0.0版本。

2、配置pom.xml文件中的项目坐标:

    每一个Maven项目必须在pom.xml文件中配置如下内容,因为Maven就是根据这些配置信息来确定一个项目的具体位置的,所以下面我们将要配置的信息,也称之为一个项目的项目坐标。

    如何理解项目坐标:在实际生活中,坐标的概念就很普及:同样是在杨国福麻辣烫订的餐,为什么外码小哥能够准确的将你的那一份送到你的手中,而不是别人的手中呢?就是使用坐标。现实生活中我们使用省、市、区、街道、小区、栋、单元、楼层、门牌号等信息标识我们自己的坐标,所以外卖小哥能够按照这个“坐标”将外卖正确的送到你的手中。

    同样的,在我们一台计算机当中,可能同时存在多个Java项目,那么Maven是如何对这些项目进行区分的呢?同样是使用项目坐标,所以说:项目坐标是Maven软件用来识别一个Java项目的唯一标识。

    就像我吃麻辣烫一定要加墨鱼丸,并且从来不吃香菜一样,Maven软件也是根据项目坐标来区分这个项目需要哪些jar文件,不需要哪些jar文件的。

    OK!现在应该已经能够理解项目坐标的含义了吧?那么下面我们来介绍一下如何配置一个Maven项目的项目坐标:首先说明下:项目坐标直接配置在pom.xml文件的标签之下。

描述一个项目坐标的标准结构如下:

                    

这些标签的配置信息含义如下:

    标签:项目组织唯一的标识符。一般为公司名称或组织名称或大项目名称,通常采用域名倒置的取名方式,影响安装到仓库的首层目录结构。
        比如:
            servlet相关jar,它的groupId就是javax.servlet,javax.servlet就是大项目名称(父项目名称)。
            mybatis相关jar,它的groupId就是org.mybatis,org.mybatis就是公司名称或组织名称。
    
    标签:项目的唯一的标识符。一般为项目名称或子项目名称或模块名称,影响安装到仓库的次层目录结构以及打包的名称。对于标签中的取值,我们建议使用当前项目(模块)的真实名称。
    
    groupId和artifactId被统称为“坐标”是为了保证项目唯一性而提出的,如果你要把你项目弄到maven本地仓库去,你想要找到你的项目就必须根据这两个id去查找。groupId一般分为多个段,这里我只说两段,第一段为域,第二段为公司名称。域又分为org、com、cn等等许多,其中org为非营利组织,com为商业组织。举个apache公司的tomcat项目例子:这个项目的groupId是org.apache,它的域是org(因为tomcat是非营利项目),公司名称是apache,artigactId是tomcat。

    标签:表示当前项目的版本号,影响安装到仓库的末层目录结构以及打包的名称。

    标签:表示当前项目的打包方式,除了常见取值"jar"之外,还可以取值"war",表示将当前工程打包为一个war文件
        可选jar:表示打包时以.jar文件打包。
        可选war:表示打包时以.war文件打包。
        可选pom:表示当前项目为管理类项目。
        默认为jar。
    
    标签:用来表示当前项目在打包生成的时候产生的一些附件文件。
        More:https://www.cnblogs.com/love-kimi/archive/2012/10/09/2716507.html
    
    怎么样,上面的标签很多吧?但是在真正创建一个Maven项目的时候,根本用不上这么多坐标,Maven项目必须的坐标标签只有前3个!
    
    下面我们来看一下如何配置我们当前这个项目的坐标:将如下标签内容拷贝到之前创建的pom.xml文件的标签之下。

com.mavenMavenTest0.0.1-SNAPSHOT

在将上述配置拷贝的pom.xml文件之下后,我们得到这样一个pom.xml文件的内容:

        4.0.0            com.maven        MavenTest        0.0.1-SNAPSHOT        jar

    到此为止,我们关于项目坐标的配置就完成了。

关于项目版本号的说明:

    Maven项目的标签中使用的版本号,除了0.0.1这个数字版本号之外,还有一个版本类型:SNAPSHOT这个单词的意思是快照版本。那么项目的版本类型都分为哪几种呢?
    1、snapshot:快照版本,也就是开发版本,这个版本的项目是不能够使用的,因为正在处于开发阶段,功能不全,错误也很多,Eclispe和IDEA在创建Maven项目的时候默认会使用这个版本。
    2、alpha:内部测试版,此版本的程序也不推荐使用,这个版本的程序用在开发人员内部进行测试,寻找并更改bug。
    3、beta:外部测试版,面向用户群体发布的测试版本,虽然能够使用,但是仍然不稳定,存在较多bug,不推荐使用。
    4、release:发行版,表示官方推荐使用的版本,功能较为全面,并且运行稳定,但是会存在一些bug,可以正常使用。
    5、stable:稳定版,在发行版的基础上去除绝大部分bug,优化一些功能,建议使用。
    6、current:最新版本,不一定是稳定版本,但是具有最新的功能,如果有对应的发行版或者稳定版,建议使用前两者。
    7、eval:评估版本,具有一个月或者固定的使用期限。
    此外,还有pre、RC、GA等版本,在此不进行详细描述。

3、在pom.xml文件中添加jar文件依赖:

    我们学习Maven的目的就是为了让Maven为我们的项目自动添加和管理所需的jar文件。

    标签中配置当前Maven项目中所有的依赖,每个依赖都以标签包裹,其中是依赖jar的坐标。

    并且在之前我们已经介绍过如何向pom.xml文件中添加项目所需jar文件的依赖配置,在这里我们不再进行赘述。

    为了方便当前项目的测试,我们在pom.xml文件中添加junit-4.12版本的jar文件依赖:

<!--    所有jar文件依赖的声明都放在标签下的子标签当中    每一个依赖单独创建一个子标签-->         junit junit 4.12 test    

将上述配置内容加入pom.xml文件之后,我们得到了一个比较全面的pom.xml文件版本:

    4.0.0com.maven    MavenTest    0.0.1-SNAPSHOT    jar<!--    所有jar文件依赖的声明都放在标签下的子标签当中    每一个依赖单独创建一个子标签-->junitjunit4.12test

    到此为止,整个Maven工程的pom.xml配置文件就已经搞定了,下面让我们来写一些Java代码。

4.1.3 在Maven工程中编写Java代码

    Maven工程是严格区分Java源代码和测试代码的,这些代码会被分别存放在不同位置下,并且在之后的编译和运行过程中我们也会看到,Maven对Java源代码和测试代码的编译和运行也是截然不同的。

1、编写Java源代码:

    Maven项目下所有的Java源代码都是统一存放在src/main/java这个文件夹下的,那么我们在这个文件夹下创建如下Java源文件和代码:

HelloMaven.java:

package com.maven;public class HelloMaven {private String msg = "Hello Maven!";public void sayHi() {System.out.println(msg);}}

2、编写测试代码:

    Maven项目下的所有测试代码存放在src/test/java这个文件夹下,那么我们在这个文件夹下创建如下的测试类和测试代码:

HelloMavenTest.java:

package com.maven;import org.junit.Test;public class HelloMavenTest {@Testpublic void test1() {HelloMaven hm = new HelloMaven();hm.sayHi();}}

    到此为止,我们Maven工程中的Java代码就已经写好了,在上述步骤中,我们需要重点掌握的是:Maven项目中,Java源代码和测试代码的存放路径的位置。

4.1.4 编译和运行Maven项目

    Maven项目在创建完成后,需要手动对项目进行编译。并且,因为在测试代码中用到了Java原码中创建的HelloMaven这个类型,所以我们必须按照如下顺序对项目进行编译:
        1、首先编译src/main/java文件夹下的Java原码
        2、其次编译src/test/java文件夹下的测试代码

    只有按照这个顺序进行编译,才能够保证测试代码能够正常运行。

1、编译Java源代码:

    首先我们进入Maven项目的根目录下,然后通过如下命令对Maven项目中的源代码进行编译:

mvn compile

    注意:如果是第一次编译Maven项目,那么Maven将会从远程的中央仓库中下载所需的jar文件到本地仓库中。

    如果我们已经配置过使用镜像进行下载,那么这个下载过程将会快得多(为了进行演示,我把本地仓库中所有已经下载好的jar文件都删了……)。

 项目编译成功后将会有如下结果:

    首先是在命令控制台中会显示如下结果内容:

     其次是在src文件夹下,会出现一个名为target的新文件夹,这个文件夹中负责保存当前已经编译好的所有Java文件的字节码文件。

 2、编译并运行测试代码:

    Maven工程下的测试代码的编译和运行使用的是同一个命令:

mvn test

    也就是说,在运行上述命令的时候,Maven会同时执行如下3个操作:
        1、根据pom.xml配置文件中声明的项目使用的jar文件依赖,去中央仓库将所需的jar文件下载到本地仓库中。
        2、编译测试代码,生成其对应的字节码文件。
        3、直接执行测试类中的测试方法,打印测试结果。

下载jar文件到本地仓库的过程:

 编译测试文件并运行测试方法,得到运行结果:

 3、Maven项目的清理:

    如果需要清空已经编译生成好的Java原码和测试代码的字节码文件,我们可以使用如下命令:

mvn clean

Maven正在清理项目:

 清理完成:

 在运行这个命令后,Maven会自动删除项目根目录下的target文件夹:

     到此为止,我们手动创建、配置并编译、运行一个Maven项目的操作就全部结束了。

4.2 手动搭建Maven Web项目

    Maven Web项目只是比Maven Java项目多了一些结构,按照Maven约定构建即可。

4.2.1 web目录创建

    在src/main目录下创建webapp目录。webapp目录是web工程的主目录。

    之后就是按照Web工程进行操作,不再赘述。 

4.2.2 HelloServlet的创建

    在src/main/java目录下面创建com.maven.servlet包,在该下定义HelloServlet类。

HelloServlet类代码如下:

package com.maven.servlet;import java.io.IOException;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;@WebServlet("/HelloServlet")public class HelloServlet extends HttpServlet {protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// TODO Auto-generated method stubdoPost(request, response);}protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// TODO Auto-generated method stubresponse.getWriter().write("Hello.................");}}

4.2.3 配置依赖和运行环境(后面详解)

  4.0.0  com.newcapec  HelloMaven  0.0.1-SNAPSHOT  war    org.apache.maven.pluginsmaven-compiler-plugin1.71.7UTF-8javax.servletservlet-api2.5providedjavax.servletjsp-api2.0provided  

4.2.4 运行测试    

    打开资料里面的HelloMaven,测试运行即可。

    进入Maven工程目录(当前目录有pom.xml文件),运行mvn tomcat:run命令。

    根据上边的提示信息,通过浏览器访问:http://localhost:8080/HelloMaven/HelloServlet

4.2.5 Maven Web项目报错处理

    问题一:可能存在jar包冲突。
        解决方案:在jsp 和 servlet的依赖中添加provided。

    问题二:Maven内置中的Tomcat是6.0,这个版本的不支持jdk1.8。
        配置tomcat7插件,同时配置maven编译环境为1.8。
        运行mvn tomcat7:run命令启动服务。如果继续用tomcat:run还是会使用Maven的Tomcat6;会继续出错…

     org.apache.tomcat.maven tomcat7-maven-plugin 2.2         org.apache.maven.plugins maven-compiler-plugin      1.8     1.8     UTF-8     

4.3 命令测试

打开cmd命令行,进入hello项目根目录执行以下命令,查看根目录变化:

#编译:mvn compile#清空:mvn clean#测试:mvn test#打包:在打包之前先执行test命令,经过测试发现,并不会打包测试文件mvn package#把项目安装到本地仓库,在安装之前先执行package命令:mvn install

五、Maven仓库和坐标

5.1 Maven仓库概述

    Maven仓库分类:
        本地仓库:本地主机上的仓库。
        远程仓库:如果本地需要插件或者jar包,本地仓库没有,默认去远程仓库下载。远程仓库可以在互联网内也可以在局域网内。

    Maven的工作需要从仓库下载一些jar包,如下图所示,本地的Maven项目都会通过Maven软件从远程仓库(可以理解为互联网上的仓库)下载jar包并存放到本地仓库,本地仓库就是本地文件夹,当第二次需要此jar包时则不再从远程仓库下载,因为本地仓库已经存在了,可以将本地仓库理解为缓存,有了本地仓库就不用每次从远程仓库下载了。

下图描述了maven中仓库的类型:

5.2 中央仓库

    由于最原始的本地仓库是空的,Maven必须知道至少一个可用的远程仓库,才能在执行Maven命令时下载到需要的构建。

    中央仓库是Maven默认的远程仓库,服务于整个互联网,它是由Maven团队自己维护,里面存储了非常全的jar包,它包含了世界上大部分流行的开源项目构件。

    中央仓库地址是:https://repo.maven.apache.org/maven2。

    Maven的安装文件默认自带了中央仓库的配置,在MAVEN_HOME/lib/maven-model-builder.jar中可找到,org\apache\maven\model\pom-4.0.0.xml,其中配置有中央仓库地址。

     central Central Repository https://repo.maven.apache.org/maven2 default  false     

5.3 私有仓库

    一种特殊的远程仓库,它是架设在局域网内的仓库,主要是为了团队协作开发,可以理解为自己公司的仓库,也叫私服。

    配置阿里云镜像,阿里云镜像其实就是阿里的私有仓库,只是公开给大家使用。

    alimaven    aliyun maven    http://maven.aliyun.com/nexus/content/groups/public/    central

使用私服的好处:

    节省自己的外网带宽:建立私服可以减少组织自己的开支,大量的对于外部远程仓库的重复请求会消耗很大的带宽,利用私服代理外部仓库后,对外的重复构件下载得以消除,即降低外网带宽的压力。

    加速Maven的构建:不停的请求外部仓库无疑是比较耗时的,但Maven的一些内部机制(如快照检测)要求Maven在执行构建的时候不停地检查远程仓库的数据。因此当配置了很多远程仓库时,构建的速度会被大大降低。使用私服可以很好地解决这个问题。

    部署第三方构件:当某个构件无法从外部远程仓库下载怎么办?这样的例子很多,如组织内部的生成的私有的构件肯定无法从外部仓库获取,Oracle的JDBC驱动由于版权原因不能发布到外网的中心仓库。建立私服之后便可以将这些构件部署到本地私服中,供内部的Maven项目使用。

    提高稳定性,增强控制:Maven构建搞定依赖于远程仓库,因此,当Internet不稳定的时候,Maven构建也会变的不稳定,甚至无法构建。
使用私服后即使暂时没有Internet连接Maven也可以正常运行,因为私服中缓存了大量的构件。此外一些私服软件(如:Nexus)还提供了很多额外的功能,如权限管理,RELEASE/SNAPSHOT区分等,管理员可以对仓库进行一些更高级的控制。

    降低中央仓库的负荷:数百万的请求,存储数T的数据,需要相相当大的财力。使用私服可以避免很多对中央仓库的重复请求。

5.4 本地仓库

    当Maven执行编译或测试时,如果需要使用到依赖文件,它总是基于坐标使用本地仓库的依赖文件。默认情况下,不管Linux还是Windows,每个用户在自己的用户目录下都有一个路径名为.m2/respository/的仓库目录。

    从中央仓库或私有仓库中下载jar包和Maven信息或者自己打jar包的依赖仓库。

    默认位置:~/.m2/repository

    修改本地仓库位置:在MAVE_HOME/conf/settings.xml文件中配置本地仓库位置。

D:\maven\repository

5.4 Maven坐标

    Maven仓库中拥有大量的依赖jar包,需要用来唯一标识来构建的统一规范。拥有统一规范,就可以从Maven仓库中准确的找到所需依赖。

                

六、在eclipse中使用Maven

6.1 安装Maven插件

    打开eclipse,点击菜单 Help - > Install New Software,点击 Add按钮。
        Name:m2e 
        Location: http://download.eclipse.org/technology/m2e/releases

注:由于新版本的 eclipse,已经直接集成了 Maven,所以无需在安装 m2eclipse 插件。

6.2 在eclipse中配置Maven

    1、打开eclipse,点击菜单Window -> Preferences,找到Maven -> Installations,修改默认Maven,点击Add添加自己的Maven。

    注意:如果自带的Maven版本是3.3+,可以省略此步骤,使用默认的即可。当然也可以使用自己安装的Maven。

    2、点击User Settings,重新选择User Settings文件和Local Repository路径。

    注意:正常来说,eclipse会默认使用用户目录下面的.m2文件夹下的settings/xml,以及自动识别settings.xml文件的配置信息。

     3、配置下载 jar 包的源码和文档(如需)。
    
    注意:如果勾选下面两项,则在下载依赖的时候,依赖对应的源码和文档都会一起下载(个人不推荐勾选)。

6.3 在eclipse中创建Maven项目

6.3.1 创建Maven Java项目

    1、点击File -> New -> Project,找到Maven -> Maven Project。

     2、点击下一步,选择Maven archetype(原型)。

     3、Catalog选择默认是All Catalogs,表示所有的原型。在Catalog中选择Internal,表示Maven内置的类型(官网提供的)。
    
        如果是Maven Java项目选择maven-archetype-quickstart。
        如果是Maven Web项目选择maven-archetype-webapp。

     4、输入Maven坐标。
        Group Id:公司域名倒置。
        Artifact Id:项目名。
        Version:版本号。
        Package:按需定义。

    5、Maven会自动下载项目所需依赖包,完成后重构项目结构。

    注意:eclipse默认引入的junit依赖版本为3.8,我们将其定义为4.12。

     6、删除自动生成App.java和AppTest.java,并编写项目代码即可。

项目源代码Student.java:

package com.maven;public class Student {    private int id;    private String name;    private String sex;    private double score;    public Student() { super(); // TODO Auto-generated constructor stub    }    public Student(int id, String name, String sex, double score) { super(); this.id = id; this.name = name; this.sex = sex; this.score = score;    }    public int getId() { return id;    }    public void setId(int id) { this.id = id;    }    public String getName() { return name;    }    public void setName(String name) { this.name = name;    }    public String getSex() { return sex;    }    public void setSex(String sex) { this.sex = sex;    }    public double getScore() { return score;    }    public void setScore(double score) { this.score = score;    }    @Override    public String toString() { return "Student [id=" + id + ", name=" + name + ", sex=" + sex + ", score=" + score + "]";    }}

项目测试代码StudentTest.java:

package com.maven;import org.junit.Test;public class StudentTest {@Testpublic void test1() {Student stu = new Student(101, "张三", "男", 22);System.out.println(stu);}}

    7、代码编写后,右击项目Run As中可选择需要执行的Maven命令。

 执行Maven Test:

     注意:我们发现eclipse中并没有Maven package命令,我们可以通过Maven build在eclipse中会调起命令配置界面,在这个插件上让你自己去配置执行目标的。

6.3.2 创建Maven Web项目

    1、按照之前的步骤,在选择Maven archetype(原型)的时候选择选择maven-archetype-webapp,创建Maven Web项目。

 建立Maven Web项目后目录结构如下:

    在搭建好Maven项目之后,项目上会出现一个小红叉,通过观察这个JSP文件中报错位置的提示信息我们发现,实际上这是因为我们的项目中没有导入Java EE的相关JAR文件导致的。
    
    在接下来的操作中,我们有两种选择:
        1.1 通过Build Path手动导入Java EE相关Jar包。
        1.2 通过Maven的pom.xml文件配置这些Jar包的相关依赖。

    我们使用的可是Maven项目!手动导入Jar包,多丢人啊!妥妥的选择使用Maven配置依赖啊!

    2、需要手动创建src/main/java,src/test/java,src/test/resources等源码目录。
    
    注意:src/main/webapp为存放web的根目录,类似于WebContent目录。但其中的index.jsp和WEB-INF目录下的web.xml因版本过早,需要删除重建。

     3、通过Maven的pom.xml文件配置这些Jar包的相关依赖。
        打开:https://mvnrepository.com,并搜索javaee关键字,选择使用最多的一个选项:

     接下来选择基于Java 8版本的Java EE类库:

     将其中的依赖声明代码复制到项目的pom.xml文件中:

或者单独配置:

    javax.servlet.jsp    jsp-api    2.1    provided    javax.servlet    javax.servlet-api    3.0.1    provided    javax.servlet    jstl    1.2

本地tomcat部署Maven Web项目:

     https://www.cnblogs.com/noyitela/p/10386455.html

Maven命令部署Maven Web项目:

    在pom.xml中添加tomcat7-maven-plugin的插件。
    
    注意:如果你的版本是tomcat7-maven-plugin 2.0 的话,由于它不支持 jdk 1.8,所以把它换成 tomcat7-maven-plugin 2.2就行了。

    maven-web-eclipse          org.apache.tomcat.maven     tomcat7-maven-plugin     2.2     

    这样就配置好了,记得把Goals改成tomcat7:run。

6.4 执行Maven命令报错的解决方案

执行Maven命令报错:

    错误原因:eclipse的执行环境默认是jre,Maven的执行环境是需要JDK,而不是jre。

    1、点击Window -> Preferences -> Java -> Installed JRES -> Add -> Standard VM -> 选择自己安装的JDK。

     2、右击当前的项目Properties -> Java Build Path -> Libraries -> Add Library -> JRE System Library -> 勾选Workspace default JRE。

     其他方案:https://blog.csdn.net/weixin_44405634/article/details/100837978

七、在IDEA中使用Maven

7.1 IDEA自带Maven插件,无需安装

7.2 在IDEA中配置Maven

    1、打开IDEA,点击菜单File -> Settings,找到Build,Execution,Deployment -> Build Tools -> Maven,修改默认Maven。

    注意:如果自带的Maven版本是3.3+,可以省略此步骤,使用默认的即可。当然也可以使用自己安装的Maven。

    2、在Build,Execution,Deployment -> Build Tools -> Maven -> Importing中配置自动下载 jar 包的源码、文档和注解。

    注意:如果勾选下面两项,则在下载依赖的时候,依赖对应的源码和文档都会一起下载(个人不推荐勾选)。

7.3 在IDEA中创建Maven项目

7.3.1 创建Maven Java项目

    1、点击File -> New -> Project,找到Maven,勾选Create from archetype,选择需要的原型,quickstart建立Java项目,webapp建立JavaWeb项目。

     2、填写maven坐标,group ID:公司名,artifactID:项目名,version:版本号,下一步直到完成。

     3、Maven会自动下载项目所需依赖包,完成后重构项目结构。

    4、按照Maven约定补全项目结构。
        在main目录下面创建resources文件夹,Mark Directory as "Resources Root"。
        在test目录下面创建resources文件夹,Mark Directory as "Test Resources Root"。

    注意:新版本的IDEA,在我们创建resources,会有相应的提示。

    5、删除自动生成App.java和AppTest.java,并编写项目代码即可。

    6、代码编写后,IDEA右侧的Maven Projects折叠窗口,选择Lifecycle下的命令,点击Run按钮(双击命令)运行。

    注意:如果右侧没有Maven Projects折叠窗口,点击View -> Appearance -> 勾选Tool Window Bars。

7.3.2 修改Maven默认的JDK编译环境

    Maven创建的项目默认的JDK编译环境是1.5,可以在pom中使用Maven的编译插件或通过全局配置改变项目的JDK版本。如果不指明版本就容易出现版本不匹配的问题,可能导致编译不通过的问题。

方式一:修改项目级别pom.xml

    UTF-8    1.8    1.8

    以上配置只针对当前项目(模块)生效。

方式二:修改maven的settings.xml文件(全局)

 jdk-1.8      true     1.8       1.8     1.8     1.8      

    以上配置为全局级别,即基于当前maven环境下创建的任何项目(模块)均生效。

7.3.3 创建Maven Web项目

    1、按照之前的步骤创建Maven Web项目。

 创建Maven web项目后目录结构如下:

    2、手动创建src/main/java,src/mian/resources,src/test/java,src/test/resources等源码目录,先建立Directory(普通目录)。

    注意:新版本IDEA在创建指定名称目录的时候,会直接提示,非常好用。

     3、右击目录,选择Mark Directory as,将该目录转换为对应的源码目录。其中Sources Root对应src/main/java目录,Test Sources Root对应src/test/java目录,Resources Root对应src/main/resources目录,Test Resources Root对应src/test/resources目录

     4、src/main/webapp为存放web的根目录,类似于WebContent目录。但其中的index.jsp和WEB-INF目录下的web.xml因版本过早,需要删除重建。

jsp模板:

    Title    

Hello Maven!

web.xml模板:

     index.jsp    

    5、部署和测试。
        本地tomcat部署Maven Web项目。
        Maven命令部署Maven Web项目。

八、Maven依赖范围

    基于之前我们创建的Maven Web项目,添加Web开发中常用的依赖。
        1、mysql驱动
        2、servlet
        3、jsp
        4、jstl
        5、gson等

目录结构如下:

Student类代码如下:

package com.maven.entity;/** * 学生实体类 */public class Student {    private Integer id;    private String name;    private String gender;    private double score;    public Student(){}    public Student(Integer id, String name, String gender, double score) { this.id = id; this.name = name; this.gender = gender; this.score = score;    }    public Integer getId() { return id;    }    public void setId(Integer id) { this.id = id;    }    public String getName() { return name;    }    public void setName(String name) { this.name = name;    }    public String getGender() { return gender;    }    public void setGender(String gender) { this.gender = gender;    }    public double getScore() { return score;    }    public void setScore(double score) { this.score = score;    }    @Override    public String toString() { return "Student{" +  "id=" + id +  ", name='" + name + '\'' +  ", gender='" + gender + '\'' +  ", score=" + score +  '}';    }}

GsonServlet类代码如下:

package com.maven.servlet;import com.google.gson.Gson;import com.maven.entity.Student;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.ArrayList;import java.util.List;/** * 测试Gson的Servlet */@WebServlet("/gsonServlet")public class GsonServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //创建Gson对象(如果没有引入gson的jar包或依赖,肯定会报错) Gson gson = new Gson(); //创建集合来存储学生对象 List studentList = new ArrayList(); studentList.add(new Student(101,"Tom","boy",89)); studentList.add(new Student(102,"Jerry","boy",56.2)); studentList.add(new Student(103,"HanMeiMei","girl",44)); studentList.add(new Student(104,"LiLei","boy",100)); //把studentList转换为json字符串响应出去 String json = gson.toJson(studentList); resp.getWriter().write(json);    }}

StudentTest类代码如下:

package com.maven.entity;import org.junit.Test;/** * 学生类单元测试 */public class StudentTest {    @Test    public void test1() { Student student = new Student(101, "张三", "男", 12); System.out.println(student);    }}

pom.xml文件依赖如下:

         junit junit 4.12 test             mysql mysql-connector-java 5.1.49             javax.servlet.jsp jsp-api 2.1 provided             javax.servlet javax.servlet-api 3.0.1 provided             javax.servlet jstl 1.2             com.google.code.gson gson 2.8.5    

    实体类、servlet、测试类代码都没什么要解释的,但是我们在引入依赖的时候会发现,有的依赖有scope标签,有的没有。

    标签就是控制 dependency 元素的使用范围。通俗的讲,就是控制 Jar 包在哪些范围被加载和使用。

8.1 依赖范围概述

三种classpath的理解:

    1、编译classpath:对应target目录下的classes目录,仅存放src/main目录下编译之后的内容。
        src/main/java:编译之后生成的字节码文件放到classes目录(带包结构)。
        src/main/resources:不会进行编译,直接放到classes目录。

    2、测试classpath:对应target目录下的test-classes目录,仅存放src/test目录下编译之后的内容。
        src/test/java:编译之后生成的字节码文件放到test-classes目录(带包结构)。
        src/test/resources:不会进行编译,直接放到test-classes目录。
    
    3、运行classpath:项目运行时的字节码文件存放目录。例如Web项目/WEB-INF/classes目录。
    
    注意:
        1、resources目录下面不能创建包,只能创建文件夹,如果我们创建文件夹com.maven.servlet,只是创建了一个文件夹叫com.maven.servlet。
        2、resources目录下面可以定义和main目录下面相同的目录结构。
        3、resources目录下面不能定义和main目录下面相同的文件。
        4、resources目录下面的Java源代码不会被编译,也不推荐把Java源代码放到resources目录。

     Maven项目不同的阶段引入到classpath中的依赖是不同的。
        编译时,Maven会将与编译相关的依赖引入classpath中。
        测试时,Maven会将测试相关的的依赖引入到classpath中。
        运行时,Maven会将与运行相关的依赖引入classpath中。
    
    而依赖范围就是用来控制依赖在编译classpath,测试classpath,运行classpath这三种classpath的使用。

8.2 依赖范围的取值

    Maven的生命周期存在编译、测试、运行这些过程,那么显然有些依赖只用于测试,比如junit;有些依赖编译用不到,只有运行的时候才能用到,比如mysql的驱动包在编译期就用不到(编译期用的是JDBC接口),而是在运行时用到的;还有些依赖,编译期要用到,而运行期不需要提供,因为有些容器已经提供了,比如servlet-api在tomcat中已经提供了,我们只需要的是编译期提供而已。总结说来,在POM 4中,中还引入了,它主要管理依赖的部署。大致有compile、provided、runtime、test、system等几个。
        
    compile:编译依赖范围。
    test:测试依赖范围。
    provided:已提供依赖范围。
    runtime:运行时依赖范围。(接口与实现分离)
    system:系统依赖范围。非本地仓库引入、存在系统的某个路径下的jar。(一般不使用)
    import:导入依赖范围。使用dependencyManagement时候,可以导入依赖配置。(后期讲解)

依赖范围 编译classpath有效 测试classpath有效 运行classpath有效 例子
compile Y Y Y spring-croe
test N Y N junit
provided Y Y N servlet-api
runtime N Y Y jdbc

compile(默认)

    含义:compile 是默认值,如果没有指定 scope 值,该元素的默认值为 compile。被依赖项目需要参与到当前项目的编译,测试,打包,运行等阶段。打包的时候通常会包含被依赖项目。

    演示:gson的scope值就是默认的compile,如果改为test,那么在编译期就找不到gson了,报错。

provided

    含义:被依赖项目理论上可以参与编译、测试、运行等阶段,相当于compile,但是再打包阶段做了exclude的动作。 

    适用场景:例如,如果我们在开发一个web 应用,在编译时我们需要依赖 servlet-api.jar,但是在运行时我们不需要该 jar 包,因为这个 jar 包已由应用服务器提供,此时我们需要使用 provided 进行范围修饰。

    演示:把gson的scope值改为provided,编译和测试都没有问题,但是运行时报错,因为应用服务器没有提供gson的jar包。

runtime

    含义:表示被依赖项目无需参与项目的编译,但是会参与到项目的测试和运行。与compile相比,被依赖项目无需参与项目的编译。

    适用场景:例如,在编译的时候我们不需要 JDBC API 的 jar 包,而在运行的时候我们才需要 JDBC 驱动包。

test

    含义:表示被依赖项目仅仅参与测试相关的工作,包括测试代码的编译,执行。

    适用场景:例如,Junit 测试。

    演示:在src/main/java中无法使用单元测试。

system

    含义:system 元素与 provided 元素类似,但是被依赖项不会从 maven 仓库中查找,而是从本地系统中获取,systemPath 元素用于制定本地系统中 jar 文件的路径。例如:

    org.open    open-core    1.5    system    ${basedir}/WebContent/WEB-INF/lib/open-core.jar

import(了解)

    它只使用在中,表示从其它的pom中导入dependency的配置,例如(B项目导入A项目中的包配置):

    想必大家在做SpringBoot应用的时候,都会有如下代码:

    org.springframework.boot    spring-boot-starter-parent    1.3.3.RELEASE

    继承一个父模块,然后再引入相应的依赖。

    假如说,我不想继承,或者我想继承多个,怎么做?我们知道Maven的继承和Java的继承一样,是无法实现多重继承的,如果10个、20个甚至更多模块继承自同一个模块,那么按照我们之前的做法,这个父模块的dependencyManagement会包含大量的依赖。如果你想把这些依赖分类以更清晰的管理,那就不可能了,import scope依赖能解决这个问题。你可以把dependencyManagement放到单独的专门用来管理依赖的pom中,然后在需要使用依赖的模块中通过import scope依赖,就可以引入dependencyManagement。例如可以写这样一个用于依赖管理的pom:

    4.0.0    com.test.sample    base-parent1    pom    1.0.0-SNAPSHOT            junit  junit  4.8.2            log4j  log4j  1.2.16          

然后我就可以通过非继承的方式来引入这段依赖管理配置:

          com.test.sample     base-parent1     1.0.0-SNAPSHOT     pom     import          junit    junit    log4j    log4j

注意:import scope只能用在dependencyManagement里面;

    这样,父模块的pom就会非常干净,由专门的packaging为pom来管理依赖,也契合的面向对象设计中的单一职责原则。此外,我们还能够创建多个这样的依赖管理pom,以更细化的方式管理依赖。这种做法与面向对象设计中使用组合而非继承也有点相似的味道。

    那么,如何用这个方法来解决SpringBoot的那个继承问题呢?

          org.springframework.boot     spring-boot-dependencies     1.3.3.RELEASE     pom     import          org.springframework.boot spring-boot-starter-web    

    这样配置的话,自己的项目里面就不需要继承SpringBoot的module了,而可以继承自己项目的module了。

九、Maven依赖传递

9.1 依赖传递

大家要理解一个问题:Maven仓库中的所有jar,其实本质上都是一个Java项目,只是打成jar包放到Maven仓库中而已,既然是Java项目,那么这个项目可能也会用到一些第三方的jar包。

当我们引入某些jar包的时候,会把这些jar包依赖的jar包同样引入进来,这就是依赖传递。

    例如有个Commons-logging项目,项目Spring-core依赖Commons-logging,而项目user-service依赖Spring-core。那么我们可以说 user-service也依赖Commons-logging。也就是说,依赖的关系为:user-service—>Spring-core—>Commons-logging

    当我们执行项目user-service时,会自动把Spring-core、Commons-logging都下载导入到user-service项目的jar包文件夹中,这就是依赖的传递性。

dependency完整结构:

     组织/父项目名称 项目名称 项目版本号 依赖的类型,对应项目的packaging,默认是jar 依赖范围 配合 scope=system 时使用 标记是否为可选依赖             组织/父项目名称 项目名称          

    通过设置true,表示该依赖是可选的,不会被依赖传递。

举例验证:

    引入依赖spring-core。

    org.springframework    spring-core    4.3.18.RELEASE

结果如下:

    仔细观察,还发现此时还多了commons-logging:commons-logging:1.2,这就涉及到了“依赖”。

    当A jar包需要用到B jar包中的类时,我们就说A对B有依赖。当前工程会到本地仓库中根据坐标查找它所依赖的jar包。

    我们通过Maven仓库可以发现,spring-core编译的时候依赖了5个jar,但是最终依赖传递进来的只有一个,原因在于配置了optinal标签。

    注意:optional标签只能在自己的项目中设置不向下传递(如果我们把项目打成jar包,我们项目的中的某些依赖不向下传递),如果不想使用要别人项目传递进来的依赖,可以使用依赖传递的排除。

9.2 依赖传递的排除

    有时候为了确保程序正确,可以将有可能重复的间接依赖排除。

    依赖传递的排除的排除分为两种:
        1、通过配置文件排除依赖。
        2、Maven自动排除重复依赖。

 例如:C -> B -> A,假如现在不想执行C时把A下载进来,那么我们可以用标签

     B B 0.0.1          A  A          

    我们一般情况下,不要自己去写依赖排除,除非某些特殊情况。

    假如:junit依赖的hamcrest-core:1.3有版本问题,我们不想要junit依赖传递进来的hamcrest-core:1.3,那么我们就可以通过exclusion排除掉。

    junit    junit    4.12    test          org.hamcrest     hamcrest-core     

注意:要跟原来引出依赖的原包处于同一个范围。

    依赖排除hamcrest-core之后,junit肯定就出现了问题,那么我们需要找一个没有版本问题的hamcrest-core再引入进来即可。

    org.hamcrest    hamcrest-core    2.2

依赖排除的场景:

    1、项目A依赖项目B,但当项目A不是完全依赖项目B的时候,即项目A只用到了项目B的一部分功能,而正巧项目B这部分功能的实现,并不需要依赖于项目C,这个时候,项目A就应该排除对项目C的依赖。

    2、版本不匹配:依赖传递进来的jar包与实际用到的jar有版本不匹配问题,造成项目运行异常。

    3、封装公共模块:使用到某个jar中的API,如果此jar中有不需要的传递依赖,可以通过排除依赖传递。

9.3 依赖冲突与解决

    依赖冲突:一个项目A,通过不同依赖传递路径依赖于X,若在不同路径下传递过来的X版本不同,那么A应该导入哪个版本的X包呢?

    由于依赖的内容存在多个版本,如果出现某一个POM依赖多个版本时,则称之为依赖冲突。

    依赖冲突遵循两个原则:
        1、短路优先(依赖的内容,传递次数越小越优先)。
        2、先声明则优先(在POM.xml中,哪个依赖的内容声明dependency靠前,则优先。

举例验证:

    引入如下依赖:

    org.springframework    spring-core    4.3.18.RELEASE    org.springframework    spring-beans    4.3.18.RELEASE    org.springframework    spring-context    4.3.18.RELEASE

    我们会发现,我们引入了spring-core,但是beans、context以及context里面的aop等都依赖传递了spring-core。那么项目中会引入多个jar吗? 

    答案是否定的。实际上Maven只引入了一个spring-core。

 冲突解决方案:

    1、如果依赖路径的长度不同,则采取最短路径原则:
        A—>B—>C—>D—>E—>X(version 0.0.2)
        A—>F—>X(version 0.0.1)
        则A依赖于X(version 0.0.1)。

    2、依赖路径长度相同情况下,则采取最先申明原则:
        A—>E—>X(version 0.0.1)
        A—>F—>X(version 0.0.2)
        则在项目A的中,E、F哪个先声明则A依赖哪条路径的X。

十、Maven生命周期

Maven的生命周期就是对所有的构建过程进行抽象和统一。包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有的构建步骤。

Maven的生命周期是抽象的,即生命周期不做任何实际的工作,实际任务由插件完成,类似于设计模式中的模板方法。

10.1 三个生命周期

    Maven有三套相互独立的生命周期,分别是clean、default和site。
        1、clean:Clean Lifecycle在进行真正的构建之前进行一些清理工作。

        2、default:Default Lifecycle构建核心部分,如编译,测试,打包,部署等等。

        3、site:Site Lifecycle生成项目报告,站点,发布站点。

    每个生命周期包含一些阶段,这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段,用户和Maven最直接的交互方式就调用这些生命周期阶段。

    较之于生命周期阶段的前后依赖关系,三套生命周期本身是相互独立的,用户可以仅仅调用clean生命周期的某个阶段,或者仅仅调用default生命周期的某个阶段,而不会对其他生命周期产生任何影响。

10.2 clean生命周期

    clean 生命周期,它包含以下阶段:
        1、pre-clean:执行一些需要在clean之前完成的工作。

        2、clean:移除所有上一次构建生成的文件。

        3、post-clean:执行一些需要在clean之后立刻完成的工作。

    mvn clean 中的 clean 就是上面的 clean,在一个生命周期中,运行某个阶段的时候,它之前的所有阶段都会被运行,也就是说,如果执行 mvn clean 将运行以下两个生命周期阶段:
        pre-clean, clean
        
    如果我们运行 mvn post-clean ,则运行以下三个生命周期阶段:
        pre-clean, clean, post-clean
        
    这是Maven很重要的一个规则,可以大大简化命令行的输入。

10.3 default生命周期

这是 Maven 的主要生命周期,被用于构建应用,包括下面的 23 个阶段:

生命周期阶段 描述
validate(校验) 校验项目是否正确并且所有必要的信息可以完成项目的构建过程。
initialize(初始化) 初始化构建状态,比如设置属性值。
generate-sources(生成源代码) 生成包含在编译阶段中的任何源代码。
process-sources(处理源代码) 处理源代码,比如说,过滤任意值。
generate-resources(生成资源文件) 生成将会包含在项目包中的资源文件。
process-resources (处理资源文件) 复制和处理资源到目标目录,为打包阶段最好准备。
compile(编译) 编译项目的源代码。
process-classes(处理类文件) 处理编译生成的文件,比如说对Java class文件做字节码改善优化。
generate-test-sources(生成测试源代码) 生成包含在编译阶段中的任何测试源代码。
process-test-sources(处理测试源代码) 处理测试源代码,比如说,过滤任意值。
generate-test-resources(生成测试资源文件) 为测试创建资源文件。
process-test-resources(处理测试资源文件) 复制和处理测试资源到目标目录。
test-compile(编译测试源码) 编译测试源代码到测试目标目录.
process-test-classes(处理测试类文件) 处理测试源码编译生成的文件。
test(测试) 使用合适的单元测试框架运行测试(Juint是其中之一)。
prepare-package(准备打包) 在实际打包之前,执行任何的必要的操作为打包做准备。
package(打包) 将编译后的代码打包成可分发格式的文件,比如JAR、WAR或者EAR文件。
pre-integration-test(集成测试前) 在执行集成测试前进行必要的动作。比如说,搭建需要的环境。
integration-test(集成测试) 处理和部署项目到可以运行集成测试环境中。
post-integration-test(集成测试后) 在执行集成测试完成后进行必要的动作。比如说,清理集成测试环境。
verify (验证) 运行任意的检查来验证项目包有效且达到质量标准。
install(安装) 安装项目包到本地仓库,这样项目包可以用作其他本地项目的依赖。
deploy(部署) 将最终的项目包复制到远程仓库中与其他开发者和项目共享。

重要的阶段如下:

阶段 处理 描述
验证 validate 验证项目 验证项目是否正确和所有需要的相关资源是否可用,这包含对pom.xml文件树的验证。
编译 compile 执行编译 源代码编译在此阶段完成。
测试 Test 测试 使用适当的单元测试框架(例如JUnit)运行测试。这些测试不需要打包和布署。
包装 package 打包 创建JAR/WAR包如在 pom.xml 中定义提及的包。
检查 verify 检查 运行所有检查,验证包是否有效且达到质量标准。
安装 install 安装 安装打包的项目到本地仓库,以供其他项目使用。
部署 deploy 部署 拷贝最终的工程包到远程仓库中,以共享给其他开发人员和工程。

    运行任何一个阶段的时候,它前面的所有阶段都会被运行,这也就是为什么我们运行mvn install 的时候,代码会被编译,测试,打包。此外,Maven的插件机制是完全依赖Maven的生命周期的,因此理解生命周期至关重要。

实验:

    把一个Maven Java项目打成jar包,在另外一个Maven项目中引入。

    1、对Maven Java项目执行install,会把Maven Java打成jar包,并安装到本地仓库。

    2、通过项目坐标引入安装到本地仓库的jar。

注意:

    如果两个项目都是Project级别的,想要相互依赖,那么必须打包之后通过依赖引入。但是如果两个项目的Module级别的,那么可以直接引入,我们在后面部分进行讲解。

同时执行两个生命周期阶段:

    mvn clean install

    Maven 会先执行 clean 命令,然后再执行 install 命令。

10.4 site生命周期

    site生命周期的目的是建立和发布项目站点,Maven能够基于POM所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息。该生命周期包含如下阶段:
        1、pre-site:执行一些需要在生成站点文档之前完成的工作。

        2、site:生成项目的站点文档。

        3、post-site: 执行一些需要在生成站点文档之后完成的工作,并且为部署做准备。

        4、site-deploy:将生成的站点文档部署到特定的服务器上。

十一、Maven项目拆分、继承、聚合

11.1 传统项目开发的问题

    使用Java技术开发的工程项目,无论是数据处理系统还是Web网站,随着项目的不断发展,需求的不断细化与添加,工程项目中的代码越来越多,包结构也越来越复杂这时候工程的进展就会遇到各种问题:

    1、传统的项目中,会将全部的业务都写到一个项目中,用一台Web应用服务器来部署。此时如果某一个业务操作造成服务器宕机,则全部的项目业务都不能访问。

    2、不同方面的代码之间相互耦合,这时候一系统出现问题很难定位到问题的出现原因,即使定位到问题也很难修正问题,可能在修正问题的时候引入更多的问题。

    3、多方面的代码集中在一个整体结构中,新入的开发者很难对整体项目有直观的感受,增加了新手介入开发的成本,需要有一个熟悉整个项目的开发者维护整个项目的结构(通常在项目较大且开发时间较长时这是很难做到的)。

    4、开发者对自己或者他人负责的代码边界很模糊,这是复杂项目中最容易遇到的,导致的结果就是开发者很容易修改了他人负责的代码且代码负责人还不知道,责任追踪很麻烦。

    将一个复杂项目拆分成多个模块是解决上述问题的一个重要方法,多模块的划分可以降低代码之间的耦合性(从类级别的耦合提升到jar包级别的耦合),每个模块都可以是自解释的(通过模块名或者模块文档),模块还规范了代码边界的划分,开发者很容易通过模块确定自己所负责的内容。

11.2 Maven项目拆分

    不知你有没有想过,一个好好的maven工程为什么要进行拆分呢?面对当今互联网+的行业,软件项目变得越来越庞大,复杂程度越来越高,这大大地提高了开发与管理的成本。而工程的拆分可以实现分模块开发与测试,亦可实现多线程开发与管理,在提高工程代码复用度的同时也提高了软件的开发速度与效率。

    例如,一个完整的早期开发好的crm项目,现在要使用maven工程对它进行拆分,这时候就可以将dao层拆解出来,形成一个独立的工程,同样service层以及web层也都进行这样的拆分。
    
    概念讲解和配图原文链接:https://blog.csdn.net/yerenyuan_pku/article/details/103680220

 理解工程拆分的好处:

    从上图可以看出,出现了一个问题,如果crm项目中dao层一旦出现bug需要修复时,erp项目与oa项目中的dao层也要做相应的修改,像这样重复的事情需要做三遍!实在是不可取,那怎么解决呢?

    这时,就可以将crm项目中的dao层拆解出来了,并形成一个独立的工程,然后每个项目都来复用这个独立的工程。

     把工程拆分成一个个独立的工程,将来要用到的时候就把它们的坐标给引进来就行了,这就有点类似于搭积木一样。

     把积木搭建成各种项目:

    对于一个大型的项目,如果我们直接作为一个工程开发,由于相互之间的依赖我们只能从头到尾由一组人开发,否则就会出现一个类好多人开发,相互更改的混乱局面,这个时候我们就将项目进行了横向和纵向的拆分。

    所谓的横向的拆分就是我们平常说的三层架构,将项目分成了web层,service层、dao层(web层也被叫做表现层,service层也被叫做业务层,dao层也被持久层),可以理解为将一个功能模块的不同调用过程进行了水平方向的拆分。

    所谓的纵向拆分就是将一个项目的多个功能模块进行了拆分,横向拆分后,每个功能模块进行了单独的开发之后,项目整合的时候就需要有一个能够整合这些项目或者模块的工程,这就是所谓聚合工程的意义。

11.3 Maven项目聚合

    项目开发通常是分组分模块开发的,每个模块开发完成后,要运行整个工程需要将每个模块聚合在一起运行,比如,dao、service以及web这三个工程最终会打一个独立的war包运行。

    就拿一个完整的早期开发好的crm项目来说,把crm项目拆成多个子模块后,独立运行各个模块是无法完成软件项目的要求的,只有把它们都整合起来,分工合作才能完成工作。因此需要父工程来管理各个子模块,把它们聚合在一起运行,即把crm_dao、crm_service以及crm_web这三个工程打成一个独立的可运行的war包。

     这有点像把汽车的各个零部件组装起来,变成一辆可以行驶的车。以下是一堆的汽车各个零部件。

     将汽车的各个零部件组装起来,可以变成一辆可以行驶的车。

     建立聚合工程需要注意:
        1、该聚合项目本身也做为一个Maven项目,它必须有自己的POM。
        2、它的打包方式必须为:pom。
        3、引入了新的元素:modules---module。
        4、版本:聚合模块的版本和被聚合模块版本一致。
        5、relativePath:每个module的值都是一个当前POM的相对目录。
            指定查找该父项目pom.xml的(相对)路径。默认顺序:relativePath > 本地仓库 > 远程仓库。
            没有relativePath标签等同…/pom.xml, 即默认从当前pom文件的上一级目录找。
        6、目录名称:为了方便的快速定位内容,模块所处的目录应当与其artifactId一致(Maven约定而不是硬性要求),总之,模块所处的目录必须和模块所处的目录相一致。
        7、聚合模块减少的内容:聚合模块的内容仅仅是一个pom.xml文件,它不包含src/main/Java、src/test/java等目录,因为它只是用来帮助其它模块构建的工具,本身并没有实质的内容。
        8、聚合模块和子模块的目录:他们可以是父子类,也可以是平行结构,当然如果使用平行结构,那么聚合模块的POM也需要做出相应的更改。
        9、如果聚合项目的子模块新建完成后进行了删除操作,一定要在聚合项目中pom.xml中的modules选项卡中将这个子模块进行删除

    ../maven-util    ../maven-entity    ../maven-dao    ../maven-service    ../maven-web

总结:

对于聚合模块来说,它知道有哪些被聚合的模块,而对于被聚合的模块来说,它们不知道被谁聚合了,也不知道它的存在对于继承关系的父POM来说,它不知道自己被哪些子模块继承了,对于子POM来说,它必须知道自己的父POM是谁在一些最佳实践中我们会发现:一个POM既是聚合POM,又是父POM,这么做主要是为了方便。

11.4 Maven项目的继承

    类似Java中类的继承,都是为了消除重复。子类继承父类,父类里有的方法和属性在子类中就不需要再定义和实现了,使用的时候直接调用父类的就可以了。我们把crm项目拆分后,将会有一个父工程(例如crm)和若干个子工程(例如crm_dao、crm_service、crm_web),子工程中要用到的依赖都可以在父工程的pom.xml文件中进行依赖管理,将来子工程在开发的时候就不需要再定义版本了,这样做的目的是为了方便管理。

    继承除了能够避免重复,还有一个好处就是让项目更加安全。

    继承时需要注意:
        1、说到继承肯定是一个父子结构,那么我们在聚合项目中来创建一个parent project。
        2、: 作为父模块的POM,其打包类型也必须为POM。
        3、结构:父模块只是为了帮助我们消除重复,所以它也不需要src/main/java、src/test/java等目录。
        4、新的元素:,它是被用在子模块中的。
        5.元素的属性:: 表示父模块POM的相对路径,在构建的时候,Maven会先根据relativePath检查父POM,如果找不到,再从本地仓库查找。
        6、relativePath的默认值:../pom.xml。
        7、子模块省略groupId和version: 使用了继承的子模块中可以不声明groupId和version, 子模块将隐式的继承父模块的这两个元素。

十二、Maven综合案例

    对于一个大型的项目,大部分情况下都是将一个项目的多个功能模块进行了拆分,拆分后,每个功能模块进行了单独的开发之后,项目整合的时候就需要有一个能够整合这些项目或者模块的工程,这就是所谓聚合工程的意义。

    我们现在不写那么多模块,把一个学生模块按照类的功能(三层架构)拆分成一个个模块,然后来体会项目的拆分、继承、聚合。

12.1 搭建项目结构

12.1.1 创建管理父项目

    1、创建管理父项目(maven-student),Maven管理类项目的打包方式为pom。管理类父项目只是用来帮助其它模块构建的工具,本身并没有实质的内容,可以删除src目录。

     2、创建完成之后,做如下配置:
        2.1 修改项目打包方式为pom。
        2.2 依赖版本管理。
        2.3 依赖管理

    4.0.0    com.maven    maven-student    1.0-SNAPSHOT        pom          3.0.1 2.1 1.2 4.12 5.1.49                        javax.servlet  javax.servlet-api    ${servlet.version}  provided            javax.servlet.jsp  jsp-api  ${jsp.version}  provided            javax.servlet  jstl  ${jstl.version}            junit  junit  ${junit.version}  test            mysql  mysql-connector-java  ${mysql.version}          

12.1.2 创建工具类子项目

    1、创建工具类子项目(maven-student-util)。

    注意:创建子项目的时候,创建的是一个Module。父项目右键 -> New -> Module,Module也是Maven项目。

     2、maven-student-util子模块的pom.xml文件里面会自动添加parent标签,标识当前模块的父项目或父模块。并且父项目的pom.xml文件中会自动添加modules,标识聚合的模块。

    maven-student    com.maven    1.0-SNAPSHOT
    maven-student-util    maven-student-entity

    3、引入当前模块需要用到的依赖。

         maven-student com.maven 1.0-SNAPSHOT        4.0.0        maven-student-util               mysql     mysql-connector-java       junit     junit     test     

12.1.3 创建实体类子项目

    创建实体类子项目(maven-student-entity),并引入当前模块需要用到的依赖。

     maven-student com.maven 1.0-SNAPSHOT        4.0.0    maven-student-entity              junit     junit     test     

12.1.4 创建持久层子项目

    创建持久层子项目(maven-student-dao),并引入当前模块需要用到的依赖。

     maven-student com.maven 1.0-SNAPSHOT        4.0.0    maven-student-dao              junit     junit     test         com.maven     maven-student-util       com.maven     maven-student-entity     

    在父项目中对自己的模块进行依赖管理,子模块中就不需要定义版本。

    com.maven    maven-student-util        ${project.version}    com.maven    maven-student-entity    ${project.version}

12.1.5 创建业务层子项目

    创建业务层子项目(maven-student-service),并引入当前模块需要用到的依赖。

     maven-student com.maven 1.0-SNAPSHOT        4.0.0    maven-student-service          com.maven     maven-student-dao     

12.1.6 创建表现层子项目

    1、创建表现层子项目(maven-student-web),修改项目打包方式为war。

    2、引入当前模块需要用到的依赖

     maven-student com.maven 1.0-SNAPSHOT        4.0.0    maven-student-web    war          com.maven     maven-student-service       javax.servlet     javax.servlet-api     provided       javax.servlet.jsp     jsp-api     provided       javax.servlet     jstl     

最后,父项目的pom.xml文件的内容:

    4.0.0    com.maven    maven-student    1.0-SNAPSHOT         maven-student-util maven-student-entity maven-student-dao maven-student-service maven-student-web            pom          3.0.1 2.1 1.2 4.12 5.1.38                        javax.servlet  javax.servlet-api    ${servlet.version}  provided            javax.servlet.jsp  jsp-api  ${jsp.version}  provided            javax.servlet  jstl  ${jstl.version}            junit  junit  ${junit.version}  test            mysql  mysql-connector-java  ${mysql.version}            com.maven  maven-student-util    ${project.version}            com.maven  maven-student-entity  ${project.version}            com.maven  maven-student-dao  ${project.version}            com.maven  maven-student-service  ${project.version}          

12.1.7 关于modules

    modules可以加,也可以不加。但是modules有一个好处,当定义modules之后,我们通过父项目执行生命周期,所有的子module都会执行对应的生命周期。

    注意:我们如果模块之间没有依赖,那么按照聚合配置的顺序来执行;如果有了依赖,肯定是被依赖的先构建。

通过父项目执行编译:

12.2 项目后端实现

12.2.1 创建学生表

    在MySQL数据库的test库下创建学生表,学生表字段如下:

12.2.2 DBUtils工具类实现

    在工具类子项目中创建com.maven.util包,创建DBUtils工具类。

DBUtils代码如下:

package com.maven.util;import java.sql.*;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;/* * JDBC工具类的高度封装 * */public class DBUtils {    /*     * private 为了不让外部调用     * static 因为要在static代码块或方法中使用     * final 不允许修改     * */    private static final String driverName = "com.mysql.jdbc.Driver";    private static final String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8";    private static final String userName = "root";    private static final String userPwd = "root";    private static Connection connection;    private static PreparedStatement preparedStatement;    private static ResultSet resultSet;    /*     * 注册数据库驱动,注册驱动只需要执行一次即可     *   static静态代码块,在类加载的时候有且仅执行一次     * */    static { try {     Class.forName(driverName); } catch (ClassNotFoundException e) {     System.out.println("数据库驱动加载失败!"); }    }    /*     * 获取连接     * */    public static Connection getConnection() { try {     connection = DriverManager.getConnection(url, userName, userPwd); } catch (SQLException throwables) {     System.out.println("数据库连接获取失败!"); } return connection;    }    /*     * 查询数据的方法     *   参数:     *String sql,要执行的sql语句     *List list,sql的参数     *   返回值:     *不能返回ResultSet,因为资源释放之后,结果集不能在操作     *把结果集里面的数据用另外一种形式返回即可 List<Map>     * */    public static List<Map> query(String sql, List list) { List<Map> resultList = new ArrayList(); // 每次执行查询,都要获取得到连接 getConnection(); // 获取执行SQL语句的PrepareStatement对象 try {     preparedStatement = connection.prepareStatement(sql);     // 给sql设置参数     for (int i = 0; i < list.size(); i++) {  // 因为不知道参数的类型,所以我们直接使用setObject(占位符索引,值); 占位符索引从1开始  preparedStatement.setObject(i + 1, list.get(i));     }     // 执行查询     resultSet = preparedStatement.executeQuery();     // 获取结果集对应的结构     ResultSetMetaData metaData = resultSet.getMetaData();     // 把resultSet转换为List<Map>     while (resultSet.next()) {  // resultSet里面每有一条数据,就创建一个Map集合  Map map = new HashMap();  // map里面的key是列名,value是列对应的值  // 结果集里面有多少列,就向map里面存储多少对值  for (int i = 1; i <= metaData.getColumnCount(); i++) {      map.put(metaData.getColumnName(i), resultSet.getString(i));  }  // 把map存储到list中  resultList.add(map);     } } catch (SQLException throwables) {     System.out.println("SQL语句异常"); } finally {     close(); } return resultList;    }    /*     * 增删改的方法     *   返回值: int类型,表示增、删、改的条目数     *   参数:     *String sql,要执行的sql语句     *List list,sql的参数     * */    public static int update(String sql, List list) { int count = 0; // 每次执行更改操作,都要获取得到连接 getConnection(); // 获取执行SQL语句的PrepareStatement对象 try {     preparedStatement = connection.prepareStatement(sql);     // 给sql设置参数     for (int i = 0; i < list.size(); i++) {  // 因为不知道参数的类型,所以我们直接使用setObject(占位符索引,值); 占位符索引从1开始  preparedStatement.setObject(i + 1, list.get(i));     }     count = preparedStatement.executeUpdate(); } catch (SQLException throwables) {     System.out.println("SQL语句异常"); } finally {     close(); } return count;    }    /*     * 释放资源     * */    public static void close() { if (resultSet != null) {     try {  resultSet.close();     } catch (SQLException throwables) {  System.out.println("结果集关闭失败");     } } if (preparedStatement != null) {     try {  preparedStatement.close();     } catch (SQLException throwables) {  System.out.println("Statement关闭失败");     } } if (connection != null) {     try {  connection.close();     } catch (SQLException throwables) {  System.out.println("连接关闭失败!");     } }    }}

12.2.3 学生实体类实现

    在实体类子项目中创建com.maven.entity包,创建StudentEntity实体类。

StudentEntity代码如下:

package com.maven.entity;/** * 学生实体类 */public class StudentEntity {    private Integer id;    private String name;    private String sex;    private String major;    public StudentEntity() {    }    public StudentEntity(Integer id, String name, String sex, String major) { this.id = id; this.name = name; this.sex = sex; this.major = major;    }    public Integer getId() { return id;    }    public void setId(Integer id) { this.id = id;    }    public String getName() { return name;    }    public void setName(String name) { this.name = name;    }    public String getSex() { return sex;    }    public void setSex(String sex) { this.sex = sex;    }    public String getMajor() { return major;    }    public void setMajor(String major) { this.major = major;    }    @Override    public String toString() { return "StudentEntity{" +  "id=" + id +  ", name='" + name + '\'' +  ", sex='" + sex + '\'' +  ", major='" + major + '\'' +  '}';    }}

12.2.4 学生持久层实现

    在持久层子项目中创建com.maven.dao包,创建StudentDao接口。

    在持久层子项目中创建com.maven.dao.impl包,创建StudentDaoImpl实现类。

    注:我们仅仅实现简单的一套CRUD即可。

StudentDao接口代码如下:

package com.maven.dao;import com.maven.entity.StudentEntity;import java.util.List;import java.util.Map;/* * 学生DAO接口 */public interface StudentDao {    /*     * 新增学生的方法     * @param studentEntity 包含新增学生的信息实体类     * @return int 实际插入的数量     */    public abstract int insert(StudentEntity studentEntity);    /*     * 删除学生的方法     * @param id 删除学生的id     * @return int 实际删除的数量     */    public abstract int delete(Integer id);    /*     * 修改学生的方法     * @param studentEntity 包含修改学生的信息实体类     * @return int     */    public abstract int update(StudentEntity studentEntity);    /*     * 根据id查询学生的方法     * @param id 查询学生的id     * @return StudentEntity 查询到的学生信息     */    public abstract List<Map> query(Integer id);    /*     * 查询所有学生的方法     * @return List 查询到的所有数据     */    public abstract List<Map> queryAll();}

StudentDaoImpl实现类代码如下:

package com.maven.dao.impl;import com.maven.dao.StudentDao;import com.maven.entity.StudentEntity;import com.maven.util.DBUtils;import java.util.ArrayList;import java.util.Arrays;import java.util.List;import java.util.Map;/** * 学生DAO接口实现类 */public class StudentDaoImpl implements StudentDao {    @Override    public int insert(StudentEntity studentEntity) { String sql = "insert into student(name,sex,major) values (?,?,?)"; List list = Arrays.asList(studentEntity.getName(), studentEntity.getSex(), studentEntity.getMajor()); return DBUtils.update(sql, list);    }    @Override    public int delete(Integer id) { String sql = "delete from student where id = ?"; List list = Arrays.asList(id); return DBUtils.update(sql, list);    }    @Override    public int update(StudentEntity studentEntity) { String sql = "update student set name = ?,sex=?,major=? where id = ?"; List list = Arrays.asList(studentEntity.getName(), studentEntity.getSex(), studentEntity.getMajor(),studentEntity.getId()); return DBUtils.update(sql, list);    }    @Override    public List<Map> query(Integer id) { String sql = "select * from student where id = ?"; List list = Arrays.asList(id); return DBUtils.query(sql,list);    }    @Override    public List<Map> queryAll() { String sql = "select * from student"; List list = new ArrayList(); return DBUtils.query(sql,list);    }}

12.2.5 学生业务层实现

    在业务层子项目中创建com.maven.service包,创建StudentService类。

StudentService类代码如下:

package com.maven.service;import com.maven.dao.StudentDao;import com.maven.dao.impl.StudentDaoImpl;import com.maven.entity.StudentEntity;import java.util.ArrayList;import java.util.List;import java.util.Map;/** * 学生的业务逻辑层 */public class StudentService {    //创建DAO实现类对象    private StudentDao studentDao = new StudentDaoImpl();    public int insert(StudentEntity studentEntity) { return studentDao.insert(studentEntity);    }    public int delete(Integer id) { return studentDao.delete(id);    }    public int update(StudentEntity studentEntity) { return studentDao.update(studentEntity);    }    public StudentEntity query(Integer id) { StudentEntity studentEntity = null; List<Map> query = studentDao.query(id); if (query.size() > 0) {     Map stringMap = query.get(0);     studentEntity = new StudentEntity();     studentEntity.setId(Integer.parseInt(stringMap.get("id")));     studentEntity.setName(stringMap.get("name"));     studentEntity.setSex(stringMap.get("sex"));     studentEntity.setMajor(stringMap.get("major")); } return studentEntity;    }    public List queryAll() { List list = new ArrayList(); List<Map> query = studentDao.queryAll(); for (int i = 0; i < query.size(); i++) {     Map stringMap = query.get(0);     StudentEntity studentEntity = new StudentEntity();     studentEntity.setId(Integer.parseInt(stringMap.get("id")));     studentEntity.setName(stringMap.get("name"));     studentEntity.setSex(stringMap.get("sex"));     studentEntity.setMajor(stringMap.get("major"));     list.add(studentEntity); } return list;    }}

12.2.6 学生表现层Servlet实现

查询所有学生的Servlet:

package com.maven.servlet;import com.maven.entity.StudentEntity;import com.maven.service.StudentService;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.List;/** * 查询所有学生的Servlet */@WebServlet("/student/list")public class StudentQueryAllServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp);    }    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); resp.setCharacterEncoding("utf-8"); resp.setContentType("text/html;charset=utf-8"); StudentService studentService = new StudentService(); List list = studentService.queryAll(); System.out.println(list); req.setAttribute("stuList", list); System.out.println(req.getServletContext().getContextPath()); //在请求转发中,/表示项目根目录 req.getRequestDispatcher("/student/list.jsp").forward(req, resp);    }}

根据id查询学生的Servlet:

package com.maven.servlet;import com.maven.entity.StudentEntity;import com.maven.service.StudentService;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * 根据学生id查询学生的Servlet */@WebServlet("/student/query")public class StudentQueryServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp);    }    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); resp.setCharacterEncoding("utf-8"); resp.setContentType("text/html;charset=utf-8"); //获取查询学生的id int id = Integer.parseInt(req.getParameter("id")); StudentService studentService = new StudentService(); StudentEntity studentEntity = studentService.query(id); req.setAttribute("editStudentInfo", studentEntity); req.getRequestDispatcher("/student/edit.jsp").forward(req, resp);    }}

添加学生的Servlet:

package com.maven.servlet;import com.maven.entity.StudentEntity;import com.maven.service.StudentService;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * 添加学生的Servlet */@WebServlet("/student/add")public class StudentAddServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp);    }    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); resp.setCharacterEncoding("utf-8"); resp.setContentType("text/html;charset=utf-8"); String name = req.getParameter("name"); String sex = req.getParameter("sex"); String major = req.getParameter("major"); StudentEntity studentEntity = new StudentEntity(null, name, sex, major); StudentService studentService = new StudentService(); int i = studentService.insert(studentEntity); if (i > 0) {     req.setAttribute("msg", "添加成功!"); } else {     req.setAttribute("msg", "添加失败!"); } req.getRequestDispatcher("/student/list").forward(req, resp);    }}

删除学生的Servlet:

package com.maven.servlet;import com.maven.entity.StudentEntity;import com.maven.service.StudentService;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * 删除学生的Servlet */@WebServlet("/student/delete")public class StudentDeleteServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp);    }    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); resp.setCharacterEncoding("utf-8"); resp.setContentType("text/html;charset=utf-8"); //获取删除学生的id int id = Integer.parseInt(req.getParameter("id")); System.out.println("删除学生的id:"+id); StudentService studentService = new StudentService(); int i = studentService.delete(id); if (i > 0) {     req.setAttribute("msg", "删除成功!"); } else {     req.setAttribute("msg", "删除失败!"); } req.getRequestDispatcher("/student/list").forward(req, resp);    }}

修改学生的Servlet:

package com.maven.servlet;import com.maven.entity.StudentEntity;import com.maven.service.StudentService;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * 修改学生的Servlet */@WebServlet("/student/update")public class StudentUpdateServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp);    }    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); resp.setCharacterEncoding("utf-8"); resp.setContentType("text/html;charset=utf-8"); int id = Integer.parseInt(req.getParameter("id")); String name = req.getParameter("name"); String sex = req.getParameter("sex"); String major = req.getParameter("major"); StudentEntity studentEntity = new StudentEntity(id, name, sex, major); System.out.println(studentEntity); StudentService studentService = new StudentService(); int i = studentService.update(studentEntity); if (i > 0) {     req.setAttribute("msg", "修改成功!"); } else {     req.setAttribute("msg", "修改失败!"); } req.getRequestDispatcher("/student/list").forward(req, resp);    }}

12.3 项目前端实现

目录结构如下:

12.3.1 项目首页实现

    Title    

这里是项目首页

查询所有学生信息

12.3.2 学生列表页实现

    Title     table, td, th {     border: 1px solid black;     border-collapse: collapse; } table {     width: 600px; } tr {     height: 40px; }        

所有学生的信息: ${msg}

学号 姓名 性别 专业 操作
${stu.id} ${stu.name} ${stu.sex} ${stu.major} 删除 修改

12.3.3 修改学生页面实现

    Title    

修改学生信息

学号:

姓名:

性别: 男 女 男 女

专业:

12.3.4 添加学生页面实现

    Title    

添加学生信息

姓名:

性别: 男 女

专业: