在正常的开发过程中,我们只需要提供一个包含了main()方法的类,并带上@SpringBootApplication注解,那么这个类就是Springboot的启动类。
@SpringBootApplication
public class ExceptionApplication {
public static void main(String[] args) {
SpringApplication.run(ExceptionApplication.class, args);
}
}
按照这种方式启动,那么这些类都是通过应用类加载器加载的,但是Springboot应用发布的时候都是打包成jar然后通过Java命令启动jar,在这种方式下情况就不一样了。
Jar目录结构
├───BOOT-INF
│ ├───classes
│ │ │ application.properties
│ │ │
│ │ └───com
│ │ └───gpcoding
│ │ │ ExceptionApplication.class
│ │ │
│ │ ├───config
│ │ │ WebMvcConfig.class
│ │ │
│ │ ├───controller
│ │ │ UserController.class
│ │ │
│ │ ├───listener
│ │ │ CustomerInterceptor.class
│ │ │
│ │ └───util
│ │ GlobleExceptionHandler.class
│ │ WebLogAspect.class
│ │
│ └───lib
│ .......(省略)
│ spring-aop-5.1.2.RELEASE.jar
│ spring-beans-5.1.2.RELEASE.jar
│ spring-boot-2.1.0.RELEASE.jar
│ spring-boot-actuator-2.1.0.RELEASE.jar
│ spring-boot-actuator-autoconfigure-2.1.0.RELEASE.jar
│ spring-boot-autoconfigure-2.1.0.RELEASE.jar
│ spring-boot-starter-2.1.0.RELEASE.jar
│ spring-boot-starter-actuator-2.1.0.RELEASE.jar
│ spring-boot-starter-aop-2.1.0.RELEASE.jar
│ spring-boot-starter-json-2.1.0.RELEASE.jar
│ spring-boot-starter-logging-2.1.0.RELEASE.jar
│ spring-boot-starter-tomcat-2.1.0.RELEASE.jar
│ spring-boot-starter-web-2.1.0.RELEASE.jar
│ spring-context-5.1.2.RELEASE.jar
│ spring-core-5.1.2.RELEASE.jar
│ spring-expression-5.1.2.RELEASE.jar
│ spring-jcl-5.1.2.RELEASE.jar
│ spring-security-core-5.1.1.RELEASE.jar
│ spring-security-web-5.1.1.RELEASE.jar
│ spring-web-5.1.2.RELEASE.jar
│ spring-webmvc-5.1.2.RELEASE.jar
│ tomcat-embed-core-9.0.12.jar
│ tomcat-embed-el-9.0.12.jar
│ tomcat-embed-websocket-9.0.12.jar
│ validation-api-2.0.1.Final.jar
│
├───META-INF
│ │ MANIFEST.MF
│ │
│ └───maven
│ └───com.gpcoding
│ └───spring-boot-exception
│ 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
│ 。。。(省略)
│
├───data
│ RandomAccessData.class
│ 。。。(省略)
├───jar
│ AsciiBytes.class
│ 。。。(省略)
└───util
SystemPropertyUtils.class
从目录结构我们看出:
|--BOOT-INF
|--BOOT-INF\classes 该文件下的文件是我们最后需要执行的代码
|--BOOT-INF\lib 该文件下的文件是我们最后需要执行的代码的依赖
|--META-INF
|--MANIFEST.MF 该文件指定了版本以及Start-Class,Main-Class
|--org 该文件下的文件是一个spring loader文件,应用类加载器首先会加载执行该目录下的代码
MANIFEST.MF
BOOT-INF里面的内容不需要多看,就是跟我们应用代码相关的class以及依赖的jar。
MANIFEST.MF描述了该Jar文件的很多信息,详细请参考MANIFEST.MF详解。
那么这里面需要关注的两个值:Main-Class和Start-Class
- Main-Class:定义jar文件的入口类,该类必须是一个可执行的类,作为一个可执行的jar包,就必须提供这个属性,且对应的class必须提供main方法。
- Start-Class:springboot应用实际启动类,即含有@SpringBootApplication注解的那个启动类。
Manifest-Version: 1.0
Implementation-Title: spring-boot-exception
Implementation-Version: 1.0.0-SNAPSHOT
Built-By: Gary_Chen1
Implementation-Vendor-Id: com.gpcoding
Spring-Boot-Version: 2.1.0.RELEASE
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.gpcoding.ExceptionApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_271
Implementation-URL: https://projects.spring.io/spring-boot/#/spring-bo
ot-starter-parent/spring-boot-exception
到这,我们可以看出来一丝不同,开发所运行的启动类,并不设置在Main-Class,而是在Start-Class,而Main-Class提供的是"org.springframework.boot.loader.JarLauncher",所以通过jar包启动,我们需要分析JarLauncher这个类(需要在pom中加入spring-boot-loader依赖)。
JarLauncher
JDWP调试:
java -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=5005,suspend=y -jar spring-boot-exception.jar
main()方法创建了JarLauncher实例,调用了launch()方法。
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
//new JarLauncher()调用父类的构造方法,将成员变量 archive 初始化
public ExecutableArchiveLauncher() {
try {
this.archive = createArchive();
this.classPathIndex = getClassPathIndex(this.archive);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
launch()
该方法最主要的四件事:
- 获取应用程序需要加载的类和jar的url
- 根据这些URL创建LaunchedURLClassLoader
- 通过LaunchedURLClassLoader加载MANIFEST.MF里面配置的Start-Class
- 通过反射调用Start-Class对应类的main方法
protected void launch(String[] args) throws Exception {
//注册"java.protocol.handler.pkgs" property值是"org.springframework.boot.loader"
JarFile.registerUrlProtocolHandler();
//创建ClassLoader,LaunchedURLClassLoader
ClassLoader classLoader = createClassLoader(getClassPathArchives());
//调用launch方法,调用getMainClass()获取Start-Class
launch(args, getMainClass(), classLoader);
}
//调用ExecutableArchiveLauncher的getClassPathArchives方法,获取
protected List<Archive> getClassPathArchives() throws Exception {
//这边获取到所有BOOT-INF/lib/的jar以及BOOT-INF/classes/下面class的url
List<Archive> archives = new ArrayList<>(
this.archive.getNestedArchives(this::isNestedArchive));
postProcessClassPathArchives(archives);
return archives;
}
//isNestedArchive是根据名称来判断的是BOOT-INF/classes/还是BOOT-INF/lib/
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
}
//根据获得到的archives创建新的Classloader
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]));
}
//最后是使用LaunchedURLClassLoader来加载BOOT-INF下面的类和jar包
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}
//创建完ClassLoader,就调用launch方法
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
//设置线程上下文类加载器,默认是AppClassLoader,现在应该是LaunchedURLClassLoader
Thread.currentThread().setContextClassLoader(classLoader);
//拿到了Start-Class,就通过run()方法来调用Springboot的启动类。
createMainMethodRunner(mainClass, args, classLoader).run();
}
//获取Start-Class
protected String getMainClass() throws Exception {
//拿到MANIFEST.MF
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
//获取MANIFEST.MF里面配置的Start-Class
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException(
"No 'Start-Class' manifest entry specified in " + this);
}
return mainClass;
}
//这里肯定是通过反射来调用Start-Class的值
public void run() throws Exception {
//通过新创建的LaunchedURLClassLoader来加载
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
//通过反射调用main方法
mainMethod.invoke(null, new Object[] { this.args });
}
最后验证一下可以看出ExceptionApplication是通过LaunchedURLClassLoader类加载加载的,而不是通过应用类加载器。
@SpringBootApplication
public class ExceptionApplication {
public static void main(String[] args) {
System.out.println("==========:"+ExceptionApplication.class.getClassLoader());
SpringApplication.run(ExceptionApplication.class, args);
}
}
LaunchedURLClassLoader
网友评论