??首先,先准备一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | ├── BOOT-INF │ ├── classes │ │ ├── application.properties │ │ └── com │ │ └── sf │ │ └── demo │ │ └── DemoApplication.class │ └── lib │ ├── spring-boot-2.1.3.RELEASE.jar │ ├── spring-boot-autoconfigure-2.1.3.RELEASE.jar │ ├── spring-boot-starter-2.1.3.RELEASE.jar │ ├── 这里省略掉很多jar包 ├── META-INF │ ├── MANIFEST.MF │ └── maven │ └── com.sf │ └── demo │ ├── pom.properties │ └── pom.xml └── org └── springframework └── boot └── loader ├── ExecutableArchiveLauncher.class ├── JarLauncher.class ├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class ├── LaunchedURLClassLoader.class ├── Launcher.class ├── 省略class ├── archive │ ├── Archive$Entry.class │ ├── 省略class ├── data │ ├── RandomAccessData.class │ ├── 省略class ├── jar │ ├── AsciiBytes.class │ ├── 省略class └── util └── SystemPropertyUtils.class |
这个文件目录分为
-
BOOT-INF/classes :主要存放应用编译后的class 文件 -
BOOT-INF/lib :主要存放应用依赖的jar 包文件 -
META-INF :主要存放maven 和MANIFEST.MF 文件 -
org :主要存放springboot 相关的class 文件
??当你使用命令
1 2 3 4 5 6 7 8 9 10 11 12 | Manifest-Version: 1.0 Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx Implementation-Title: demo Implementation-Version: 0.0.1-SNAPSHOT Start-Class: com.sf.demo.DemoApplication Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Build-Jdk-Spec: 1.8 Spring-Boot-Version: 2.3.0.RELEASE Created-By: Maven Jar Plugin 3.2.0 Implementation-Vendor: Pivotal Software, Inc. Main-Class: org.springframework.boot.loader.JarLauncher |
??启动类
探索JarLauncher 的实现原理
??当执行
JarLauncher 重点类的介绍:
-
java.util.jar.JarFile :JDK 工具类,用于读取JAR 文件的内容 -
org.springframework.boot.loader.jar.JarFile :继承于JDK 工具类JarFile 类并扩展了一些嵌套功能 -
java.util.jar.JarEntry :JDK 工具类,此类用于表示JAR 文件条目 -
org.springframework.boot.loader.jar.JarEntry :也是继承于JDK 工具类JarEntry 类 -
org.springframework.boot.loader.archive.Archive :spring boot loader 抽象出来的统一访问资源的接口 -
org.springframework.boot.loader.archive.JarFileArchive :JAR 文件的实现 -
org.springframework.boot.loader.archive.ExplodedArchive :文件目录的实现
在项目里面添加一个依赖配置,就可以看
1 2 3 4 5 | <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-loader</artifactId> <scope>provided</scope> </dependency> |
org.springframework.boot.loader.ExecutableArchiveLauncher
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | public class JarLauncher extends ExecutableArchiveLauncher { private static final String DEFAULT_CLASSPATH_INDEX_LOCATION = "BOOT-INF/classpath.idx"; static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> { if (entry.isDirectory()) { return entry.getName().equals("BOOT-INF/classes/"); } return entry.getName().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.loadIfPossible(archive.getUrl(), location); } return super.getClassPathIndex(archive); } private String getClassPathIndexFileLocation(Archive archive) throws IOException { Manifest manifest = archive.getManifest(); Attributes attributes = (manifest != null) ? manifest.getMainAttributes() : null; String location = (attributes != null) ? attributes.getValue(BOOT_CLASSPATH_INDEX_ATTRIBUTE) : null; return (location != null) ? location : DEFAULT_CLASSPATH_INDEX_LOCATION; } @Override protected boolean isPostProcessingClassPathArchives() { return false; } @Override protected boolean isSearchCandidate(Archive.Entry entry) { return entry.getName().startsWith("BOOT-INF/"); } @Override protected boolean isNestedArchive(Archive.Entry entry) { return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry); } //start public static void main(String[] args) throws Exception { new JarLauncher().launch(args); } } |
org.springframework.boot.loader.Launcher
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /** * * 启动程序的基类,该启动程序可以使用一个或多个支持的完全配置的类路径来启动应用程序 * * @author Phillip Webb * @author Dave Syer * @since 1.0.0 */ public abstract class Launcher { private static final String JAR_MODE_LAUNCHER = "org.springframework.boot.loader.jarmode.JarModeLauncher"; /** * 启动应用程序,此方法是子类方法{@code public static void main(String[] args)}调用的初始入口点 * @param args the incoming arguments * @throws Exception if the application fails to launch */ protected void launch(String[] args) throws Exception { if (!isExploded()) { //①注册一个自定义URL的jar协议 JarFile.registerUrlProtocolHandler(); } //②创建指定archive的类加载器 ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator()); String jarMode = System.getProperty("jarmode"); //③获取Start-Class属性对应的com.sf.demo.DemoApplication String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass(); //④利用反射调用Start-Class,执行main方法 launch(args, launchClass, classLoader); } } |
①注册一个自定义URL 的JAR 协议
org.springframework.boot.loader.jar.JarFile#registerUrlProtocolHandler
1 2 3 4 5 6 7 8 9 | /** * 注册一个'java.protocol.handler.pkgs'属性,让URLStreamHandler处理jar的URL */ public static void registerUrlProtocolHandler() { String handlers = System.getProperty(PROTOCOL_HANDLER, ""); System.setProperty(PROTOCOL_HANDLER, ("".equals(handlers) ? HANDLERS_PACKAGE : handlers + "|" + HANDLERS_PACKAGE)); resetCachedUrlHandlers(); } |
org.springframework.boot.loader.jar.JarFile#resetCachedUrlHandlers
1 2 3 4 5 6 7 8 9 10 11 | /** * 防止已经使用了jar协议,需要重置URLStreamHandlerFactory缓存的处理程序。 */ private static void resetCachedUrlHandlers() { try { URL.setURLStreamHandlerFactory(null); } catch (Error ex) { // Ignore } } |
②创建指定archive 的类加载器
org.springframework.boot.loader.ExecutableArchiveLauncher#getClassPathArchivesIterator
1 2 3 4 5 6 7 8 9 10 | @Override protected Iterator<Archive> getClassPathArchivesIterator() throws Exception { Archive.EntryFilter searchFilter = this::isSearchCandidate; Iterator<Archive> archives = this.archive.getNestedArchives(searchFilter, (entry) -> isNestedArchive(entry) && !isEntryIndexed(entry)); if (isPostProcessingClassPathArchives()) { archives = applyClassPathArchivePostProcessing(archives); } return archives; } |
org.springframework.boot.loader.Launcher#createClassLoader(java.util.Iterator)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /** * 创建一个指定的archives的类加载器 * @param archives the archives * @return the classloader * @throws Exception if the classloader cannot be created * @since 2.3.0 */ protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception { List<URL> urls = new ArrayList<>(50); while (archives.hasNext()) { Archive archive = archives.next(); urls.add(archive.getUrl()); archive.close(); } return createClassLoader(urls.toArray(new URL[0])); } |
org.springframework.boot.loader.Launcher#createClassLoader(java.util.Iterator)
1 2 3 4 5 6 7 8 9 | /** * 创建一个指定的自定义URL的类加载器 * @param urls the URLs * @return the classloader * @throws Exception if the classloader cannot be created */ protected ClassLoader createClassLoader(URL[] urls) throws Exception { return new LaunchedURLClassLoader(isExploded(), urls, getClass().getClassLoader()); } |
③获取Start-Class 属性对应的com.sf.demo.DemoApplication
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Override protected String getMainClass() throws Exception { Manifest manifest = this.archive.getManifest(); String mainClass = null; if (manifest != null) { //从配置文件获取Start-Class对应的com.sf.demo.DemoApplication mainClass = manifest.getMainAttributes().getValue("Start-Class"); } if (mainClass == null) { throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this); } return mainClass; } |
④利用反射调用Start-Class ,执行main 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /** * 启动应用程序 * @param args the incoming arguments * @param launchClass the launch class to run * @param classLoader the classloader * @throws Exception if the launch fails */ protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception { //将当前线程的上下文类加载器设置成LaunchedURLClassLoader Thread.currentThread().setContextClassLoader(classLoader); //启动应用程序 createMainMethodRunner(launchClass, args, classLoader).run(); } /** * 构造一个MainMethodRunner类,来启动应用程序 * @param mainClass the main class * @param args the incoming arguments * @param classLoader the classloader * @return the main method runner */ protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) { return new MainMethodRunner(mainClass, args); } |
org.springframework.boot.loader.MainMethodRunner
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /** * 用来调用main方法的工具类 * * @author Phillip Webb * @author Andy Wilkinson * @since 1.0.0 */ public class MainMethodRunner { private final String mainClassName; private final String[] args; /** * Create a new {@link MainMethodRunner} instance. * @param mainClass the main class * @param args incoming arguments */ public MainMethodRunner(String mainClass, String[] args) { this.mainClassName = mainClass; this.args = (args != null) ? args.clone() : null; } //利用反射启动应用程序 public void run() throws Exception { Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader()); Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); mainMethod.setAccessible(true); mainMethod.invoke(null, new Object[] { this.args }); } } |
我们先了解一下类加载机制:
??我们知道双亲委派模型的原则,当一个类加载器收到类加载任务,会先交给其父类加载器去完成,因此最终加载任务都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时,才会尝试加载任务。
??由于
??在
org.springframework.boot.loader.LaunchedURLClassLoader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class LaunchedURLClassLoader extends URLClassLoader { @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { if (name.startsWith("org.springframework.boot.loader.jarmode.")) { ........省略代码 try { try { //尝试根据类名去定义类所在的包,即java.lang.Package,确保jar文件嵌套jar包里匹配的manifest能够和package关联起来 definePackageIfNecessary(name); } catch (IllegalArgumentException ex) { // Tolerate race condition due to being parallel capable if (getPackage(name) == null) { // This should never happen as the IllegalArgumentException indicates // that the package has already been defined and, therefore, // getPackage(name) should not return null. throw new AssertionError("Package " + name + " has already been defined but it could not be found"); } } return super.loadClass(name, resolve); } finally { Handler.setUseFastConnectionExceptions(false); } } } |
org.springframework.boot.loader.LaunchedURLClassLoader#definePackageIfNecessary
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** * 在进行调用findClass方法之前定义一个包,确保嵌套jar与包关联 * @param className the class name being found */ private void definePackageIfNecessary(String className) { int lastDot = className.lastIndexOf('.'); if (lastDot >= 0) { String packageName = className.substring(0, lastDot); if (getPackage(packageName) == null) { try { definePackage(className, packageName); } catch (IllegalArgumentException ex) { // Tolerate race condition due to being parallel capable if (getPackage(packageName) == null) { // This should never happen as the IllegalArgumentException // indicates that the package has already been defined and, // therefore, getPackage(name) should not have returned null. throw new AssertionError( "Package " + packageName + " has already been defined but it could not be found"); } } } } } |
总结:
1、
2、
3、
4、
参考资料:
https://www.yht7.com/news/18153
https://segmentfault.com/a/1190000016192449
https://cloud.tencent.com/developer/article/1469863
https://www.cnblogs.com/xxzhuang/p/11194559.html
http://www.10qianwan.com/articledetail/577937.html
https://blog.csdn.net/shenchaohao12321/article/details/103543446
https://fangjian0423.github.io/2017/05/31/springboot-executable-jar/
如果文章存在问题、不妥的地方或者有疑惑麻烦给我留言,大家一起学习,感谢您~
欢迎关注我的微信公众号,里面有很多干货,各种面试题