SpringJava技术升华JAVA

Springboot启动原理

2019-11-19  本文已影响0人  AriseFX

Springboot是什么:SpringBoot=自动装配+外部化配置

启动方式:1,IDE启动;2,使用打包插件打成FatJar启动

1.在开发阶段通常会使用IDE工具,启动类代码通常为:

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

查看@SpringBootApplication注解发现

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

在Spring中,对注解实现了“派生性”,故仅使用@SpringBootApplication等同于加上了上述注解。其中@EnableAutoConfiguration是很关键的一个注解。

图1.png
在图1中,SpringBoot在META-INF目录下,定义了一个spring.factories文件,在该文件中,定义了EnableAutoConfiguration的时候,会去加载的类,最终会实例化为SpringBoot应用所需要的默认组件,达到了自动化装配的目的。
该spring.factories文件的扫描和装配依赖于SpringBoot的一个实现:org.springframework.core.io.support.SpringFactoriesLoader,其中该类的常量中定义了该文件的目录:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
        Assert.notNull(factoryClass, "'factoryClass' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
                //注意这一行,会去加载spring.factories文件
        List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
        }
        List<T> result = new ArrayList<T>(factoryNames.size());
        for (String factoryName : factoryNames) {
            result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
        }
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }
        //这个方法去加载了FACTORIES_RESOURCE_LOCATION定义的目录下的factories文件
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        try {
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            List<String> result = new ArrayList<String>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                    "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

最终该factories文件会解析为一个Map<String, List<String>>,key为注解,value为对应类的集合。第一次加载后,后面每次都会在该map中读取。如图2。


图2.png

在图3中,org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter#getAutoConfigurations拿到了@EnableAutoConfiguration对应的96个类名。

图3.png
如果想使用SpringBoot的自动装配特性,去加载我们自己编写的组件,可以使用照葫芦画瓢的方式,在META-INF/spring.factories,增加相关配置。
拿到相应的全类名后,就可以通过获取类对象,获取类的构造器去实例化类
上述说明了SpringBoot自动装配的配置数据如何拿到,那么在哪用到了这些数据呢。
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}

点开@EnableAutoConfiguration发现使用了@Import注解,该注解作用是导入一个或多个类。关注org.springframework.boot.autoconfigure.AutoConfigurationImportSelector,,该类为导入的EnableAutoConfigurationImportSelector的父类。

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        try {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                    .loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
            List<String> configurations = getCandidateConfigurations(annotationMetadata,
                    attributes);
            configurations = removeDuplicates(configurations);
            configurations = sort(configurations, autoConfigurationMetadata);
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = filter(configurations, autoConfigurationMetadata);
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return configurations.toArray(new String[configurations.size()]);
        }
        catch (IOException ex) {
            throw new IllegalStateException(ex);
        }
    }
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
              //getSpringFactoriesLoaderFactoryClass()返回,EnableAutoConfiguration.class;
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        Assert.notEmpty(configurations,
                "No auto configuration classes found in META-INF/spring.factories. If you "
                        + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

getCandidateConfigurations()中第一行会拿到,@EnableAutoConfiguration对应的96个类名。从IDEA的DeBug模式调用栈中可以看出来,调用selectImports()发生在invokeBeanFactoryPostProcessors()阶段,此时beanDefinition 已经解析注册,但是没有进行初始化。在processConfigBeanDefinitions中会去解析额外的BeanDefinition,通过调用导入Selector的selectImports()方法,完成spring.factories的读取和解析成BeanDefinition存入BeanDefinitionMap中。最后在finishBeanFactoryInitialization()中进行Bean的初始化。图4


图4.png

执行前后BeanDefinitionMap变化:图5--->图6


图5.png
图6.png

SpringBoot中的条件装配:
@ConditionalOnClass(Test.class) 当前classpath下存在Test类的时候才会去解析该Configuration
@ConditionalOnBean 当前BeanFactory中存在该Bean的时候才会去解析该Configuration
诸如此类的注解还有很多。

例如

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
      String[] value() default {};
      String prefix() default "";
      String[] name() default {};
      String havingValue() default "";
      boolean matchIfMissing() default false;
      boolean relaxedNames() default true;
}

