最近遇到一個(gè)問題绑榴,自己直接通過Maven去建立一個(gè)SpringBoot項(xiàng)目,最終打包的結(jié)果卻并不是SpringBoot的打包結(jié)果盈魁,導(dǎo)致Jar包不能正常通過java -jar啟動(dòng)。
項(xiàng)目依賴很簡(jiǎn)單窃诉,如下面的所示:
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<groupId>com.wanna</groupId>
<artifactId>maven-test-2</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.15</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.15</version>
</dependency>
</dependencies>
</project>
只是簡(jiǎn)單導(dǎo)入了一個(gè)SpringBoot的WebStarter和一個(gè)SpringBoot的Maven編譯器插件杨耙。
自己搗鼓了幾天都沒有結(jié)果赤套,在網(wǎng)上無(wú)意間發(fā)現(xiàn)了一篇文章說,將Maven插件的配置改為如下的方式珊膜,就可以容握。
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.15</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
實(shí)驗(yàn)了一下,最終發(fā)現(xiàn)確實(shí)可以车柠,能正常打包剔氏,也能正常運(yùn)行Jar包。
可是為什么呢竹祷?為什么加上這樣的配置之后就行谈跛。我之前的SpringBoot項(xiàng)目都是,直接導(dǎo)入插件就行塑陵。
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.15</version>
</plugin>
剛剛我們的這個(gè)repackage是Maven插件的一個(gè)Mojo(RepackageMojo)感憾,于是我們需要弄清楚為什么這個(gè)Mojo沒生效。
于是開始去Debug MAVEN源碼令花,找到MAVEN當(dāng)中構(gòu)建Mojo的地方阻桅,代碼位置如下。
org.apache.maven.lifecycle.internal.DefaultLifecycleMappingDelegate#calculateLifecycleMappings
public Map<String, List<MojoExecution>> calculateLifecycleMappings( MavenSession session, MavenProject project,
Lifecycle lifecycle, String lifecyclePhase )
throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
MojoNotFoundException, InvalidPluginDescriptorException
{
/*
* Initialize mapping from lifecycle phase to bound mojos. The key set of this map denotes the phases the caller
* is interested in, i.e. all phases up to and including the specified phase.
*/
Map<String, Map<Integer, List<MojoExecution>>> mappings =
new LinkedHashMap<>();
for ( String phase : lifecycle.getPhases() )
{
Map<Integer, List<MojoExecution>> phaseBindings = new TreeMap<>();
mappings.put( phase, phaseBindings );
if ( phase.equals( lifecyclePhase ) )
{
break;
}
}
/*
* Grab plugin executions that are bound to the selected lifecycle phases from project. The effective model of
* the project already contains the plugin executions induced by the project's packaging type. Remember, all
* phases of interest and only those are in the lifecycle mapping, if a phase has no value in the map, we are
* not interested in any of the executions bound to it.
*/
for ( Plugin plugin : project.getBuild().getPlugins() )
{
for ( PluginExecution execution : plugin.getExecutions() )
{
// if the phase is specified then I don't have to go fetch the plugin yet and pull it down
// to examine the phase it is associated to.
if ( execution.getPhase() != null )
{
Map<Integer, List<MojoExecution>> phaseBindings = mappings.get( execution.getPhase() );
if ( phaseBindings != null )
{
for ( String goal : execution.getGoals() )
{
MojoExecution mojoExecution = new MojoExecution( plugin, goal, execution.getId() );
mojoExecution.setLifecyclePhase( execution.getPhase() );
addMojoExecution( phaseBindings, mojoExecution, execution.getPriority() );
}
}
}
// if not then i need to grab the mojo descriptor and look at the phase that is specified
else
{
for ( String goal : execution.getGoals() )
{
MojoDescriptor mojoDescriptor =
pluginManager.getMojoDescriptor( plugin, goal, project.getRemotePluginRepositories(),
session.getRepositorySession() );
Map<Integer, List<MojoExecution>> phaseBindings = mappings.get( mojoDescriptor.getPhase() );
if ( phaseBindings != null )
{
MojoExecution mojoExecution = new MojoExecution( mojoDescriptor, execution.getId() );
mojoExecution.setLifecyclePhase( mojoDescriptor.getPhase() );
addMojoExecution( phaseBindings, mojoExecution, execution.getPriority() );
}
}
}
}
}
Map<String, List<MojoExecution>> lifecycleMappings = new LinkedHashMap<>();
for ( Map.Entry<String, Map<Integer, List<MojoExecution>>> entry : mappings.entrySet() )
{
List<MojoExecution> mojoExecutions = new ArrayList<>();
for ( List<MojoExecution> executions : entry.getValue().values() )
{
mojoExecutions.addAll( executions );
}
lifecycleMappings.put( entry.getKey(), mojoExecutions );
}
return lifecycleMappings;
}
這里的一個(gè)MojoExecution對(duì)象兼都,就是一個(gè)我們配置的Mojo嫂沉,如果我們?cè)趐lugin當(dāng)中添加了如下這樣的配置,那么這里就能構(gòu)建出來(lái)repackage這樣的一個(gè)MojoExecution扮碧,如果我們沒有添加输瓜,則會(huì)缺少了這樣的MojoExecution。
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.15</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
這里芬萍,我們開始懷疑尤揣,是在哪里悄悄的配置了repackage這個(gè)Mojo。
我們找到了SpringBootMavenPlugin的官方文檔:
https://docs.spring.io/spring-boot/docs/3.2.2/maven-plugin/reference/htmlsingle/
在第三章"Using the Plugin"當(dāng)中柬祠,有下面這樣的介紹北戏。
其中一條告訴了我們,我們?nèi)绻^承了"spring-boot-starter-parent"的話漫蛔,將會(huì)生成一個(gè)repackage goal的Execution(并且指定了executionId為repackage)嗜愈。
我們找到了spring-boot-starter-parent這個(gè)POM,發(fā)現(xiàn)其中定義的spring-boot-maven-plugin莽龟,確實(shí)通過execution標(biāo)簽去指定了repackage的goal蠕嫁。
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>${start-class}</mainClass>
</configuration>
</plugin>
這下明白了,默認(rèn)情況下spring-boot-maven-plugin并不會(huì)指定Maven的自動(dòng)打包毯盈,只是定義了repackage這個(gè)Mojo剃毒,我們通過Intellij可以看到。
如果我們想要在沒有spring-boot-starter-parent的情況下使用,就可以添加上如下的配置赘阀,去進(jìn)行開啟益缠。(實(shí)際上,沒有spring-boot-starter-parent的情況是很常見的基公,比如在一些大公司當(dāng)中幅慌,公司可能會(huì)自定義一些中間件,就可能會(huì)要求所有的項(xiàng)目統(tǒng)一導(dǎo)入公司的父POM*轰豆,此時(shí)就會(huì)導(dǎo)致我們無(wú)法使用spring-boot-starter-parent去作為父工程胰伍,就很可能需要用到手動(dòng)配置repackage)
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>