java -jar springboot-xxx.jar Spring Boot 帮我们做了什么?

Spring Boot 2.0从出来到现在已经很久了,也使用一阵子,每次构建一个内嵌tomcat的jar的web项目,都很自然的java -jar XXXX.jar就跑起来了。没有深入的去研究过这条命令后面的执行过程,今天就整了这篇博文,做个记录。来分析分析Spring Boot给我们带来了什么便捷配置。

1. Spring Boot Web Jar包结构拆解

1.1. 目标项目结构

为了便于调试,构建一个极简的基于Spring Boot 2.x的Web项目:

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>

启动类

@SpringBootApplication
public class UnifiedPlatformApplication {

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

Maven 编译

mvn compiler:compile

在项目目录下将生成:unified-platform-0.0.1-SNAPSHOT.jar

1.2. Jar包展示

  • 用解压软件,打开jar文件,目录结构如下:
jar.png
  • 主要文件和目录
目录 内容
BOOT-INF\classes 当前项目编译后的class文件
BOOT-INF\lib 当前项目依赖jar包
META-INF MANIFEST.MF文件
org\springframework\boot\loader Spring Boot运行辅助类
  • 重点文件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 执行过程解析

根据可运行Jar执行机制,存在MANIFEST.MF的情况下,JVM会解析该文件内部的Main-Class的值,作为整个jar程序运行的入口。

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

看看解压出来的文件夹,都是class,二话不说那就反编译呗,其实直接丢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/");
    }
    //入口方法,构建JarLauncher对象,执行父类的ExecutableArchiveLauncher的父类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 {
        //反射执行对象mainClass类的main方法
        Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
        Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
        mainMethod.invoke((Object)null, this.args);
    }
}

所以关键点就在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启动类,整个过程貌似也就是获取了下当前线程的类加载器,不知道这么搞的初衷是啥,没有进一步深究。那么接下来,就得分析Spring Boot的启动过程了,别的不说,直接代码跟踪。

3. Spring Boot自动配置过程源码解析

3.1. 官方约定

Spring Boot 文档显示,在项目Jar下META-INFO下文件spring.factories内添加org.springframework.boot.autoconfigure.EnableAutoConfiguration=***,即可以按照约定的方式进行Bean的注册和初始化

cotent.png

3.2. 源码调试跟踪

  • 项目启动类
  public static void main(String[] args) {
        SpringApplication.run(UnifiedPlatformApplication.class, args);
  }
  • SpringApplication.java构建Spring Context,然后进行上下文刷新(构建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);
            
            //调试断点
            //Spring 容器上下文准备好后,进行相关工厂Bean等的注册
            refreshContext(context);
            //**************************
            
            afterRefresh(context, applicationArguments);
            
            //...非核心代码...
        }
        
        //...非核心代码...

        return context;
    }
  • AbstractApplicationContext.java,执行各种处理器。
@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);
                
                //调试断点
                //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循环调用实现了BeanDefinitionRegistryPostProcessor类的处理器
    private static void invokeBeanDefinitionRegistryPostProcessors(
            Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
        
        //调试断点
        //循环调用实现了BeanDefinitionRegistryPostProcessor类的处理器
        //当然也包括本文重点的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);
        //调试断点 
        //处理配置类Bean的注册信息
        processConfigBeanDefinitions(registry);
    }

@import注解处理

public void parse(Set<BeanDefinitionHolder> configCandidates) {
        //...非核心代码...
        //调试断点  
        //处理注解导入类
        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);
                    //调试断点  
                    //处理注解导入类
                    handler.processGroupImports();
                }
            }
            finally {
                this.deferredImportSelectors = new ArrayList<>();
            }
        }
public Iterable<Group.Entry> getImports() {
            //调试断点
            //定位到 @EnableAutoConfiguration的注解
            //@Import(AutoConfigurationImportSelector.class)
            for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
                this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                        deferredImport.getImportSelector());
            }
            return this.group.selectImports();
        }

这个@EnableAutoConfiguration注解正好是启动类继承来的

@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) {
            //调试断点
            //获取自动配置实体
            AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                    .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
                            annotationMetadata);
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }
        }
  • SpringFactoriesLoader.java,终于到了核心类,就是它去加载了需要自动加载的类
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
        //调试断点
        //通过SpringFactoriesLoader 去META-INF/spring.factories.
        //加载 org.springframework.boot.autoconfigure.EnableAutoConfiguration
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());

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

接下去就是通过Spring Bean的导入机制,遍历各种AutoConfiguration类,进行上下文Bean注册以及后续的处理过程。这里有必要列下常用注解的意思:

注解 作用
@Conditional 作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean
@ConditionalOnMissingBean 容器中不存在指定Bean
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项

3. Spring MVC 在哪里初始化

心细如我的读者可能会发现,貌似对于Web项目还少了些什么,怎么没有看到MVC的重要类:DispatcherServlet。莫急,其实看看spring.factories文件会发现:

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类,进行了对MVC各个组件的初始化。比如:

@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 {
    //.....
}

具体过程这里就不在研究了,话题太大了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容