Spring-boot-loader原理及源码分析-刘宇
投影仪接口Spring-boot-loader原理及源码分析-刘宇
⼀、什么是Spring-boot-loader
spring-boot-loader模块让你的springboot应⽤具备打包为可执⾏jar或war⽂件的能⼒。只需要引⼊Maven插件或者Gradle插件就可以⾃动⽣成。
⼆、jar包中内容解析
2.1、⽬录结构高中数学椭圆知识点
BOOT-INF:spring boot项⽬所独有的⽂件
class:该⽬录下存放了我们⼯程相应的字节码⽂件和配置⽂件
lib:该⼯程所依赖的jar包
META-INF:
MANIFEST.MF:清单⽂件,描述当前可执⾏jar包的⼀些信息
maven:maven项⽬管理⼯具的pom⽂件等
org:该⽂件为org.springframework.boot.loader的jar包的内容,该jar包会被单独拿出来并不存放于项⽬所依赖的第三⽅lib中2.2、MANIFEST.MF内容解析
//清单版本
Manifest-Version:1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: demo
Implementation-Version:0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
//项⽬⽣成的字节码⽂件
Spring-Boot-Class: BOOT-INF/class/
//springboot所依赖的第三⽅库
Spring-Boot-Lib: BOOT-INF/lib/
//jdk版本
Build-Jdk-Spec:1.8
//springboot版本
Spring-Boot-Version:2.4.4
//maven版本
Created-By: Maven Jar Plugin 3.2.0
//springboot⾃定义的属性
Start-Class: ample.demo.DemoApplication
//该可执⾏jar包的⼊⼝类,main⽅法所在的类,该类由spring-boot-loader所提供,该类就在org⽬录下
Main-Class: org.springframework.boot.loader.JarLauncher
2.3、为何需要使⽤spring-boot-loader
2.3.1、jar⽂件的规范
在jar⽂件中,我们必须要将mainclass的⼊⼝的那个类放于jar包的根⽬录中,否则⽆法启动该jar⽂件
jar⽂件中不能够在套嵌jar⽂件
根据jar⽂件的规范我们就应该明⽩,为什么需要将spring-boot-loader中的内容全部拿出来放在jar⽂件的根⽬录了吧。
2.3.2、疑问:为什么不直接将DemoApplication.class放于根⽬录中呢?
这样做理论上是可以的,但是随之⾯临的就是第三⽅jar包也要全部解压出来放置于该应⽤jar包的根⽬录,因为jar规范中是不允许再含有jar 包的。那么有⼈就会说那就全拿出来放在根⽬录中呗?那这样就会出现包名重复的问题。所以我们需要⼀个启动类来帮我们实现,那就是spring-boot-loader。
三、spring-boot-loader源码分析
招标文件封面
通过jar包中的MANIFEST.MF我们可以得知,jar包的⼊⼝类是org.springframework.boot.loader.JarLauncher
启动类结构图:
3.1、从JarLauncher切⼊
因为我们这边打包的是jar包,所以从JarLauncher类开始分析JarLauncher.java
public class JarLauncher extends ExecutableArchiveLauncher {
private static final String DEFAULT_CLASSPATH_INDEX_LOCATION ="BOOT-INF/classpath.idx";
//这个是个过滤器,⽤来筛选出jar包中BOOT-INF/class/和BOOT-INF/lib/下的⽂件和⽬录
static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER =(entry)->{
if(entry.isDirectory()){
Name().equals("BOOT-INF/class/");
}
Name().startsWith("BOOT-INF/lib/");
};
public JarLauncher(){
}
protected JarLauncher(Archive archive){
super(archive);
}
@Override
protected ClassPathIndexFile getClassPathIndex(Archive archive)throws IOException {
// Only needed for exploded archives, regular ones already have a defined order
if(archive instanceof ExplodedArchive){
String location =getClassPathIndexFileLocation(archive);
return ClassPathIndexFile.Url(), location);
}
ClassPathIndex(archive);
}
private String getClassPathIndexFileLocation(Archive archive)throws IOException {
Manifest manifest = Manifest();
Attributes attributes =(manifest != null)? MainAttributes(): null;
String location =(attributes != null)? Value(BOOT_CLASSPATH_INDEX_ATTRIBUTE): null;
return(location != null)? location : DEFAULT_CLASSPATH_INDEX_LOCATION;
}
@Override
protected boolean isPostProcessingClassPathArchives(){
return fal;
}
@Override
protected boolean isSearchCandidate(Archive.Entry entry){
Name().startsWith("BOOT-INF/");
}
//判断该归档是否符合上⾯过滤器的规则
//这个⽅法会在ExecutableArchiveLauncher中的getClassPathArchivesIterator⽅法中执⾏
//⽤于返回符合规则的归档⽂件
@Override
protected boolean isNestedArchive(Archive.Entry entry){
return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
}
//主⼊⼝,⾥⾯调⽤的launch⽅法为顶层Launcher抽象类的⽅法
宫宝森
public static void main(String[] args)throws Exception {
new JarLauncher().launch(args);
}
}
3.2、获取项⽬的归档⽂件
由于JarLauncher继承ExecutableArchiveLauncher,那么JarLauncher实例化时ExecutableArchiveLauncher也会被实例化。
ExecutableArchiveLauncher.java
private static final String START_CLASS_ATTRIBUTE ="Start-Class";
public ExecutableArchiveLauncher(){
try{
//获取项⽬的归档⽂件
this.archive =createArchive();
千层饼怎么做
this.classPathIndex =getClassPathIndex(this.archive);
}
catch(Exception ex){
throw new IllegalStateException(ex);
}
}
//这个⽅法就是获取我们项⽬的归档⽂件,就是我们项⽬⽣成的jar包
protected final Archive createArchive()throws Exception {
ProtectionDomain protectionDomain =getClass().getProtectionDomain();
CodeSource codeSource = CodeSource();
URI location =(codeSource != null)? Location().toURI(): null;
String path =(location != null)? SchemeSpecificPart(): null;所以英语
if(path == null){
throw new IllegalStateException("Unable to determine code source archive");
}
File root =new File(path);
if(!ists()){
throw new IllegalStateException("Unable to determine code source archive from "+ root); }
//返回他是war包归档还是jar包的归档
return(root.isDirectory()?new ExplodedArchive(root):new JarFileArchive(root));
}
/
cad画电路图/该⽅法继承⾃Launcher抽象类,⽤于判断该归档属于jar还是war
@Override
protected boolean isExploded(){
return this.archive.isExploded();
}
//该⽅法返回项⽬归档⽂件中符合过滤器的归档⽂件
@Override
protected Iterator<Archive>getClassPathArchivesIterator()throws Exception {
Archive.EntryFilter archFilter =this::isSearchCandidate;
//通过JarLauncher中的isNestedArchive来判断归档是否符合要求
//将符合要求的归档⽂件放置于集合中
/
/对于打包成jar格式的项⽬,符合要求的就是class和lib下的⽂件
Iterator<Archive> archives =NestedArchives(archFilter,
(entry)->isNestedArchive(entry)&&!isEntryIndexed(entry));
if(isPostProcessingClassPathArchives()){
archives =applyClassPathArchivePostProcessing(archives);
}
return archives;
}
//获取清单⽂件中的start-class类
@Override
protected String getMainClass()throws Exception {
Manifest manifest =Manifest();
String mainClass = null;
红豆饼的做法家常if(manifest != null){
mainClass = MainAttributes().getValue(START_CLASS_ATTRIBUTE);
}
if(mainClass == null){
throw new IllegalStateException("No 'Start-Class' manifest entry specified in "+this);
}
return mainClass;
}