1.包管理工具
什么是包?
- jvm的工作被设计的相当简单,且枯燥:
- 执行一个类的字节码
- 假如这个过程中碰到了新的类,加载它
那么,去哪里加载这些类呢?
答:去classpath里面找。无论是IDA,tomcat,maven,gradle。再启动的时候都是再拼接java命令行,启动jvm,并且再jvm里面跑。传入参数后,程序执行的时候需要用到哪个类,jvm就到相应的位置上,把类找到,然后加载它
再-classpath/-cp里面找,类的全限定名唯一确认了一个类(目录层级),包就是把许多类放在一起打的压缩包。
举例: mvn compile (第一个是可执行程序,去$Path环境变量里面找, 第二个是参数。本质上是在拼接java命令行)
- 传递性依赖
- 你依赖的类还依赖了别的类
- Classpath hell
- 全限定类名是类的唯一标识
- 当多个同名类同时出现在Classpath中,就是噩梦的开始。所以maven出现了
maven之前是怎么进行包管理的
- 查看项目中需要用的的jar包,去网上下载
- 查看这些jar包依赖的jar包,去网上下载
- 将他们放在一个目录里面,运行命令进行编译/测试
maven的包管理
- 划时代的坐标系统
- 通过唯一的坐标定位包,不需要傻乎乎去网上下载了
- 坐标可读性好,简单方便
- 语义化版本,方便自动化工具处理
- 一旦发布不可修改,实现稳定的构建
- 元数据系统
- jar包本身是i没有元信息,很难知道他依赖谁?
- pom存储这个jar包的传递性依赖关系
坐标 gourpId/artifactId/version
maven根据pom.xml文件,在仓库里面找到需要的包和依赖的包。下载下来,然后拼接参数到-classpath后面,启动jvm
- 传递性依赖
- 自动管理传递性依赖
- 原则:绝对不允许最终classpath出现同名不同版本的jar包
- 冲突解决
- 解决原则
- 最近原则
- 解决原则
a >> b >> c.0.2 三层
d >> c.0.1 两层。 这个里的进,所以这个优先
- 最先声明原则
- 解决方法
- 根据最先声明原则,在pom中追加依赖包,并且指定想要的版本 (0.2)
- 使用maven的排除机制(exclusion)
依赖的scope
- 通常的构建包含4个步骤
- javac编译main目录
- 【compile + provided -runtime】
- javac编译testmul
- 【compile + provided +test - runtime】
- java运行所有测试
- 【compile +test +runtime】
- java运行生产代码
- 【compile + runtime】
- javac编译main目录
- compile:生产代码的依赖
- test:只有测试代码的依赖,例如测试框架Junit
- provided:编译是需要,但是运行时由容器提供,典型如Servlet Api
- runtime:编译时候不需要,运行时候需要,典型如mysql-connector等JDBC的包
2. Maven的自动化构建
maven的本地仓库
本地仓库的角色
- 中央仓库的本地缓存
- 先找本地仓库,找不到才去中央仓库去下载
- 启动java/javac命令时的包来源
- 跨项目/模块开发时的代码共享
自动化构建
默认的三套生命周期
- default
- clean
- site
声明周期与阶段
- 通过命令行指定要运行的阶段
- 从头到该阶段所有的阶段都会被运行
插件
- 插件/目标/绑定
- 插件是maven的灵魂,实际上就是一组代码
- 插件可以声明多个目标,简称MOJO
- 同一个目标可以绑定到多个阶段上,多次执行
- 默认绑定
插件和目标的绑定
- 引入execute插件, 并且绑定到某个阶段(例子展示的是validate阶段)
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
绑定具体的目标到某个阶段上(示例是把exec绑定到validate上)
<groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.0.0</version> <executions> <execution> ... <goals> <goal>exec</goal> </goals> </execution> </executions>
绑定完后执行报错,报错信息指明,缺少一个参数,这个参数指向了一个可以执行的程序。
The parameter executable is missing or invalid绑定可执行程序(文档实例)
<configuration> <executable>java</executable> <arguments> <argument>-classpath</argument> <classpath> <dependency>commons-io:commons-io</dependency> <dependency>commons-lang:commons-lang</dependency> </classpath> <argument>com.example.Main</argument> ... </arguments> </configuration>
从命令行直接调用插件
- mvn dependency: tree
- mvn help:describe -Dplugin=surefire
- mvn surefire:test
- mvn flyaway:migrate
他们是从哪里来的?
其实每个plugin有个前缀来修饰他,执行mvn surefire 相当于执行了
mvn maven-surefire-plugin
插件是怎么解析的?
插件就是普通的jar包
- groupId/artifactId/version去插件仓库寻找
- 插件仓库和仓库是不同的配置
- 如果gourpId不指定,则默认org.apache.maven.plugins
- 如果version不指定,则可以从父POM继承
- 如果groupId/artifactId不指定,可按照prefix调用
3. 自己写一个Maven插件
编写自己的插件
在编译阶段,统计代码行数
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>myPlugin</artifactId>
<packaging>maven-plugin</packaging>
<version>1.0-SNAPSHOT</version>
<name>myPlugin Maven Mojo</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.5.2</version>
</dependency>
</dependencies>
</project>
package org.example;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
/**
* Goal which touches a timestamp file.
*
* @goal touch
*
* @phase process-sources
*/
@Mojo(name = "countLines", defaultPhase = LifecyclePhase.COMPILE)
public class MyMojo extends AbstractMojo {
@Parameter(defaultValue = "${project.build.sourceDirectory}", property = "sourceDir", required = true)
private File sourceDir;
public void execute() throws MojoExecutionException {
try {
Files.walkFileTree(sourceDir.toPath(), new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
int lineCount = Files.readAllLines(file).size();
getLog().info("Line count of file" + file + " is " + lineCount);
return super.visitFile(file, attrs);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
写完之后一定要变成一个jar包才能被别人使用,使用如下命令,把jar包发布到本地仓库
mvn install
把包发布到了如下的路径下面。
[INFO] Installing D:\ITLearning\Java\myPlugin\target\myPlugin-1.0-SNAPSHOT.jar to D:\MavenRepository\org\example\myPlugin\1.0-SNAPSHOT\myPlugin-1.0-SNAPSHOT.jar
现在所有的项目都可以使用这个包了。因为已经发布到了本地仓库
现在打开另一个项目。使用plugin插件引入刚才写的Maven插件,并且把目标绑定到相应的阶段(initialize)上面
<plugin>
<!--这里是引入自己写的插件-->
<groupId>org.example</groupId>
<artifactId>myPlugin</artifactId>
<version>1.0-SNAPSHOT</version>
<!--这里是要把目标绑定到initialize阶段-->
<executions>
<execution>
<phase>initialize</phase>
<goals>
<goal>countLines</goal>
</goals>
</execution>
</executions>
</plugin>
然后terminal执行 mvn initialize
给插件传入参数
prefix 参数
package org.example;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
/**
* Goal which touches a timestamp file.
*
* @goal touch
*
* @phase process-sources
*/
@Mojo(name = "countLines", defaultPhase = LifecyclePhase.COMPILE)
public class MyMojo extends AbstractMojo {
@Parameter(defaultValue = "${project.build.sourceDirectory}", property = "sourceDir", required = true)
private File sourceDir;
public void execute() throws MojoExecutionException {
// 插件接受参数
String prefix = System.getProperty("prefix");
try {
Files.walkFileTree(sourceDir.toPath(), new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
int lineCount = Files.readAllLines(file).size();
getLog().info("Line count of file" + file + " is " + lineCount + "and prefix is " + prefix);
return super.visitFile(file, attrs);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行 mvn install更新到本地仓库里面去
// 调用方调用的时候使用-D来传入参数
mvn initialize -Dprefix=wahahahahahah
调试Maven(debug)
PS:jar包和源代码必须得是一一对应的才行。一点不能错
输入下列命令行
mvnDebug initialize -Dprefix=wahahahahahah
然后再maven插件的项目里面
打断点 点击dubug按钮
4. Maven多模块
为什么要进行多模块构建
- 软件工程的原则:高内聚,低耦合
- 模块化的系统,用户自由选择所需模块
- 可能带来性能的提升
- 多模块->独立的项目->微服务
把一个项目拆分为多模块
父项目
总项目的打包方式必须是pom,modeules里面存放的是子项目
项目结构
子项目
gourpId和父项目一致,artifactId是子项目本来的名称,version和父项目一直
模块之间依赖
也是用depency标签
- groupId
- artifactId
- version
mvn test
mvn install
可以把子模块安装到maven仓库里。 首先使用mvn install将模块安装到本地仓库里面。以防止以前的构建影响现在的项目要(如果直接跑项目,可能maven会读取以前的jar包)
解决重复—-maven继承
模块的继承
- 多个模块间大量的重复代码违反了DRY原则
- 将共同元素抽取成为公用的pom
- 所有的pom都隐式继承超级pom
为所有的子模块定义统一的依赖版本 为所有的子模块定义统一的插件版本
父pom引入依赖,子项目用parent标签来继承父pom
——注意注意——
重要的事情说三遍
子pom,不用声明version,这个依赖只能依赖版本,不能依赖依赖本身,需要进行格外声明,只是不用声明版本
在父亲pom里面可以用property声明version,然后子pom来引用