java -jar springboot-xxx.jar Spring Boot 幫我們做了什么某饰?

Spring Boot 2.0從出來(lái)到現(xiàn)在已經(jīng)很久了蝗碎,也使用一陣子猛蔽,每次構(gòu)建一個(gè)內(nèi)嵌tomcat的jar的web項(xiàng)目剥悟,都很自然的java -jar XXXX.jar就跑起來(lái)了。沒(méi)有深入的去研究過(guò)這條命令后面的執(zhí)行過(guò)程曼库,今天就整了這篇博文区岗,做個(gè)記錄。來(lái)分析分析Spring Boot給我們帶來(lái)了什么便捷配置毁枯。

1. Spring Boot Web Jar包結(jié)構(gòu)拆解

1.1. 目標(biāo)項(xiàng)目結(jié)構(gòu)

為了便于調(diào)試慈缔,構(gòu)建一個(gè)極簡(jiǎn)的基于Spring Boot 2.x的Web項(xiàng)目:

pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
</dependencies>
<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
 </build>

啟動(dòng)類(lèi)

@SpringBootApplication
public class UnifiedPlatformApplication {

    public static void main(String[] args) {
        SpringApplication.run(UnifiedPlatformApplication.class, args);
    }
}

Maven 編譯

mvn compiler:compile

在項(xiàng)目目錄下將生成:unified-platform-0.0.1-SNAPSHOT.jar

1.2. Jar包展示

  • 用解壓軟件,打開(kāi)jar文件后众,目錄結(jié)構(gòu)如下:
jar.png
  • 主要文件和目錄
目錄 內(nèi)容
BOOT-INF\classes 當(dāng)前項(xiàng)目編譯后的class文件
BOOT-INF\lib 當(dāng)前項(xiàng)目依賴jar包
META-INF MANIFEST.MF文件
org\springframework\boot\loader Spring Boot運(yùn)行輔助類(lèi)
  • 重點(diǎn)文件MANIFEST.MF
Manifest-Version: 1.0
Implementation-Title: unified-platform
Implementation-Version: 0.0.1-SNAPSHOT
Built-By: fuhao-pc
Implementation-Vendor-Id: com.supcon
Spring-Boot-Version: 2.1.2.RELEASE
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.supcon.UnifiedPlatformApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_144
Implementation-URL: https://projects.spring.io/spring-boot/#/spring-bo
 ot-starter-parent/unified-platform

2. Java -jar 執(zhí)行過(guò)程解析

根據(jù)可運(yùn)行Jar執(zhí)行機(jī)制胀糜,存在MANIFEST.MF的情況下,JVM會(huì)解析該文件內(nèi)部的Main-Class的值蒂誉,作為整個(gè)jar程序運(yùn)行的入口教藻。

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

看看解壓出來(lái)的文件夾,都是class右锨,二話不說(shuō)那就反編譯唄括堤,其實(shí)直接丟IDEA就行了。

JarLauncher.class:

public class JarLauncher extends ExecutableArchiveLauncher {
    static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
    static final String BOOT_INF_LIB = "BOOT-INF/lib/";

    public JarLauncher() {
    }

    protected JarLauncher(Archive archive) {
        super(archive);
    }

    protected boolean isNestedArchive(Entry entry) {
        return entry.isDirectory() ? entry.getName().equals("BOOT-INF/classes/") : entry.getName().startsWith("BOOT-INF/lib/");
    }
    //入口方法绍移,構(gòu)建JarLauncher對(duì)象悄窃,執(zhí)行父類(lèi)的ExecutableArchiveLauncher的父類(lèi)Launcher的Launch方法。
    public static void main(String[] args) throws Exception {
        (new JarLauncher()).launch(args);
    }
}

Launcher.class:

    
    protected void launch(String[] args) throws Exception {
        JarFile.registerUrlProtocolHandler();
        ClassLoader classLoader = this.createClassLoader(this.getClassPathArchives());
        this.launch(args, this.getMainClass(), classLoader);
    }

    protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
        Thread.currentThread().setContextClassLoader(classLoader);
        this.createMainMethodRunner(mainClass, args, classLoader).run();
    }

    protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
        return new MainMethodRunner(mainClass, args);
    }

MainMethodRunner.class:

public class MainMethodRunner {
    private final String mainClassName;
    private final String[] args;

    public MainMethodRunner(String mainClass, String[] args) {
        this.mainClassName = mainClass;
        this.args = args != null ? (String[])args.clone() : null;
    }

