对于一个使用Maven来管理的项目,Maven提倡使用一个目录结构标准:
${basedir} 存放pom.xml和所有的子目录
${basedir}/src/main/java 项目的java源代码
${basedir}/src/main/resources 项目的资源,比如说property文件,springmvc.xml
${basedir}/src/test/java 项目的测试类,比如说Junit代码
${basedir}/src/test/resources 测试用的资源
${basedir}/src/main/webapp/WEB-INF web应用文件目录,web项目的信息,比如存放web.xml、本地图片、jsp视图页面
${basedir}/target 打包输出目录
${basedir}/target/classes 编译输出目录
${basedir}/target/test-classes 测试编译输出目录
Test.java Maven只会自动运行符合该命名规则的测试类
~/.m2/repository Maven默认的本地仓库目录位置
说到这里,可以提一下Archetype,Maven中利用Archetype来生成项目骨架,其实就是上面这些东西,会自动生成一个空白项目的目录,连pom.xml都给你生成了,异常贴心。
当然,如果你是用的IDE的话,项目骨架它自动就给你建好了,但如果你一定要体验一下通过命令行来生成项目骨架的话,也可以。
如果是Maven3的话,在指定的项目父目录执行:
mvn archetype:generate
这个过程实际上是运行插件maven-archetype-plugin的过程。
接着会输出很多很多很多东西,这是在向你展示可供选择的项目骨架,即Archetype,每个Archetype都有一个编号,我看了一下,编号都到2423了。。。
我们可以选择maven-archetype-quickstart
,输入后回车,通过数字选择就可以。
然后maven会提示你输入要创建项目的groupID、artifactId、version以及包名package等。全部输入,然后确定Y:
目录已经建好了。
像是src/main/java
,src/test/java
,pom.xml
这种,它都给你就建好了。
我设定的包名是org.wlh
,于是它很贴心的给我生成了一个src/main/java/org/wlh/App.java
来作为主文件。
Archetype可以帮我们迅速构建起项目的骨架,而且提供了多种模板,非常棒。
这个了解下就行,毕竟IDE都帮我们做好了。
类似Make的Makefile、Ant的build.xml一样,Maven项目的核心是pom.xml。
POM(Project Object Model,项目对象模型)定义了项目的基本信息,比如说项目如何构建、声明项目依赖等。
下面是一个极度原始的pom.xml:
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.companyname.project-groupgroupId>
<artifactId>projectartifactId>
<version>1.0version>
<name>Hello World Projectname>
project>
第一行是XML头,指定了该xml文档的版本和编码方式。
其余是project元素,project是pom.xml的根元素。
modelVersion指定了当前pom模型的版本,对于Maven2及Maven3来说,它只能是4.0.0。
这段代码中最重要的是groupId、artifactId和version三行。这三个元素基本定义了一个项目的基本坐标。
正常来讲,应该把项目主代码放到src/main/java目录下,Maven会自动搜索这个目录来找到项目入口类。
其次,项目入口类的包名,应该跟POM中定义的groupId和artifactId保持一致,即包名为groupId.artifactId
,这样子更加清晰,也方便Maven的自动搜索。当然,不一样也是可以的,对运行没啥大影响。
入口类编写完毕后,就可以使用Maven进行编译了。
在根目录下运行命令:mvn clean compile
clean会告知Maven清理输出目录target/;
compile则告诉Maven编译项目主代码;
完成后,Maven会将项目主代码编译至target/classes目录。
比如说我现在声明一个主类Hello World:
package org.wlh.helloworld;
public class Hello {
public static void main(String[] args) {
System.out.println(new Hello().HelloMaven());
}
public String HelloMaven(){
return "Hello Maven";
}
}
Maven中测试代码应该放在src/test/java
目录下。
Java项目中常用的单元测试框架JUnit。使用时需要在POM中引用该依赖。
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.wlhgroupId>
<artifactId>helloworldartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
dependencies>
project>
代码中添加了dependencies元素,该元素下可以包含多个dependency元素以声明项目的依赖,一个dependency就是一个依赖。
dependency元素下的scope元素,表示该依赖的依赖范围:
在src/test/java下编写测试类HelloTest:
package org.wlh.helloworld;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class HelloTest {
@Test
public void testHello(){
Hello hello = new Hello();
String result = hello.HelloMaven();
assertEquals("Hello Maven", result);
}
}
以上就是一个典型单元测试的三个步骤:
Junit3中,约定需要执行的测试方法以test开头,Junit4仍然推荐遵循这一规定。另外,Junit4中,需要执行的测试方法都应该以@Test进行标注。
测试用例编写完成后,可以执行测试了。如果是通过mvn命令行的话,那需要运行mvn clean test
打包:mvn clean package
输出的日志里可以看到jar:jar的形式,实际上就是把jar插件的jar目标将项目主代码进行打包。
如果想让其他的Maven项目直接引用这个jar包作为依赖的话,则需要把这个jar包安装到本地仓库:
安装:mvn clean install
这里需要注意,默认打包生成的jar是不能够直接运行的。如果直接java -jar运行的话,会提示jar包中没有主清单属性。
这是因为带有main方法的入口类不会添加到manifest中,即打开jar文件中的META-INF/MANIFEST.MF文件,无法看到Main-Class一行。
另外,如果你的项目里有依赖其他jar包的话,Maven默认的package也不会把它们打进去,而只会打包自身的class文件。
那如果想生成可执行的jar文件,该怎么办呢?
两种解决方案:
Main-Class: org.wlh.helloworld.Hello
,即入口类的完整路径,再java -jar运行就可以了。在Maven中,其实提供了3种方式来打包:
maven-jar-plugin打的包,只包含了项目本身的class文件,并没有把项目依赖的所有jar包一起打进去,再加上默认不会指定main-class,所以这样打出来的包不能执行。
maven-shade-plugin和maven-assembly-plugin打出来的包都是含项目依赖的所有jar包的,所以都是可以执行的。二者的具体区别在于,maven-assembly-plugin的一项bug。
一个项目一般会依赖很多jar包,而这些被依赖的jar包可能又会依赖其他的jar包,这样的话,项目本身可能会依赖不同版本的相同包(假设是spring包)。
在这种前提下,使用assembly打包的时候,只能将某一个版本的spring.schemas文件放入最终打出的jar包里,这样会有问题。
而使用shade进行打包的时候,它能够将所有spring jar包中的spring.schemas文件进行合并,所以最终生成的jar包里,相当于是会包含项目中所有出现过的spring版本。
基于这种问题,如果只是想打可执行包的话,推荐使用maven-shade-plugin就可以了。
2022-5-17 16:22:46 但是好像要配置一下?这个看看参考文献3吧。
下面讲一下如何通过maven-shade-plugin插件来生成可执行文件:
在pom.xml文件中加入下列代码:
记得把mainClass节点里的路径换成你项目的入口类就可以。
<project>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-shade-pluginartifactId>
<version>3.0.0version>
<configuration>
<createDependencyReducedPom>falsecreateDependencyReducedPom>
configuration>
<executions>
<execution>
<phase>packagephase>
<goals>
<goal>shadegoal>
goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.wlh.helloworld.HellomainClass>
transformer>
transformers>
configuration>
execution>
executions>
plugin>
plugins>
build>
project>
基于shade插件打包完毕后,target目录下会生成两个jar包,一个是origin-xxx.jar,一个是xxx.jar。其中origin-xxx.jar中只包含了工程自己的class文件,而xxx.jar包含了项目本身的class + 所依赖的jar包。用第二个就可以。
而且也可以看到,xxx.jar中包含了Main-Class一行。
利用filter可以在打包的时候排除掉jar包中的部分内容。
以 groupId:artifactId 为标识,在 filter 内部可以使用
更细致地控制,既可以移除代码文件,也可以移除配置文件。
<configuration>
<filters>
<filter>
<artifact>junit:junitartifact>
<includes>
<include>junit/framework/**include>
<include>org/junit/**include>
includes>
<excludes>
<exclude>org/junit/experimental/**exclude>
<exclude>org/junit/runners/**exclude>
excludes>
filter>
filters>
configuration>
如果想将整个 jar 包都过滤掉,可以使用
,也是指定 groupId:artifactId 的标识。
<configuration>
<artifactSet>
<excludes>
<exclude>classworlds:classworldsexclude>
<exclude>junit:junitexclude>
<exclude>jmock:*exclude>
<exclude>*:xml-apisexclude>
<exclude>org.apache.maven:lib:testsexclude>
<exclude>log4j:log4j:jar:exclude>
excludes>
artifactSet>
configuration>
另外配置
将项目中没有使用的依赖自动移除。
<configuration>
<minimizeJar>trueminimizeJar>
configuration>
shade插件提供了丰富的Transformer工具类,这里介绍一些常用的Transformer。更多的 Transformer 见http://maven.apache.org/plugins/maven-shade-plugin/examples/resource-transformers.html
ManifestResourceTransformer
作用是往 MANIFEST 文件中写入 Main-Class ,这是可执行包的必要条件。
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.lcifn.ApplicationmainClass>
transformer>
transformers>
configuration>
AppendingTransformer
用来处理多个jar包中存在相同的配置文件的合并,尤其是spring。
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlersresource>
transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemasresource>
transformer>
transformers>
configuration>
ServicesResourceTransformer
JDK 的服务发现机制是基于 META-INF/services/目录的,如果同一接口存在多个实现需要合并 ,则可以使用此 Transformer。
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
transformers>
configuration>