Spring 将 uber jar 包装 类 引导至 root 而不是 BOOT-INF/classes

Spring Boot uber jar packaging classes to root instead of BOOT-INF/classes

嗨 Spring 引导专家 -

我正在尝试创建一个 spring 启动 uber jar,它需要部署到 apache 风暴集群。但是,要注意的是,当使用 "spring-boot-maven-plugin".

打包时,Storm 期望 jar 根目录中的所有 class 文件,而打包的应用程序文件在 "BOOT-INF/classes" 下

有什么方法可以让我的应用 classes 直接打包在根目录下而不是 "BOOT-INF/classes" 下?

我尝试将 "maven-assembly-plugin" 与 "spring-boot-maven-plugin" 一起使用,如下所示,它创建了 Uber jar,其中包含打包在 uber jar 根目录下的依赖项 jar 中的所有 class 文件,但应用程序 classes 仍在 BOOT-INF/classes。

<plugins>
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
            <excludes>
                <exclude>
                    <groupId>org.apache.storm</groupId>
                    <artifactId>storm-core</artifactId>
                </exclude>
            </excludes>
            <requiresUnpack>
                <dependency>
                    <groupId>com.myorg</groupId>
                    <artifactId>my-app-artifact</artifactId> <!-- This does not help! :( -->
                </dependency>
            </requiresUnpack>
        </configuration>
    </plugin>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>2.4</version>
        <configuration>
            <appendAssemblyId>false</appendAssemblyId>
            <descriptorRefs>
                <descriptorRef>jar-with-dependencies</descriptorRef>
            </descriptorRefs>
        </configuration>
        <executions>
            <execution>
                <id>make-assembly</id>
                <phase>package</phase>
                <goals>
                    <goal>single</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
</plugins>

因此,对于我未来的自己或任何试图为类似问题寻找答案的人。以下是我在研究过程中意识到的不同之处 -

  1. Storm 需要一个可执行文件 java jar 文件
  2. Spring Boot提供了自定义jar包。虽然它使用 java jar 包装进行确认,但 Spring Boot 从 BOOT-INF/classes
  3. 加载 classes

因此,要使 Spring Boot jar 在 storm 集群上工作,同时表现为 Spring Boot - 我们需要从 [创建所有 classes 的副本=61=] 到 jar 文件的根目录。

这可能吗?答案是肯定的。

使用描述 here 的方法,我能够创建一个 Spring 引导 jar,并将 BOOT-INF/classes 复制到 Spring 引导 jar 的根目录。这种方法需要 ant build.xml、ivy 设置和 ivy.xml,如下所示。 (免责声明:配置只测试到打包之前不在风暴集群上)

因为我们能够创建一个 Spring Boot Jar,在根目录使用 classes 破解 -

我们应该这样做吗?没有.

原因如下 -

  1. Spring 强烈建议不要采用这种方法,以免最终导致不需要的 class 覆盖和 class 版本控制问题 classes跨 jar 文件和不同版本的名称。

  2. Spring Boot Jar 打包不是一种旨在用作依赖 jar 的格式。 Read the first line here. 因此对于依赖用例,您需要坚持使用普通的旧 java 模块。 Spring 引导用于更多的独立可执行文件或部署在 tomcat.

  3. 等容器上

祝你好运!

build.xml

<project
        xmlns:ivy="antlib:org.apache.ivy.ant"
        xmlns:spring-boot="antlib:org.springframework.boot.ant"
        name="spring-boot-sample-ant"
        default="build">

    <description>
        Sample ANT build script for a Spring Boot executable JAR project. Uses ivy for
        dependency management and spring-boot-antlib for additional tasks. Run with
        '$ ant -lib ivy-2.2.jar spring-boot-antlib.jar' (substitute the location of your
        actual jars). Run with '$ java -jar target/*.jar'.
    </description>

    <property name="spring-boot.version" value="1.4.2.RELEASE" />
    <property name="lib.dir" location="${basedir}/target/lib" />
    <property name="start-class" value="com.my.main.class" />

    <target name="resolve" description="--> retrieve dependencies with ivy">
        <ivy:retrieve pattern="${lib.dir}/[conf]/[artifact]-[type]-[revision].[ext]" />
    </target>

    <target name="classpaths" depends="resolve">
        <path id="compile.classpath">
            <fileset dir="${lib.dir}/compile" includes="*.jar" />
        </path>
    </target>

    <target name="init" depends="classpaths">
        <mkdir dir="target/classes" />
    </target>

    <target name="compile" depends="init" description="compile">
        <javac srcdir="src/main/java" destdir="target/classes" classpathref="compile.classpath" />
    </target>

    <target name="clean" description="cleans all created files/dirs">
        <delete dir="target" />
    </target>

    <target name="build" depends="compile">
        <spring-boot:exejar destfile="target/${ant.project.name}-${spring-boot.version}.jar" classes="target/classes">
            <spring-boot:lib>
                <fileset dir="${lib.dir}/runtime" />
            </spring-boot:lib>
        </spring-boot:exejar>
    </target>

    <target name="unjar_dependencies" depends="compile">
        <unzip dest="target/classes">
            <fileset dir="${lib.dir}/compile">
                <include name="my-app-common-0.1-SNAPSHOT.jar" />
            </fileset>
        </unzip>
    </target>

    <!-- Manual equivalent of the build target -->
    <target name="manual" depends="compile, unjar_dependencies">
        <jar destfile="target/manual/${ant.project.name}-${spring-boot.version}.jar" compress="false">
            <mappedresources>
                <fileset dir="target/classes" />
                <globmapper from="*" to="BOOT-INF/classes/*"/>
            </mappedresources>
            <mappedresources>  <!-- **** this mapped resources block does what I was looking for **** -->
                <fileset dir="target/classes" />
                <globmapper from="*" to="/*"/> 
            </mappedresources>
            <mappedresources>
                <fileset dir="src/main/resources" erroronmissingdir="false"/>
                <globmapper from="*" to="BOOT-INF/classes/*"/>
            </mappedresources>
            <mappedresources>
                <fileset dir="${lib.dir}/runtime" />
                <globmapper from="*" to="BOOT-INF/lib/*"/>
            </mappedresources>
            <zipfileset src="${lib.dir}/loader/spring-boot-loader-jar-${spring-boot.version}.jar" />
            <manifest>
                <attribute name="Main-Class" value="org.springframework.boot.loader.JarLauncher" />
                <attribute name="Start-Class" value="${start-class}" />
            </manifest>
        </jar>
    </target>
</project>

ivysettings.xml

<ivysettings>
    <settings defaultResolver="chain" />
    <resolvers>
        <chain name="chain" returnFirst="true">
            <!-- NOTE: You should declare only repositories that you need here -->
            <filesystem name="local" local="true" m2compatible="true">
                <artifact pattern="${user.home}/.m2/repository/[organisation]/[module]/[revision]/[module]-[revision].[ext]" />
                <ivy pattern="${user.home}/.m2/repository/[organisation]/[module]/[revision]/[module]-[revision].pom" />
            </filesystem>
            <ibiblio name="ibiblio" m2compatible="true" />
            <ibiblio name="spring-milestones" m2compatible="true" root="http://repo.spring.io/release" />
            <ibiblio name="spring-milestones" m2compatible="true" root="http://repo.spring.io/milestone" />
            <ibiblio name="spring-snapshots" m2compatible="true" root="http://repo.spring.io/snapshot" />
        </chain>
    </resolvers>
</ivysettings>

ivy.xml

<ivy-module version="2.0">
    <info organisation="org.springframework.boot" module="spring-boot-sample-ant" />
    <configurations>
        <conf name="compile" description="everything needed to compile this module" />
        <conf name="runtime" extends="compile" description="everything needed to run this module" />
        <conf name="loader" description="Spring Boot loader used when manually building an executable archive" />
    </configurations>
    <dependencies>
        <dependency org="org.springframework.boot" name="spring-boot-starter" rev="${spring-boot.version}" conf="compile">
                <exclude org="ch.qos.logback" name="logback-classic"/>
        </dependency>

        <dependency org="org.springframework.boot" name="spring-boot-loader" rev="${spring-boot.version}" conf="loader->default" />

        <dependency org="org.apache.storm" name="storm-core" rev="1.0.2">
            <exclude org="org.apache.logging.log4j" name="log4j-slf4j-impl"/>
            <exclude org="org.apache.logging.log4j" name="log4j-core"/>
        </dependency>

        <dependency org="com.mycompany" name="app-common" rev="0.1-SNAPSHOT"/>

        <dependency org="org.apache.storm" name="storm-kafka" rev="1.0.2"/>

        <dependency org="org.apache.kafka" name="kafka_2.10" rev="0.10.1.0"/>

        <dependency org="org.apache.kafka" name="kafka_2.10" rev="0.10.1.0"/>

        <dependency org="org.apache.httpcomponents" name="httpcomponents-client" rev="4.5.2"/>

        <dependency org="org.eclipse.paho" name="org.eclipse.paho.client.mqttv3" rev="1.1.0"/>

        <dependency org="com.amazonaws" name="aws-java-sdk-s3" rev="1.11.53"/>

        <dependency org="com.jcraft" name="jsch" rev="0.1.54"/>

        <dependency org="io.netty" name="netty-handler" rev="3.7.0.Final"/>


    </dependencies>
</ivy-module>

Is there a way I can have my app classes packaged directly under the root instead of "BOOT-INF/classes"?

是的,您只需要使用 Spring Boot 1.3。回到 maven ... 在你的 pom.xml 中,如果你像这样声明你的 parent:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.5.RELEASE</version>
</parent>

然后您的 类(和其他文件)将被放置在根级别。这是 spring 引导的 "old way"。

在 1.4 版中,他们更改了 spring 引导 jar 结构以使用 BOOT-INF 目录。因此,如果您使用 <version>1.4.1.RELEASE</version>,那么您的 类 将在 BOOT-INF/classes 之下。一个不良的副作用是您的配置文件(例如 application.properties、application-myprofile.properties 等)也将在 BOOT-INF/classes 下,即使它们不是 Java 类。