美文网首页Spring cloud
java -jar springboot-xxx.jar Spr

java -jar springboot-xxx.jar Spr

作者: BeautifulHao | 来源:发表于2019-05-14 14:45 被阅读144次

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

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

    相关文章

      网友评论

        本文标题:java -jar springboot-xxx.jar Spr

        本文链接:https://www.haomeiwen.com/subject/iopsaqtx.html