maven使用

如何使用Maven

Maven

Maven的作用

Maven最主要的两个作用

  1. 项目构建
  2. 项目的依赖管理

Maven, Make, Ant的对比

这三个都可以用来构建java代码, 它们之间的对比如下

编程范式 依赖管理 跨平台
Make 命令式 不支持 不支持
Ant 命令式 不支持 支持
Maven 声明式 支持 支持

Make是命令式的, 需要自己写明先编译什么, 再编译什么, 这样比较麻烦. 同时Make调用的是操作系统的命令, 例如cp, mv等等, 在LinuxWindows是不通用的.

Antjava实现了操作系统的某些指令, 让Ant可以跨平台使用

创建Maven项目

只需要使用下面的命令即可

1
mvn archetype:generate "-DgroupId=com.caicai.maven" "-DartifactId=maven-test" "-DarchetypeArtifactId=maven-archetype-quickstart" "-DinteractiveMode=false"

这个命令会形成一个这样的目录结构

也可以自己手动创建创建目录和pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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>com.caicai.maven</groupId>
<artifactId>maven-test</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>maven-test</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

所以说这个命令其实并没有做什么特殊操作, 就创建了一些目录, pom.xml和简单的java文件(这个不是必须的). 只要有这样的目录结构和pom.xml, 就可以把一个项目称为maven项目了

如何打包可运行的jar包

我们首先需要了解一下jar命令, jar命令其实就是一个打包压缩命令, 像zip一样. 只不过会多生成一个META-INF/MANIFEST.MF, 用于描述class. jar打包就是class文件, 其实也可以自己用zip命令打包class, 然后创建MANIFEST文件

1
2
3
4
5
6
7
8
9
10
jar -c[m]f [manifest.mf文件] "jar包的名称" 要打包的目录
-c: 创建
-f: jar包的名称
-m: 使用文件中的内容来生成MEAT-INF/MANIFEST.MF文件

# tip
# 这个命令很奇怪, 参数选项的顺序不能随意写

# 举例
jar -cf "myjar.jar" com

不可运行的jarMANIFEST.MF

可运行的jarMANIFEST.MF

运行jar包

1
java -jar jar包

Maven打包成可执行的jar包

上面我们编写的可执行java程序没有依赖别的jar包, 用的都是java自己的类. 如果要使用别的jar包, 需要自己把jar包复制出来, 然后在MANIFEST.MF中指定类路径. 这是不使用maven的手动方式, 如果依赖的jar包比较多, 自己就需要一个一个复制, 比较麻烦

Maven可以配置构建插件, 自动复制. 如果不配置构建插件, 依赖的jar包, 不会出现在打包的jar文件中

配置插件, 会生成两个jar, 一个是带original, 一个是不带的. 带的不包含自己依赖的jar包, 一般是提供给别人用的, 别人的maven会自动下载这些依赖. 不带的, 放入了解压后的jar包, jar包依赖的jar包也会被放入, 是可以运行的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<build>
<plugins>

<!-- 使用maven-shade-plugin插件打包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.3</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>

<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.caicai.maven.App</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

Maven指定java版本

为什么Maven需要指定java版本呢?

