springboot打包發(fā)布原理

如何打包發(fā)布一個springboot項目腰懂?

SpringBoot 提供了 Maven 插件 spring-boot-maven-plugin,將 Spring Boot 項目打成 jar 包或者 war 包茫多。
只需要在pom.xml文件中加入下面這個插件配置,再通過mvn clean package獲取jar包即可喂江。

<project ...>
    ...
    <build>
        ...
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

打包后 通過下面的命令即可啟動一個服務(wù)胆敞。

java -jar  **.jar 

Jar包如何運行并啟動SpringBoot項目?

springboot jar 的目錄結(jié)構(gòu)

image.png

可以看到,主要有三個大目錄META-INF,BOOT-INF以及org携悯,

META-INF

比較重要的是MAINIFEST.MF文件:

Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: jarlearn
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.jsvc.jarlearn.JarlearnApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.5.5
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher

該文件聲明了Main-Class 配置項:可以理解為jar包的啟動類祭芦,這里設(shè)置為 spring-boot-loader 項目的 JarLauncher類,進行 Spring Boot 應(yīng)用的啟動憔鬼。
還有一個Start-Class 配置項:配置的內(nèi)容是我們springboot項目的主啟動類龟劲。

BOOT-INF

classes文件中保存了 Java 類所編譯的 .class文件以及配置文件等。
lib目錄中保存了我們項目所依賴的jar包轴或。


image.png

org

該文件中即springboot為我們提供的jar包啟動類昌跌,亦即JarLauncher.class

當(dāng)使用 java -jar filename.jar 命令啟動時,會執(zhí)行封裝在 JAR 文件中的程序照雁。JAR 文件需包含 manifest蚕愤,其中一行格式為 Main-Class:classname,指定了一個包含 public static void main(String[] args) 方法的類,作為該程序的啟動點萍诱。

為什么不可以直接Main-Class 放置為springboot的啟動類呢悬嗓?

對應(yīng)在示例的這個項目,問題可以翻譯為為什么不可以直接使用com.jsvc.jarlearn.JarlearnApplication類作為啟動類砂沛?

主要是因為烫扼,Java 沒有提供任何加載嵌套 jar 文件的標準方法(即加載本身包含在 jar 中的 jar 文件)。當(dāng)需要分發(fā)一個可以從命令行運行而不需要解壓縮的自包含應(yīng)用程序時 , 會出現(xiàn)問題碍庵。

同時映企,我試了下,直接運行application類的話静浴,是找不到主類的:

java -classpath /Users/sensu/jarl/jarlearn-0.0.1-SNAPSHOT.jar com.jsvc.jarlearn.JarlearnApplication

錯誤: 找不到或無法加載主類 com.jsvc.jarlearn.JarlearnApplication

因為在文件目錄中堰氓,JarlearnApplication實際上是在META-INF/maven/... 中的,所以會找不到苹享。

所以双絮,springboot以org.springframework.boot.loader.JarLauncher為啟動類,
又自定義了LaunchedURLClassLoader用來加載BOOT-INF中的class文件以及BOOT-INF/lib中的嵌套jar包得问。

關(guān)于JarLaunch

我這邊通過引入spring-boot-loader模塊來看下JarLaunch的源碼:

//JarLauncher

    public static void main(String[] args) throws Exception {
        new JarLauncher().launch(args);
    }

可以看到main方法中囤攀,執(zhí)行了launch方法,改方法由JarLaunch的父類Launcher提供:

    private static final String JAR_MODE_LAUNCHER = "org.springframework.boot.loader.jarmode.JarModeLauncher";

    /**
     * Launch the application. This method is the initial entry point that should be
     * called by a subclass {@code public static void main(String[] args)} method.
     * @param args the incoming arguments
     * @throws Exception if the application fails to launch
     */
    protected void launch(String[] args) throws Exception {
        if (!isExploded()) {
            JarFile.registerUrlProtocolHandler();
        }
        ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
        String jarMode = System.getProperty("jarmode");
        String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
        launch(args, launchClass, classLoader);
    }

launch方法主要分為三步:

  1. 在launch方法中,首先調(diào)用了registerUrlProtocolHandler方法注冊URLStreamHandler類用于jar包的解析.
  2. 調(diào)用了createClassLoader方法宫纬,創(chuàng)建自定義的ClassLoader,用于從jar中加載類焚挠。
  3. 執(zhí)行Application啟動類。

registerUrlProtocolHandler

private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs";
private static final String HANDLERS_PACKAGE = "org.springframework.boot.loader";


    /**
     * Register a {@literal 'java.protocol.handler.pkgs'} property so that a
     * {@link URLStreamHandler} will be located to deal with jar URLs.
     */
    public static void registerUrlProtocolHandler() {
        Handler.captureJarContextUrl();
        String handlers = System.getProperty(PROTOCOL_HANDLER, "");
        System.setProperty(PROTOCOL_HANDLER,
                ((handlers == null || handlers.isEmpty()) ? HANDLERS_PACKAGE : handlers + "|" + HANDLERS_PACKAGE));
        resetCachedUrlHandlers();
    }

基本思路就是將org.springframework.boot.loader包路徑添加到java.protocol.handler.pkgs環(huán)境變量中漓骚,從而使用自定義的 URLStreamHandler 實現(xiàn)類 Handler處理 jar: 協(xié)議的 URL蝌衔。
關(guān)于handler 可以自行百度下。

createClassLoader

這里有兩個主要方法:

ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());

