您好,登錄后才能下訂單哦!
初始化一個Spring 應用,添加如下依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.fxipp.spring</groupId>
<artifactId>first-app-by-gui</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>first-app-by-gui</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
執行mvn package
命令打包,查看jar包的目錄結構
.
├── BOOT-INF
│?? ├── classes
│?? │?? ├── application.properties
│?? │?? └── com
│?? │?? └── fxipp
│?? │?? └── spring
│?? │?? └── FirstAppByGuiApplication.class
│?? └── lib
│?? ├── classmate-1.4.0.jar
│?? ├── hibernate-validator-6.0.17.Final.jar
│?? ├── jackson-annotations-2.9.0.jar
│?? ├── jackson-core-2.9.9.jar
│?? ......
├── META-INF
│?? ├── MANIFEST.MF
│?? └── maven
│?? └── com.fxipp.spring
│?? └── first-app-by-gui
│?? ├── pom.properties
│?? └── pom.xml
└── org
└── springframework
└── boot
└── loader
├── ExecutableArchiveLauncher.class
├── JarLauncher.class
├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
├── LaunchedURLClassLoader.class
├── ......
├── archive
│?? ├── Archive$Entry.class
│?? ├── Archive$EntryFilter.class
│?? ├── Archive.class
│?? ├── ......
├── data
│?? ├── RandomAccessData.class
│?? ├── RandomAccessDataFile$1.class
│?? ├──......
├── jar
│?? ├── AsciiBytes.class
│?? ├── Bytes.class
│?? ├── ......
└── util
└── SystemPropertyUtils.class
18 directories, 91 files
文件結構比較復雜,解釋一下
BOOT-INF/classes
: 存放應用編譯后的class文件;BOOT-INF/lib
:class path目錄, 存放應用依賴的jar包;META-INF
: 存放應用的元信息,如MANIFEST.MF
文件;org
:存放Spring Boot自身的class文件;我們先從MANIFEST.MF
文件查看
Manifest-Version: 1.0
Implementation-Title: first-app-by-gui
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.fxipp.spring.FirstAppByGuiApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.1.6.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher
里面記錄了應用的元信息,Spring的版本,應用的版本,Maven的版本,Main-Class等信息。不難發現,MainClass指向的是org.springframework.boot.loader.JarLauncher
(以下簡稱JarLauncher
),而不是我們自己編寫的com.fxipp.spring.FirstAppByGuiApplication
。
JarLauncher
從名字看出是一個jar的執行器,他的class文件位于org.springframework.boot.loader
目錄下,可見它是Spring自身的class文件。
JarLauncher的GAV org.springframework.boot:spring-boot-loader:2.1.6.RELEASE
通常情況下,他會在spring-boot-starter-parent
引入到應用中,既然main-class指向到是JarLauncher
,那我們也可以直接執行java org.springframework.boot.loader.JarLauncher
,也可以啟動Spring項目的。
java org.springframework.boot.loader.JarLauncher
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.6.RELEASE)
2019-06-19 20:30:52.202 INFO 3094 --- [ main] c.fxipp.spring.FirstAppByGuiApplication : Starting FirstAppByGuiApplication on fangxideMacBook-Pro.local with PID 3094 (/Users/fangxi/Java/workspace/default/spring-boot/first-app-by-gui/target/temp/BOOT-INF/classes started by fangxi in /Users/fangxi/Java/workspace/default/spring-boot/first-app-by-gui/target/temp)
既然可以執行,那就說明了,JarLauncher
這個類才是Spring項目真正的入口。如果我們執行自己寫的com.fxipp.spring.FirstAppByGuiApplication
會怎么樣?
? classes java com.fxipp.spring.FirstAppByGuiApplication
Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/boot/SpringApplication
at com.fxipp.spring.FirstAppByGuiApplication.main(FirstAppByGuiApplication.java:10)
Caused by: java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 1 more
啟動報錯,原因是找不到org.springframework.boot.SpringApplication
這個類,說白了就是沒有指定Class Path,Spring Boot應用的Class Path目錄是BOOT-INF/lib
。
也就是說,JarLauncher
可以執行成功,是因為Spring Boot知道了Class Path的路徑,說明JarLauncher
在啟動調用com.fxipp.spring.FirstAppByGuiApplication
之前,指定了Class Path的位置。
JarLauncher
的代碼如下
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);
}
@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 {
new JarLauncher().launch(args);
}
}
Archive.Entry
:這個類對對象,代編jar包中的資源文件。isNestedArchive
方法判斷entry對象是不是位于jar包內,如果在jar內部,返回true。如果不在jar包里面,也就是我們解壓了jar包,返回false。
重點看launch(String[])
方法
protected void launch(String[] args) throws Exception {
// 1
JarFile.registerUrlProtocolHandler();
// 2
ClassLoader classLoader = createClassLoader(getClassPathArchives());
// 3
launch(args, getMainClass(), classLoader);
}
這個方法一共3步
sun.net.www.protocol
包下。JarFile.registerUrlProtocolHandler();
這個方法將org.springframework.boot.loader
包下對應的JAR協議實現,覆蓋原有的JAR實現。BOOT-INF/lib
。JarLauncher
類,因為jar包可能會被解壓,解壓前和解壓后的的ClassLoader是不同的。launch
方法,將參數傳遞。
MANIFEST.MF
文件里面Statr-Class屬性,也就是獲取我們自定義主類的Class 文件地址。launch方法
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(mainClass, args, classLoader).run();
}
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
}
public class MainMethodRunner {
private final String mainClassName;
private final String[] args;
public MainMethodRunner(String mainClass, String[] args) {
this.mainClassName = mainClass;
this.args = (args != null) ? args.clone() : null;
}
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 });
}
}
launch方法分析:
MainMethodRunner
對象,調用里面的run()
方法。
run()
方法先獲取到之前設定的ClassLoader。main
方法,通過反射執行。通過分析,我們可以看出,Spring Boot Loader在調用我們自己的主類之前,主要做了三件事
BOOT-INF/lib
這個目錄下的文件。默認實現無法將BOOT-INF/lib
這個目錄當作ClassPath,故需要替換實現。java -jar
啟動,還是java org.springframework.boot.loader.JarLauncher
啟動。以便獲取對應的ClassLoader。MANIFEST.MF
文件中的Start-Class屬性,也就是我們自定義的主類。通過第二步獲取的ClassLoader加載獲取到Class文件,通過反射調用main
方法,啟動應用。免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。