我们已经安装了jdk, 也指定了jdkjavacpath, 这里就已经确定了java的版本, 为什么还有指定java的版本呢? 其中的原因就是高版本的java可以编译出低版本的class文件, 而Maven默认使用的是jdk1.5, 这就意味着, 写的代码不能使用jdk1.5之后的新的特性, 例如jdk1.8的lambda`表达式

利用高版本jdk编译低版本class的方式如下

1
2
3
4
5
6
7
javac.exe -bootclasspath ‪rt.jar -source 1.8 -target 1.8 HelloWorld.java

-bootclasspath: 用于指定要编译成的版本的核心class, 可以用于校验源代码的方法是否能用于生成目标版本, 其实不指定, 也是会校验的, 只不过只会语法特性, 不会校验方法. 如果源代码使用旧版本的不支持的新方法, 不使用这个选项, 就会编译成功, 但实际上在旧版本上运行时, 又会报错
-source: -source和-target通常相同

# 从java1.9开始 javac新增了--release选项, 可以一次性代替上面三个选项
# 其实最好还是使用对应版本的java版本来编译, 不使用这种方式

Maven指定java版本

1
2
3
4
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

Maven中scope=provided

provided的两种应用场景

  1. servlet-api: 编译时需要, 但运行时也需要, 但你不应该提供, 应该用别人已经提供好的, 否则就会产生冲突
  2. lombok: 自己编译时需要, 别人用你的代码时编译时, 不需要. 生命周期在自己的代码编译成class后, 就已经结束了, 代码中的import已经被移除掉了

和optional=true的区别

大概意思是说,在A项目依赖B项目提供了一些特性,但又不想让这些特性默认提供,而是作为可以选择的附加功能,默认不提供,需要声明后(主动添加B项目的依赖)才生效,这时用optional;而对于provided,文档侧重提到了运行环境概念,强调只在编译时存在,而运行时不存在的依赖,也就是说,provided的主要用途不是为了考虑依赖是否传递,而是要看项目运行时是不是不应该有这个依赖(是不是需要jvm或者运行容器提供)。

经常拿scope=provided来举例的经典场景之一,就是servlet-api这个依赖了,在代码coding阶段需要使用到它的一些api,而在实际运行时,它的作用要由具体的运行容器来实现,因此编译时可以有它,而打成war包放到tomcat环境下运行时,war包里面不应该有这个servlet-api.jar,否则就会报错了。

在实际的spring-boot项目中,由于大部分使用了内置的undertow或者tomcat容器,已经不需要特别声明这个jar的provided属性了。事实上,日常中更经常需要被用到的,应该主要就是这个optinal了,比如你要提供一组基础jar包,供项目组中的其他同事在他们的项目中引入依赖使用时,如果你提供的某些依赖了其他jar包来做的功能并不一定会被使用到,便可以用到这个optinal了。特别是用到诸如@ConditionalOnClass这种检测项目中是否存在某个class的判断条件时,更是用optional的好时机。

provided的使用场景,除了servlet-apilombok也很适合:A项目使用lombok做了一些代码生成,完成开发需要deploy到私服仓库之前,记得要将lombok的依赖加上<scope>provided</scope>,因为它的作用周期已经在A项目打包完成时结束了,对于依赖A项目的其他项目,不需要用到lombok这个玩意儿,它们需要的是A项目提供的功能,而不是附带的帮助自己生成代码的额外功能;也不应该用optional,因为没什么好选择的,它并不是A项目提供的可选功能之一。

链接:https://www.jianshu.com/p/4b100150038b

其实最好不要使用optional, 创建一个新的maven项目提供另一种功能更好, 对于使用者更加友好

注意事项

  1. 如果使用了maven来管理项目依赖, 就不要再自己手动添加jar包了. 如果不进行配置, maven看不见你加的jar包的, maven应该只能看到pom里面的依赖
  2. Maven仓库下载的jar包, 只会包含自身的class, 不会包含它依赖的jar包, 还会在同级目录的``pom.xml里面, 会写明自己的依赖, 这样可以让Maven不用每次都下载全量文件, 可以利用之前下载好的jar`包
  3. 一个maven项目中每个jar包只能出现一个版本, 毕竟java只会找类路径, 根本没有版本的概念
  4. Maven跳过测试mvn clean package -Dmaven.test.skip=true

问题

  1. 如何将本地的jar包加入maven的本地仓库呢?

    做不到, 除非自己用源码, 然后再maven install. 只有一个jar包是缺少很多东西的

  2. 为什么spring-core会被mavendependency:analyze分析为Unused declared dependencies found

    这个用lombok可以解释, 字节码中并没有Lombok, 只是源代码中有, 而maven分析的只是字节码, 所以Maven会认为没有使用Lombok, 下面是Maven官网的一段描述, 可以印证我这个猜想

    By default, maven-dependency-analyzer is used to perform the analysis, with limitations due to the fact that it works at bytecode level, but any analyzer can be plugged in through analyzer parameter.

    那为什么Lombokscopeprovided, 而spring-corescopecompile呢? 这个我猜是因为运行时, 也要用到spring-core, Lombok运行时是用不到的, 所以, spring-core是编译时需要, 运行时也需要, 那就只能是compile

  3. 为什么Lombok可以在编译的时候生成代码呢?
    这个是用到java自己的一些特性, 只有实现AbstractProcessor, 就可以在编译时, 运行一些代码来操作编译时存在的注解了. 这也说明了编译时注解的作用

-------------本文结束感谢您的阅读-------------