    public void run() throws Exception {
        //反射執(zhí)行對(duì)象mainClass類(lèi)的main方法
        Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
        Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
        mainMethod.invoke((Object)null, this.args);
    }
}

所以關(guān)鍵點(diǎn)就在mainClass的獲取了:

    protected String getMainClass() throws Exception {
        Manifest manifest = this.archive.getManifest();
        String mainClass = null;
        if (manifest != null) {
            mainClass = manifest.getMainAttributes().getValue("Start-Class");
        }

        if (mainClass == null) {
            throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
        } else {
            return mainClass;
        }
    }

跟蹤到這里蹂窖,最后還是到了我們自己的代碼 Start-Class: com.supcon.UnifiedPlatformApplication轧抗。繞了一大圈,才到SpringBoot啟動(dòng)類(lèi)瞬测,整個(gè)過(guò)程貌似也就是獲取了下當(dāng)前線程的類(lèi)加載器横媚,不知道這么搞的初衷是啥,沒(méi)有進(jìn)一步深究月趟。那么接下來(lái)灯蝴,就得分析Spring Boot的啟動(dòng)過(guò)程了,別的不說(shuō)孝宗,直接代碼跟蹤穷躁。

3. Spring Boot自動(dòng)配置過(guò)程源碼解析

3.1. 官方約定

Spring Boot 文檔顯示,在項(xiàng)目Jar下META-INFO下文件spring.factories內(nèi)添加org.springframework.boot.autoconfigure.EnableAutoConfiguration=***因妇,即可以按照約定的方式進(jìn)行Bean的注冊(cè)和初始化

cotent.png

3.2. 源碼調(diào)試跟蹤

  • 項(xiàng)目啟動(dòng)類(lèi)
  public static void main(String[] args) {
        SpringApplication.run(UnifiedPlatformApplication.class, args);
  }
  • SpringApplication.java構(gòu)建Spring Context问潭,然后進(jìn)行上下文刷新(構(gòu)建Bean)
public ConfigurableApplicationContext run(String... args) {

        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        //...非核心代碼...
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            configureIgnoreBeanInfo(environment);

            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            
            //調(diào)試斷點(diǎn)
            //Spring 容器上下文準(zhǔn)備好后猿诸,進(jìn)行相關(guān)工廠Bean等的注冊(cè)
            refreshContext(context);
            //**************************
            
            afterRefresh(context, applicationArguments);
            
            //...非核心代碼...
        }
        
        //...非核心代碼...

        return context;
    }
  • AbstractApplicationContext.java睦授,執(zhí)行各種處理器两芳。
@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);
                
                //調(diào)試斷點(diǎn)
                //BeanFactory后置處理器
                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);
                //************************************

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            //...非核心代碼...
        }
    }
  • PostProcessorRegistrationDelegate.java循環(huán)調(diào)用實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor類(lèi)的處理器
    private static void invokeBeanDefinitionRegistryPostProcessors(
            Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
        
        //調(diào)試斷點(diǎn)
        //循環(huán)調(diào)用實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor類(lèi)的處理器
        //當(dāng)然也包括本文重點(diǎn)的ConfigurationClassPostProcessor
        for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessBeanDefinitionRegistry(registry);
        }
        
    }

  • ConfigurationClassPostProcessor.java
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        int registryId = System.identityHashCode(registry);
        
        //...非核心代碼...
        this.registriesPostProcessed.add(registryId);
        //調(diào)試斷點(diǎn) 
        //處理配置類(lèi)Bean的注冊(cè)信息
        processConfigBeanDefinitions(registry);
    }

@import注解處理

public void parse(Set<BeanDefinitionHolder> configCandidates) {
        //...非核心代碼...
        //調(diào)試斷點(diǎn)  
        //處理注解導(dǎo)入類(lèi)
        this.deferredImportSelectorHandler.process();
    }
public void process() {
            List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
            this.deferredImportSelectors = null;
            try {
                if (deferredImports != null) {
                    DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
                    deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
                    deferredImports.forEach(handler::register);
                    //調(diào)試斷點(diǎn)  
                    //處理注解導(dǎo)入類(lèi)
                    handler.processGroupImports();
                }
            }
            finally {
                this.deferredImportSelectors = new ArrayList<>();
            }
        }
public Iterable<Group.Entry> getImports() {
            //調(diào)試斷點(diǎn)
            //定位到 @EnableAutoConfiguration的注解
            //@Import(AutoConfigurationImportSelector.class)
            for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
                this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                        deferredImport.getImportSelector());
            }
            return this.group.selectImports();
        }