也就是 getClassPathArchivesIterator 以及createClassLoader
首先是 getClassPathArchivesIterator

// ExecutableArchiveLauncher

    @Override
    protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
        Archive.EntryFilter searchFilter = this::isSearchCandidate;
        Iterator<Archive> archives = this.archive.getNestedArchives(searchFilter,
                (entry) -> isNestedArchive(entry) && !isEntryIndexed(entry));
        if (isPostProcessingClassPathArchives()) {
            archives = applyClassPathArchivePostProcessing(archives);
        }
        return archives;
    }

首先是isSearchCandidate蝌蹂,在JarLaunch中實現(xiàn):

//JarLaunch 

    @Override
    protected boolean isSearchCandidate(Archive.Entry entry) {
        return entry.getName().startsWith("BOOT-INF/");
    }

可以看出是只處理BOOT-INF/文件夾下的內(nèi)容噩斟。
然后會通過getNestedArchives獲取到嵌套的Archive,其中的isNestedArchive 方法也由JarLaunch實現(xiàn):

//JarLaunch 

    static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
        if (entry.isDirectory()) {
            return entry.getName().equals("BOOT-INF/classes/");
        }
        return entry.getName().startsWith("BOOT-INF/lib/");
    };

    @Override
    protected boolean isNestedArchive(Archive.Entry entry) {
        return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
    }

基本就是獲取 BOOT-INF/classes/下的目錄以及BOOT-INF/lib/ 下的jar文件剃允,最終通過getNestedArchives將其封裝為對應(yīng)的Archive并返回。

然后就是createClassLoader方法:


    /**
     * Create a classloader for the specified archives.
     * @param archives the archives
     * @return the classloader
     * @throws Exception if the classloader cannot be created
     * @since 2.3.0
     */
    protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
        List<URL> urls = new ArrayList<>(50);
        while (archives.hasNext()) {
            urls.add(archives.next().getUrl());
        }
        return createClassLoader(urls.toArray(new URL[0]));
    }

基本上就是通過archives獲取到所有的URL齐鲤,然后創(chuàng)建處理這些URL的ClassLoader硅急。

執(zhí)行Application啟動類方法

主要就是通過getMainClass方法獲取到manifest文件中配置的Start-Class:

// ExecutableArchiveLauncher
    private static final String START_CLASS_ATTRIBUTE = "Start-Class";

    @Override
    protected String getMainClass() throws Exception {
        Manifest manifest = this.archive.getManifest();
        String mainClass = null;
        if (manifest != null) {
            mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);
        }
        if (mainClass == null) {
            throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
        }
        return mainClass;
    }

然后通過另一個launch方法,開始執(zhí)行:

// Launcher

    /**
     * Launch the application given the archive file and a fully configured classloader.
     * @param args the incoming arguments
     * @param launchClass the launch class to run
     * @param classLoader the classloader
     * @throws Exception if the launch fails
     */
    protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
        Thread.currentThread().setContextClassLoader(classLoader);
        createMainMethodRunner(launchClass, args, classLoader).run();
    }

這里createMainMethodRunner創(chuàng)建出來的是什么呢佳遂?

// MainMethodRunner

    private final String mainClassName;

    private final String[] args;

    /**
     * Create a new {@link MainMethodRunner} instance.
     * @param mainClass the main class
     * @param args incoming arguments
     */
    public MainMethodRunner(String mainClass, String[] args) {
        this.mainClassName = mainClass;
        this.args = (args != null) ? args.clone() : null;
    }

    public void run() throws Exception {
        Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
        Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
        mainMethod.setAccessible(true);
        mainMethod.invoke(null, new Object[] { this.args });
    }

最終調(diào)用的其實就是MainMethodRunner的run方法了营袜,其實也就是通過反射調(diào)用Application的main方法了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末丑罪,一起剝皮案震驚了整個濱河市荚板,隨后出現(xiàn)的幾起案子凤壁,更是在濱河造成了極大的恐慌,老刑警劉巖跪另,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拧抖,死亡現(xiàn)場離奇詭異,居然都是意外死亡免绿,警方通過查閱死者的電腦和手機唧席,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嘲驾,“玉大人淌哟,你說我怎么就攤上這事×晒剩” “怎么了徒仓?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長誊垢。 經(jīng)常有香客問我掉弛,道長,這世上最難降的妖魔是什么喂走? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任殃饿,我火速辦了婚禮,結(jié)果婚禮上芋肠,老公的妹妹穿的比我還像新娘乎芳。我一直安慰自己,他們只是感情好业栅,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布秒咐。 她就那樣靜靜地躺著谬晕,像睡著了一般碘裕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上攒钳,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天帮孔,我揣著相機與錄音,去河邊找鬼不撑。 笑死文兢,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的焕檬。 我是一名探鬼主播姆坚,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼实愚!你這毒婦竟也來了兼呵?” 一聲冷哼從身側(cè)響起兔辅,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎击喂,沒想到半個月后维苔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡懂昂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年介时,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凌彬。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡沸柔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饿序,到底是詐尸還是另有隱情勉失,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布原探,位于F島的核電站乱凿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏咽弦。R本人自食惡果不足惜徒蟆,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望型型。 院中可真熱鬧段审,春花似錦、人聲如沸闹蒜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绷落。三九已至姥闪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間砌烁,已是汗流浹背筐喳。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留函喉,地道東北人避归。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像管呵,于是被迫代替她去往敵國和親梳毙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容