涉及的知识点主要有Maven的生命周期和自定义插件,JDK提供jar包的工具类以及如何扩展Springboot,最后是自定义类加载器。spring-boot-maven-pluginSpringBoot的可执行jar包,又称fatjar,是包含所有第三方依赖的jar包。jar包内嵌了除java虚拟机以外的所有依赖。是一个一体化的jar包。普通插件maven-jar-plugin生成的包和spring-boot-maven-plugin生成的包的直接区别就是在fatjar中增加了两部分。第一部分是lib目录,存放Maven依赖的jar。包文件,第二部分是springbootloader相关的类。fatjar//目录结构├─BOOT-INF│├─classes│└─lib├─META-INF│├─maven│├─app.properties│├─MANIFEST.MF└─org└─springframework└??─boot└─loader├─archive├─data├─jar└─util也就是说,要想知道fatjar是如何生成的,就必须知道spring-boot-maven-plugin的工作机制,而spring-boot-maven-plugin是属于自己定义的插件,所以一定要知道Maven的自定义插件是如何工作的Maven的自定义插件Maven有三套独立的生命周期:clean、default和site,每个生命周期都包含一些阶段阶段,阶段是连续的,后面的阶段依赖于前面的阶段。生命周期的phase阶段与插件完成实际构建任务的目标绑定。*
**/jar中的资源分隔符是!/,即JarFileURLJDK提供的只支持一个'!/',而Springboot扩展了这个协议,让它支持多个'!/',可以表示jar中的jar,目录中的jar,fatjar资源。最基本的自定义类加载机制:BootstrapClassLoader(加载JDK/lib目录下的类)次要基础:ExtensionClassLoader(加载JDK/lib/ext目录下的类)常见:ApplicationClassLoader(程序自身classpath下的类))首先要注意双亲委托机制。如果一个类可以被委托的最基本的ClassLoader加载,那么它就不能被高级的ClassLoader加载。这是为了引入一个不在JDK下但类名相同的类。.第二,如果在这种机制下,fatjar依赖的各种第三方jar文件不在程序自己的classpath中,也就是说,如果我们使用双亲委派机制,我们将无法获取到jar我们完全依赖的包,所以我们需要修改双亲委托机制的类查找方法,自定义类加载机制。先简单介绍一下Springboot2中的LaunchedURLClassLoader,它继承了java.net.URLClassLoader,重写了java.lang.ClassLoader#loadClass(java.lang.String,boolean),然后我们讨论他是如何修改双亲委托机制的。上面我们提到了Springboot支持多个'!/'来表示多个jar,而我们的问题就是如何解决查找这多个jar包的问题。我们来看看LaunchedURLClassLoader的构造方法。publicLaunchedURLClassLoader(URL[]urls,ClassLoaderparent){super(urls,parent);}urls注释解释了从中加载类和资源的url,即fatjar包依赖的所有类和资源,urls参数传递给父类java.net.URLClassLoader,java.net.URLClassLoader。父类的net.URLClassLoader#findClass执行搜索类方法。该类的搜索源是构造方法传入的urls参数。//LaunchedURLClassLoader的实现protectedClass>loadClass(Stringname,booleanresolve)throwsClassNotFoundException{Handler.setUseFastConnectionExceptions(true);try{try{//尝试根据类名定义类所在的包,即java.lang。打包,确保jar中jar中匹配的manifest可以关联到关联的//packagedefinePackageIfNecessary(name);}catch(IllegalArgumentExceptionex){//由于具有并行能力而容忍竞争条件if(getPackage(name)==null){//这不应该发生,因为IllegalArgumentException指示//包已经被定义,因此,//getPackage(name)不应返回null。//这里的异常说明definePackageIfNecessary方法的作用其实是预先过滤掉找不到的包thrownewAssertionError("包"+name+"已经被"+"定义但找不到");}}返回super.loadClass(名称,解析);}最后{Handler.setUseFastConnectionExceptions(false);}}方法super.loadClass(name,resolve)实际上会返回java.lang.ClassLoader#loadClass(java.lang.String,boolean),遵循双亲委派机制搜索类,BootstrapClassLoader和ExtensionClassLoader不会能够找到fatjar依赖的类,最终会来到ApplicationClassLoader,调用java.net.URLClassLoader#findClass如何真正启动Springboot2和Springboot1最大的区别是Springboo1会新建一个线程去执行对应的反射调用逻辑,而SpringBoot2去掉了新建线程的步骤该方法是org.springframework.boot.loader.Launcher#launch(java.lang.String[],java.lang.String,java.lang.ClassLoader)。反射调用逻辑比较简单,这里就不分析了,但是重点是,在调用main方法之前,将当前线程的上下文类加载器设置为LaunchedURLClassLoaderprotectedvoidlaunch(String[]args,StringmainClass,ClassLoaderclassLoader)throwsException{Thread.currentThread().setContextClassLoader(classLoader);createMainMethodRunner(mainClass,args,classLoader).run();}Demopublicstaticvoidmain(String[]args)throwsClassNotFoundException,MalformedURLException{JarFile.registerUrlProtocolHandler();//构造LaunchedURLClassLoader类加载器,这里使用2个URL,对应jar包依赖spring-boot-loader和spring-boot包,以“!/”分隔,需要org.springframework.boot.loader。jar.Handler处理器处理LaunchedURLClassLoaderclassLoader=newLaunchedURLClassLoader(newURL[]{newURL("jar:file:/E:/IdeaProjects/oneday-auth/oneday-auth-server/target/oneday-auth-server-1.0.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-loader-1.2.3.RELEASE.jar!/"),新URL("jar:file:/E:/IdeaProjects/oneday-auth/oneday-auth-server/target/oneday-auth-server-1.0.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot-2.1.3.RELEASE.jar!/")},Application.class.getClassLoader());//加载Class//这两个类会在第二步本地查找(URLClassLoader的findClass方法)classLoader.loadClass("org.springframework.boot.loader.JarLauncher");classLoader.loadClass("org.springframework.boot.SpringApplication");//第三步,默认加载顺序在ApplicationClassLoaderclassLoader.loadClass("org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration");//SpringApplication.run(Application.class,args);