這個(gè)@EnableAutoConfiguration注解正好是啟動(dòng)類(lèi)繼承來(lái)的

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}
  • EnableAutoConfiguration.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
        //...非核心代碼...
}
  • AutoConfigurationImportSelector.java
        public void process(AnnotationMetadata annotationMetadata,
                DeferredImportSelector deferredImportSelector) {
            //調(diào)試斷點(diǎn)
            //獲取自動(dòng)配置實(shí)體
            AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                    .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
                            annotationMetadata);
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }
        }
  • SpringFactoriesLoader.java摔寨,終于到了核心類(lèi)去枷,就是它去加載了需要自動(dòng)加載的類(lèi)
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
        //調(diào)試斷點(diǎn)
        //通過(guò)SpringFactoriesLoader 去META-INF/spring.factories.
        //加載 org.springframework.boot.autoconfigure.EnableAutoConfiguration
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());

        //...非核心代碼...
        return configurations;
    }

接下去就是通過(guò)Spring Bean的導(dǎo)入機(jī)制,遍歷各種AutoConfiguration類(lèi)是复,進(jìn)行上下文Bean注冊(cè)以及后續(xù)的處理過(guò)程删顶。這里有必要列下常用注解的意思:

注解 作用
@Conditional 作用(判斷是否滿足當(dāng)前指定條件)
@ConditionalOnJava 系統(tǒng)的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean
@ConditionalOnMissingBean 容器中不存在指定Bean
@ConditionalOnExpression 滿足SpEL表達(dá)式指定
@ConditionalOnClass 系統(tǒng)中有指定的類(lèi)
@ConditionalOnMissingClass 系統(tǒng)中沒(méi)有指定的類(lèi)
@ConditionalOnSingleCandidate 容器中只有一個(gè)指定的Bean,或者這個(gè)Bean是首選Bean
@ConditionalOnProperty 系統(tǒng)中指定的屬性是否有指定的值
@ConditionalOnResource 類(lèi)路徑下是否存在指定資源文件
@ConditionalOnWebApplication 當(dāng)前是web環(huán)境
@ConditionalOnNotWebApplication 當(dāng)前不是web環(huán)境
@ConditionalOnJndi JNDI存在指定項(xiàng)

3. Spring MVC 在哪里初始化

心細(xì)如我的讀者可能會(huì)發(fā)現(xiàn)淑廊,貌似對(duì)于Web項(xiàng)目還少了些什么逗余,怎么沒(méi)有看到MVC的重要類(lèi):DispatcherServlet。莫急季惩,其實(shí)看看spring.factories文件會(huì)發(fā)現(xiàn):

org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\

就是這些AutoConfiguration類(lèi)录粱,進(jìn)行了對(duì)MVC各個(gè)組件的初始化。比如:

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
        TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    //.....
}

具體過(guò)程這里就不在研究了画拾,話題太大了啥繁。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市青抛,隨后出現(xiàn)的幾起案子旗闽,更是在濱河造成了極大的恐慌,老刑警劉巖蜜另,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件适室,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡举瑰,警方通過(guò)查閱死者的電腦和手機(jī)捣辆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)此迅,“玉大人汽畴,你說(shuō)我怎么就攤上這事∮势ǎ” “怎么了整袁?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)佑吝。 經(jīng)常有香客問(wèn)我坐昙,道長(zhǎng),這世上最難降的妖魔是什么芋忿? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任炸客,我火速辦了婚禮疾棵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘痹仙。我一直安慰自己是尔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布开仰。 她就那樣靜靜地躺著拟枚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪众弓。 梳的紋絲不亂的頭發(fā)上恩溅,一...
    開(kāi)封第一講書(shū)人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音谓娃,去河邊找鬼脚乡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛滨达,可吹牛的內(nèi)容都是我干的奶稠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼捡遍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锌订!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起稽莉,我...
    開(kāi)封第一講書(shū)人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瀑志,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后污秆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體劈猪,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年良拼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了战得。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡庸推,死狀恐怖常侦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贬媒,我是刑警寧澤聋亡,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站际乘,受9級(jí)特大地震影響坡倔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一罪塔、第九天 我趴在偏房一處隱蔽的房頂上張望投蝉。 院中可真熱鬧,春花似錦征堪、人聲如沸瘩缆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)庸娱。三九已至,卻和暖如春爽锥,著一層夾襖步出監(jiān)牢的瞬間涌韩,已是汗流浹背畔柔。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工氯夷, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人靶擦。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓腮考,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親玄捕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子踩蔚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

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