Maven插件系列之spring-boot-maven-plugin

烂漫一生 提交于 2020-08-12 15:50:55

Spring Boot的Maven插件(Spring Boot Maven plugin)能够以Maven的方式为应用提供Spring Boot的支持,即为Spring Boot应用提供了执行Maven操作的可能。
Spring Boot Maven plugin能够将Spring Boot应用打包为可执行的jar或war文件,然后以通常的方式运行Spring Boot应用。
Spring Boot Maven plugin的最新版本为2017.6.8发布的1.5.4.RELEASE,要求Java 8, Maven 3.2及以后。

Spring Boot Maven plugin的5个Goals

  • spring-boot:repackage,默认goal。在mvn package之后,再次打包可执行的jar/war,同时保留mvn package生成的jar/war为.origin
  • spring-boot:run,运行Spring Boot应用
  • spring-boot:start,在mvn integration-test阶段,进行Spring Boot应用生命周期的管理
  • spring-boot:stop,在mvn integration-test阶段,进行Spring Boot应用生命周期的管理
  • spring-boot:build-info,生成Actuator使用的构建信息文件build-info.properties

配置pom.xml文件

复制代码

<build>
      <plugins>
          <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.0.1.RELEASE</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
          </plugin>
      </plugins>
</build>

复制代码

mvn package spring-boot:repackage说明

Spring Boot Maven plugin的最主要goal就是repackage,其在Maven的package生命周期阶段,能够将mvn package生成的软件包,再次打包为可执行的软件包,并将mvn package生成的软件包重命名为*.original。

基于上述配置,对一个生成Jar软件包的项目执行如下命令

可以看到生成的两个jar文件,一个是*.jar,另一个是*.jar.original。在执行上述命令的过程中,Maven首先在package阶段打包生成*.jar文件;然后执行spring-boot:repackage重新打包,查找Manifest文件中配置的Main-Class属性,如下所示:

复制代码

Manifest-Version: 1.0  
Implementation-Title: gs-consuming-rest  
Implementation-Version: 0.1.0  
Archiver-Version: Plexus Archiver  
Built-By: exihaxi  
Implementation-Vendor-Id: org.springframework  
Spring-Boot-Version: 1.5.3.RELEASE  
Implementation-Vendor: Pivotal Software, Inc.  
Main-Class: org.springframework.boot.loader.JarLauncher  
Start-Class: com.ericsson.ramltest.MyApplication  
Spring-Boot-Classes: BOOT-INF/classes/  
Spring-Boot-Lib: BOOT-INF/lib/  
Created-By: Apache Maven 3.5.0  
Build-Jdk: 1.8.0_131

复制代码

注意,其中的Main-Class属性值为org.springframework.boot.loader.JarLauncher;

Start-Class属性值为com.ericsson.ramltest.MyApplication。

其中com.ericsson.ramltest.MyApplication类中定义了main()方法,是程序的入口。

通常,Spring Boot Maven plugin会在打包过程中自动为Manifest文件设置Main-Class属性,事实上该属性究竟作用几何,还可以受Spring Boot Maven plugin的配置属性layout控制的,示例如下

复制代码

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>1.5.4.RELEASE</version>
    <configuration>
    <mainClass>${start-class}</mainClass>
    <layout>ZIP</layout>
    </configuration>
    <executions>
    <execution>
    <goals>
    <goal>repackage</goal>
    </goals>
    </execution>
    </executions>
    </plugin>

复制代码

layout属性的值可以如下:

  • JAR,即通常的可执行jar

Main-Class: org.springframework.boot.loader.JarLauncher

  • WAR,即通常的可执行war,需要的servlet容器依赖位于WEB-INF/lib-provided

Main-Class: org.springframework.boot.loader.warLauncher

  • ZIP,即DIR,类似于JAR

Main-Class: org.springframework.boot.loader.PropertiesLauncher

  • MODULE,将所有的依赖库打包(scope为provided的除外),但是不打包Spring Boot的任何Launcher
  • NONE,将所有的依赖库打包,但是不打包Spring Boot的任何Launcher

RepackageMojo 的关键方法execute:

 

 <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
          <version>2.1.16.RELEASE</version>
        </dependency>

 

复制代码

@Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        if (this.project.getPackaging().equals("pom")) {
            getLog().debug("repackage goal could not be applied to pom project.");
            return;
        }
        if (this.skip) {
            getLog().debug("skipping repackaging as per configuration.");
            return;
        }
        //得到项目中的原始的jar,就是使用maven-jar-plugin生成的jar
        File source = this.project.getArtifact().getFile();
        //要写入的目标文件,就是fat jar
        File target = getTargetFile();
        Repackager repackager = new Repackager(source) {
            //从source中寻找spring boot 应用程序入口的main方法。
            @Override
            protected String findMainMethod(JarFile source) throws IOException {
                long startTime = System.currentTimeMillis();
                try {
                    return super.findMainMethod(source);
                }
                finally {
                    long duration = System.currentTimeMillis() - startTime;
                    if (duration > FIND_WARNING_TIMEOUT) {
                        getLog().warn("Searching for the main-class is taking some time, "
                                + "consider using the mainClass configuration "
                                + "parameter");
                    }
                }
            }
        };
        //如果插件中指定了mainClass就直接使用
        repackager.setMainClass(this.mainClass);
        if (this.layout != null) {
            getLog().info("Layout: " + this.layout);
            repackager.setLayout(this.layout.layout());
        }
        //寻找项目运行时依赖的jar,过滤后.
 org.apache.maven.project.DefaultProjectBuilder.resolveDependencies(MavenProject, RepositorySystemSession)获取到依赖的jar对象列表
  RepositoryUtils.toArtifacts( artifacts, resolutionResult.getDependencyGraph().getChildren(),
                                         Collections.singletonList( project.getArtifact().getId() ), null );

        Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(),
                getFilters(getAdditionalFilters()));
        //将Artifact转化成Libraries 
        Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack,
                getLog());
        try {
            LaunchScript launchScript = getLaunchScript();
            //进行repackage
            repackager.repackage(target, libraries, launchScript);
        }
        catch (IOException ex) {
            throw new MojoExecutionException(ex.getMessage(), ex);
        }
        if (this.classifier != null) {
            getLog().info("Attaching archive: " + target + ", with classifier: "
                    + this.classifier);
            this.projectHelper.attachArtifact(this.project, this.project.getPackaging(),
                    this.classifier, target);
        }
        else if (!source.equals(target)) {
            this.project.getArtifact().setFile(target);
            getLog().info("Replacing main artifact " + source + " to " + target);
        }

复制代码

 

基本上重要的步骤都有注释,应该不难理解的。再来看下面,当然也不是重点,看看就行

复制代码

public void repackage(File destination, Libraries libraries,
            LaunchScript launchScript) throws IOException {
        if (destination == null || destination.isDirectory()) {
            throw new IllegalArgumentException("Invalid destination");
        }
        if (libraries == null) {
            throw new IllegalArgumentException("Libraries must not be null");
        }
        if (alreadyRepackaged()) {
            return;
        }
        destination = destination.getAbsoluteFile();
        File workingSource = this.source;
        //如果源jar与目标jar的文件路径及名称是一致的
        if (this.source.equals(destination)) {
            //将源jar重新命名为原名称+.original,同时删除原来的源jar
            workingSource = new File(this.source.getParentFile(),
                    this.source.getName() + ".original");
            workingSource.delete();
            renameFile(this.source, workingSource);
        }
        destination.delete();
        try {
            //将源jar变成JarFile 
            JarFile jarFileSource = new JarFile(workingSource);
            try {
                repackage(jarFileSource, destination, libraries, launchScript);
            }
            finally {
                jarFileSource.close();
            }
        }
        finally {
            if (!this.backupSource && !this.source.equals(workingSource)) {
                deleteFile(workingSource);
            }
        }
    }

复制代码

这一步所做的是清理工作,如果源jar同目标文件路径名称等一致,将源jar重命名,原来的文件删除。为目标文件腾位置。下面的重点来了。

复制代码

private void repackage(JarFile sourceJar, File destination, Libraries libraries,
            LaunchScript launchScript) throws IOException {
        JarWriter writer = new JarWriter(destination, launchScript);
        try {
            final List<Library> unpackLibraries = new ArrayList<Library>();
            final List<Library> standardLibraries = new ArrayList<Library>();
            libraries.doWithLibraries(new LibraryCallback() {
                @Override
                public void library(Library library) throws IOException {
                    File file = library.getFile();
                    if (isZip(file)) {
                        if (library.isUnpackRequired()) {
                            unpackLibraries.add(library);
                        }
                        else {
                            standardLibraries.add(library);
                        }
                    }
                }
            });
            //按照规则写入manifest文件
            writer.writeManifest(buildManifest(sourceJar));
            Set<String> seen = new HashSet<String>();
            writeNestedLibraries(unpackLibraries, seen, writer);
           //写入源jar中的内容
            writer.writeEntries(sourceJar);
           //写入标准的jar,依赖的jar
            writeNestedLibraries(standardLibraries, seen, writer);
            if (this.layout.isExecutable()) {
               //写入spring boot loader的类
                writer.writeLoaderClasses();
            }
        }
        finally {
            try {
                writer.close();
            }
            catch (Exception ex) {
                // Ignore
            }
        }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!