Springboot启动原理
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个类名。
如果想使用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值来判断是否是要解析该配置类。
类关系图如下:
具体对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()方法,如下图:
@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.pngpublic 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包会这么处理呢?
未完待续....