表示当前SystemPropertys中存在该key的时候才去解析该Configuration。例如@ConditionalOnProperty(prefix="prefix",name = "assert", havingValue = "true"),表示当前SystemProperty中存在prefix.assert=true这个key-value,才会生效,对该匹配规则的实现依赖于Spring4提供的@Conditional注解,具体代码实现为OnPropertyCondition类,顶层接口为org.springframework.context.annotation.Condition

public interface Condition {

    /**
     * Determine if the condition matches.
     * @param context the condition context
     * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
     * or {@link org.springframework.core.type.MethodMetadata method} being checked.
     * @return {@code true} if the condition matches and the component can be registered
     * or {@code false} to veto registration.
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

当目标配置类有@Condition注解时,会去调用matches()方法返回的boolean值来判断是否是要解析该配置类。
类关系图如下:

image.png
具体对SystemProperty的实现在org.springframework.boot.autoconfigure.condition.OnPropertyCondition#getMatchOutcome中,
@Override
    public ConditionOutcome getMatchOutcome(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
                metadata.getAllAnnotationAttributes(
                        ConditionalOnProperty.class.getName()));
        List<ConditionMessage> noMatch = new ArrayList<ConditionMessage>();
        List<ConditionMessage> match = new ArrayList<ConditionMessage>();
        for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
            ConditionOutcome outcome = determineOutcome(annotationAttributes,
                    context.getEnvironment());
            (outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
        }
        if (!noMatch.isEmpty()) {
            return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
        }
        return ConditionOutcome.match(ConditionMessage.of(match));
    }
SpringBoot启动流程图.png

2,使用插件打包为FatJar启动

这是SpringBoot提供的maven打包插件

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                   <!-- 可以指定启用类-->
                    <mainClass>com.xxx.xxxApplication</mainClass> 
                </configuration>
            </plugin>
        </plugins>
    </build>

SpringBoot jar包结构为:


jar结构.png

这里直接看直观一点的树状图:

├─BOOT-INF
│  ├─classes
│  │  │  application.properties
│  │  │
│  │  └─com
│  │      └─wy
│  │          └─thinkingofspringboot
│  │                  ThinkingofSpringbootApplication.class
│  │
│  └─lib
│          classmate-1.5.1.jar
│          hibernate-validator-6.0.18.Final.jar
│          jackson-annotations-2.10.0.jar
│          jackson-core-2.10.0.jar
│          jackson-databind-2.10.0.jar
│          jackson-datatype-jdk8-2.10.0.jar
│          jackson-datatype-jsr310-2.10.0.jar
│          jackson-module-parameter-names-2.10.0.jar
│          jakarta.annotation-api-1.3.5.jar
│          jakarta.validation-api-2.0.1.jar
│          jboss-logging-3.4.1.Final.jar
│          jul-to-slf4j-1.7.29.jar
│          log4j-api-2.12.1.jar
│          log4j-to-slf4j-2.12.1.jar
│          logback-classic-1.2.3.jar
│          logback-core-1.2.3.jar
│          lombok-1.18.10.jar
│          slf4j-api-1.7.29.jar
│          snakeyaml-1.25.jar
│          spring-aop-5.2.1.RELEASE.jar
│          spring-beans-5.2.1.RELEASE.jar
│          spring-boot-2.2.1.RELEASE.jar
│          spring-boot-autoconfigure-2.2.1.RELEASE.jar
│          spring-boot-loader-2.2.1.RELEASE.jar
│          spring-boot-starter-2.2.1.RELEASE.jar
│          spring-boot-starter-json-2.2.1.RELEASE.jar
│          spring-boot-starter-logging-2.2.1.RELEASE.jar
│          spring-boot-starter-tomcat-2.2.1.RELEASE.jar
│          spring-boot-starter-validation-2.2.1.RELEASE.jar
│          spring-boot-starter-web-2.2.1.RELEASE.jar
│          spring-context-5.2.1.RELEASE.jar
│          spring-core-5.2.1.RELEASE.jar
│          spring-expression-5.2.1.RELEASE.jar
│          spring-jcl-5.2.1.RELEASE.jar
│          spring-web-5.2.1.RELEASE.jar
│          spring-webmvc-5.2.1.RELEASE.jar
│          tomcat-embed-core-9.0.27.jar
│          tomcat-embed-el-9.0.27.jar
│          tomcat-embed-websocket-9.0.27.jar
│
├─META-INF
│  │  MANIFEST.MF
│  │
│  └─maven
│      └─com.wy
│          └─thinkingof-springboot
│                  pom.properties
│                  pom.xml
│
└─org
    └─springframework
        └─boot
            └─loader
                │  ExecutableArchiveLauncher.class
                │  JarLauncher.class
                │  LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
                │  LaunchedURLClassLoader.class
                │  Launcher.class
                │  MainMethodRunner.class
                │  PropertiesLauncher$1.class
                │  PropertiesLauncher$ArchiveEntryFilter.class
                │  PropertiesLauncher$PrefixMatchingArchiveFilter.class
                │  PropertiesLauncher.class
                │  WarLauncher.class
                │
                ├─archive
                │      Archive$Entry.class
                │      Archive$EntryFilter.class
                │      Archive.class
                │      ExplodedArchive$1.class
                │      ExplodedArchive$FileEntry.class
                │      ExplodedArchive$FileEntryIterator$EntryComparator.class
                │      ExplodedArchive$FileEntryIterator.class
                │      ExplodedArchive.class
                │      JarFileArchive$EntryIterator.class
                │      JarFileArchive$JarFileEntry.class
                │      JarFileArchive.class
                │
                ├─data
                │      RandomAccessData.class
                │      RandomAccessDataFile$1.class
                │      RandomAccessDataFile$DataInputStream.class
                │      RandomAccessDataFile$FileAccess.class
                │      RandomAccessDataFile.class
                │
                ├─jar
                │      AsciiBytes.class
                │      Bytes.class
                │      CentralDirectoryEndRecord$1.class
                │      CentralDirectoryEndRecord$Zip64End.class
                │      CentralDirectoryEndRecord$Zip64Locator.class
                │      CentralDirectoryEndRecord.class
                │      CentralDirectoryFileHeader.class
                │      CentralDirectoryParser.class
                │      CentralDirectoryVisitor.class
                │      FileHeader.class
                │      Handler.class
                │      JarEntry.class
                │      JarEntryFilter.class
                │      JarFile$1.class
                │      JarFile$2.class
                │      JarFile$JarFileType.class
                │      JarFile.class
                │      JarFileEntries$1.class
                │      JarFileEntries$EntryIterator.class
                │      JarFileEntries.class
                │      JarURLConnection$1.class
                │      JarURLConnection$2.class
                │      JarURLConnection$CloseAction.class
                │      JarURLConnection$JarEntryName.class
                │      JarURLConnection.class
                │      StringSequence.class
                │      ZipInflaterInputStream.class
                │
                └─util
                        SystemPropertyUtils.class

观察到jar包分为BOOT-INF,META-INF和org目录。其中只有org目录才是正儿八经的包结构,代码中的启动类ThinkingofSpringbootApplication位于BOOT-INF/classes目录下,并不是正儿八经的包结构,在java类加载器中,BootstrapClassLoader负责加载JAVA_HOME/jre/lib目录下的Archive文件,ExtClassLoader负责加载JAVA_HOME/jre/lib/ext目录,AppClassLoader负责加载classpath定义的第三方包,所以java -jar命令只会识别classpath下的字节码文件(org/springframework/boot/loader)

查看org/springframework/boot/loader目录下的JarLauncher源码:

public class JarLauncher extends ExecutableArchiveLauncher {
    static final String BOOT_INF_CLASSES = "BOOT-INF/classes/"; //对应jar包内部目录
    static final String BOOT_INF_LIB = "BOOT-INF/lib/"; //对应jar包内部目录
    public JarLauncher() {
    }
    protected JarLauncher(Archive archive) {
        super(archive);
    }
    @Override
    protected boolean isNestedArchive(Archive.Entry entry) {
        if (entry.isDirectory()) {
            return entry.getName().equals(BOOT_INF_CLASSES);
        }
        return entry.getName().startsWith(BOOT_INF_LIB);
    }
    public static void main(String[] args) throws Exception {
              //org.springframework.boot.loader.Launcher#launch(java.lang.String[]) 
        new JarLauncher().launch(args);
    }
}

-jar命令作用于该FatJar时,会直接调用该类的main方法,如果有SpringBoot的使用经验,会发现此时ThinkingofSpringbootApplication类(SpringBoot主类)都不在ClassPath目录下,那怎么去启用SpringBoot应用的呢。


类图.png

查看org.springframework.boot.loader.Launcher#launch(java.lang.String[]) 方法源码

protected void launch(String[] args) throws Exception {
                //先不看这行
        JarFile.registerUrlProtocolHandler();
                //重点
        ClassLoader classLoader = createClassLoader(getClassPathArchives());
        launch(args, getMainClass(), classLoader);
    }

查看getClassPathArchives()源码,由上述继承关系,结合JVM中方法的动态分派(就是java中的多态),最终会执行到org.springframework.boot.loader.ExecutableArchiveLauncher下的getClassPathArchives()方法,如下图:

image.png
@Override
    protected List<Archive> getClassPathArchives() throws Exception {
                //this.archive具体是哪个对象,可以去看该类的构造方法,下面不会说那么多。
        List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));
        postProcessClassPathArchives(archives);
        return archives;
    }

查看javadoc可知该方法作用为获取归档文件的List。查看第一行
List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));
this::isNestedArchive为方法引用,最终分派到到JarLauncher中的实现:

@Override
    protected boolean isNestedArchive(Archive.Entry entry) {
        if (entry.isDirectory()) {
            return entry.getName().equals(BOOT_INF_CLASSES);
        }
        return entry.getName().startsWith(BOOT_INF_LIB);
    }

可以看到获取了定义的两个路径,如果entry是文件夹则判断是不是"BOOT-INF/classes/"目录,如果entry是文件就判断该是不是"BOOT-INF/lib/"开头的。

接着看List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));中的this.archive.getNestedArchives()方法,该方法具体会分派到哪,请去看该类的构造,篇幅有限,下面直接说结论。
查看org.springframework.boot.loader.archive.ExplodedArchive#getNestedArchives方法,参数列表中EntryFilter为一个函数接口,具体实现为刚刚的isNestedArchive()。

@Override
public List<Archive> getNestedArchives(EntryFilter filter) throws IOException {
        List<Archive> nestedArchives = new ArrayList<>();
        for (Entry entry : this) {
            if (filter.matches(entry)) {
                nestedArchives.add(getNestedArchive(entry));
            }
        }
        return Collections.unmodifiableList(nestedArchives);
    }
protected Archive getNestedArchive(Entry entry) throws IOException {
        File file = ((FileEntry) entry).getFile();
        return (file.isDirectory() ? new ExplodedArchive(file) : new JarFileArchive(file));
    }

该代码块为循环遍历 BOOT_INF_CLASSES = "BOOT-INF/classes/";
BOOT_INF_LIB = "BOOT-INF/lib/"; 目录下所有的归档文件,从org.springframework.boot.loader.archive.Archive中可以看出保存了归档文件的url、manifes、nested archives等信息。

上述内容已经说明了Launcher中的launch方法的第二行的部分内容,讲了getClassPathArchives()是如何获取到FatJar中的所有归档文件的。

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

下面介绍使用Archive集合去创建类加载器

protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
        List<URL> urls = new ArrayList<>(archives.size());
        for (Archive archive : archives) {
            urls.add(archive.getUrl());
        }
        return createClassLoader(urls.toArray(new URL[0]));
    }
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
        return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
    }

首先获取到所有归档文件的urls,接着获取到当前类的类加载器(AppClassLoader),最后new了一个LaunchedURLClassLoader的实例。

LaunchedURLClassLoader.png
public class LaunchedURLClassLoader extends URLClassLoader {
    static {
        ClassLoader.registerAsParallelCapable();
    }
    /**
     * Create a new {@link LaunchedURLClassLoader} instance.
     * @param urls the URLs from which to load classes and resources
     * @param parent the parent class loader for delegation
     */
    public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

其中parent会一路super到java.lang.ClassLoader,说明LaunchedURLClassLoader的直接父类加载器为AppClassLoader。为什么要这么设计,后面会说。urls在构造中经过包装成为了其父类的成员。

public class URLClassLoader extends SecureClassLoader implements Closeable {
    /* The search path for classes and resources */
    private final URLClassPath ucp;
    /* The context to be used when loading classes and resources */
    private final AccessControlContext acc;
    public URLClassLoader(URL[] urls, ClassLoader parent) {
        super(parent);
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        this.acc = AccessController.getContext();
        ucp = new URLClassPath(urls, acc);
    }

类加载器创建出来了,接下来开始进行launch(args, getMainClass(), classLoader);

protected void launch(String[] args) throws Exception {
        JarFile.registerUrlProtocolHandler();
        ClassLoader classLoader = createClassLoader(getClassPathArchives());
                //开始这一行
        launch(args, getMainClass(), classLoader);
    }

其中getMainClass()为获取真正的SpringBootApplication启动类,下面代码可以看出,从this.archive中获取到了Manifest对象,之前已经说了BOOT-INF下所有归档文件的扫描,而Manifest对象就是从META-INF目录下获取的。之前有个构造没有讲。就是这个:

public ExecutableArchiveLauncher() {
        try {
            this.archive = createArchive();
        }
        catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }
protected final Archive createArchive() throws Exception {
        ProtectionDomain protectionDomain = getClass().getProtectionDomain();
        CodeSource codeSource = protectionDomain.getCodeSource();
        URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
        String path = (location != null) ? location.getSchemeSpecificPart() : null;
        if (path == null) {
            throw new IllegalStateException("Unable to determine code source archive");
        }
        File root = new File(path);
        if (!root.exists()) {
            throw new IllegalStateException("Unable to determine code source archive from " + root);
        }
        return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
    }
public ExplodedArchive(File root, boolean recursive) {
        if (!root.exists() || !root.isDirectory()) {
            throw new IllegalArgumentException("Invalid source folder " + root);
        }
        this.root = root;
        this.recursive = recursive;
        this.manifestFile = getManifestFile(root);
    }
private File getManifestFile(File root) {
        File metaInf = new File(root, "META-INF");
        return new File(metaInf, "MANIFEST.MF");
    }

调用栈比较深,耐心看,最终会去读取MANIFEST.MF文件,而MANIFEST.MF文件就定义了启动类等信息。

Manifest-Version: 1.0
Implementation-Title: thinkingof-springboot
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.wy.thinkingofspringboot.ThinkingofSpringbootApplicati
 on
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.2.1.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher

到这里就清楚了Manifest对象是怎么来的,是如何获取到真正的启动类全类名的。

@Override
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);
        }
        return mainClass;
    }

前面已经构建好了自定义类加载器,拿到了启动类的全类名,开始真正的执行launch了

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

java -jar命令只能使用AppClassLoader去加载classpath的类,其他目录下(如BOOT-INF)应用类加载器根本不认识,那么只能通过自定义类加载器去自定义类的位置进行加载,指定AppClassLoader为直接父类加载器。这时也会导致一问题,当前Launcher类是AppClassloader加载的,由于类加载器的委派关系,当前Launcher类所引用的类只会委派给AppClassLoader的父类加载器加载,其父类加载器都没有加载BOOT_INF目录下归档文件的能力。所以第一行将前面构建的类加载器设为线程上下文类加载器,其目的是对双亲委派模型的"破坏"。

第二行createMainMethodRunner(mainClass, args, classLoader)为对启动类全类名、命令行参数、类加载器包装,直接看run()方法。

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

这里应该就清楚了,最终会从线程上下文中取出实现设置的类加载器,然后获取main方法的Method对象,用反射直接运行。
那么为什么SpringBoot插件对Jar包会这么处理呢?
未完待续....

上一篇下一篇

猜你喜欢

热点阅读