专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

ampampquot类加载器ampampquot与ampam

  引子
  大家好,我是呼噜噜,大家想必都有过平时开发springboot项目的时候稍微改动一点代码,就得重启,就很烦
  网上一般介绍2种方式springbootdevtools,或者通过JRebel插件来实现热部署
  热部署就是当应用正在运行时,修改应用不需要重启应用。
  其中springbootdevtools其实是自动重启,主要是节省了我们手动点击重启的时间,不算真正意义上的热部署。JRebel插件啥都好,就是需要收费
  但如果平时我们在调试debug的情况下,只是在方法块内代码修改了一下,我们还得重启项目,就很浪费时间。这个时候我们其实可以直接build,不重启项目,即可实现热部署。
  我们先来写一个例子演示一下:RestControllerpublicclassTestController{RequestMapping(valuetest,method{RequestMethod。GET,RequestMethod。POST})publicvoidtestclass(){Stringnamezj;intweight100;System。out。println(name:name);System。out。println(weight:weight);}}
  结果:
  name:zjweight:100
  修改代码,然后直接build项目,不重启项目,我们再请求这个测试接口:Stringnameming;intweight300;
  神奇的一幕出现了,结果为:
  name:mingweight:300
  当我们修改。java文件,只需重新生成对应的。class文件,就能影响到程序运行结果,无需重启,Why?背后JVM的操作原理且看本文娓娓道来。了解。class文件
  首先我们得先了解一下什么是。class文件
  举个简单的例子,创建一个Person类:publicclassPerson{状态or属性Stringname;姓名Stringsex;性别intheight;身高intweight;体重行为publicvoidsleep(){System。out。println(this。name睡觉);}publicvoideat(){System。out。println(吃饭);}publicvoidDance(){System。out。println(跳舞);}}
  我们执行javac命令,生成Person。class文件
  然后我们通过vim16进制打开它打开file文件vimPerson。class在命令模式下输入。。以16进制显示:!xxd在命令模式下输入。。切换回默认显示:!xxdr
  不同的操作系统,不同的CPU具有不同的指令集,JAVA能做到平台无关性,依靠的就是Java虚拟机。。java源码是给人类读的,而。class字节码是给JVM虚拟机读的,计算机只能识别0和1组成的二进制文件,所以虚拟机就是我们编写的代码和计算机之间的桥梁。
  虚拟机将我们编写的。java源程序文件编译为字节码格式的。class文件,字节码是各种虚拟机与所有平台统一使用的程序存储格式,class文件主要用于解决平台无关性的中间文件
  类加载的过程
  在之前的一篇文章谈谈JAVA中对象和类、this、super和static关键字中,我们知晓Java是如何创建对象的PersonzhangnewPerson();
  虽然我们写的时候是简单的一句,但是JVM内部的实现过程却是复杂的:将硬盘上指定位置的Person。class文件加载进内存
  执行main方法时,在栈内存中开辟了main方法的空间(压栈进栈),然后在main方法的栈区分配了一个变量zhang。
  执行new,在堆内存中开辟一个实体类的空间,分配了一个内存首地址值
  调用该实体类对应的构造函数,进行初始化(如果没有构造函数,Java会补上一个默认构造函数)。
  将实体类的首地址赋值给zhang,变量zhang就引用了该实体。(指向了该对象)
  其中上图步骤1Classloader(类加载器)将class文件加载到内存中具体分为3个步骤:加载、连接、初始化
  类的生命周期一般有如下图有7个阶段,其中阶段15为类加载过程,验证、准备、解析统称为连接
  加载
  加载阶段:指的是将类对应的。class文件中的二进制字节流读入到内存中,将这个字节流转化为方法区的运行时数据结构,然后在堆区创建一个java。lang。Class对象,作为对方法区中这些数据的访问入口
  相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是我们最可以控制的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义类加载器来完成加载。这个我们文章后面再详细讲验证
  验证阶段:校验字节码文件正确性。这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
  这部分对开发者而言是无法干预的,以下内容了解即可
  更多精彩文章在公众号小牛呼噜噜
  验证阶段大致会完成4个阶段的检验动作:
  文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java。lang。Object之外。字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。符号引用验证:确保解析动作能正确执行。验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。准备
  准备阶段:为类变量(static修饰的变量)分配内存,并将其初始化为默认值注意此阶段仅仅是为类变量即静态变量分配内存,并将其初始化为默认值举个例子,在这个准备阶段:staticintvalue3;类变量初始化,设为默认值0,不是3哦!!!intnum4;类成员变量,在这个阶段不初始化;在new类,调用对应类的构造函数才进行初始化finalstaticvalFin5;这个比较特殊,在这个阶段也不会分配内存!!!
  注意:valFin是被finalstatic修饰的常量在编译的时候已分配好了,所以在准备阶段此时的值为5,所以在这个阶段也不会初始化!解析
  解析阶段:是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
  符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
  这个阶段了解一下即可初始化
  直到初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。
  初始化阶段是类加载过程的最后一个步骤,之前介绍的几个类加载的动作里,除了在加载阶段用户应用程序可以通过自定义类加载器的方式局部参与外,其余动作都完全由Java虚拟机来主导控制。Java程序对类的使用方式可分为两种:主动使用与被动使用。一般来说只有当对类的首次主动使用的时候才会导致类的初始化,所以主动使用又叫做类加载过程中初始化开始的时机。
  类实例初始化方式,主要是以下几种:
  1、创建类的实例,也就是new的方式2、访问某个类或接口的静态变量,或者对该静态变量赋值3、调用类的静态方法4、反射(如Class。forName(com。test。Person))5、初始化某个类的子类,则其父类也会被初始化6、Java虚拟机启动时被标明为启动类的类(JavaTest),还有就是Main方法的类会首先被初始化
  这边就不展开说了,大家记住即可使用
  当JVM完成初始化阶段之后,JVM便开始从入口方法开始执行用户的程序代码卸载
  当用户程序代码执行完毕后,JVM便开始销毁创建的Class对象,最后负责运行的JVM也退出内存在如下几种情况下,Java虚拟机将结束生命周期
  执行了System。exit()方法程序正常执行结束程序在执行过程中遇到了异常或错误而异常终止由于操作系统出现错误而导致Java虚拟机进程终止类加载器与双亲委派机制
  上文类加载过程中,是需要类加载器的参与,类加载器在Java中非常重要,它使得Java类可以被动态加载到Java虚拟机中并执行
  那什么是类加载器?通过一个类的全限定名来获取描述此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java。lang。Class对象实例
  Java虚拟机支持类加载器的种类:主要包括3中:引导类加载器(BootstrapClassLoader)、扩展类加载器(ExtensionClassLoader)、应用类加载器(系统类加载器,AppClassLoader),另外我们还可以自定义加载器用户自定义类加载器
  引导类加载器(BootstrapClassLoader):BootStrapClassLoader是由c实现的。引导类加载器加载java运行过程中的核心类库JRElibrt。jar,sunrsasign。jar,charsets。jar,jce。jar,jsse。jar,plugin。jar以及存放在JREclasses里的类,也就是JDK提供的类等常见的比如:Object、Stirng、List等扩展类加载器(ExtensionClassLoader):它用来加载jrelibext目录以及java。ext。dirs系统变量指定的类路径下的类。应用类加载器(AppClassLoader):它主要加载应用程序ClassPath下的类(包含jar包中的类)。它是java应用程序默认的类加载器。其实就是加载我们一般开发使用的类用户自定义类加载器:用户根据自定义需求,自由的定制加载的逻辑,只需继承应用类加载器AppClassLoader,负责加载用户自定义路径下的class字节码文件线程上下文类加载器:除了以上列举的三种类加载器,其实还有一种比较特殊的类型就是线程上下文类加载器。ThreadContextClassLoader可以是上述类加载器的任意一种,这个我们下文再细说
  我们来看一个例子:publicclassTestClassLoader{publicstaticvoidmain(String〔〕args)throwsClassNotFoundException{ClassLoaderclassLoaderTestClassLoader。class。getClassLoader();System。out。println(classLoader);System。out。println(classLoader。getParent());获取其父类加载器System。out。println(classLoader。getParent()。getParent());获取父类的父类加载器}}
  结果:sun。misc。Launcher
  ExtClassLoader5caf905dnull
  结果显示分别打印应用类加载器、扩展类加载器和引导类加载器由于引导类加载器是由c实现的,所以并不存在一个Java的类,因此会打印出null我们还可以看到结果里面打印了sun。misc。Launcher,这个是什么东东?
  其实Launcher是JRE中用于启动程序入口main()的类,我们看下Launcher的源码:publicclassLauncher{privatestaticLauncherlaunchernewLauncher();privatestaticStringbootClassPathSystem。getProperty(sun。boot。class。path);publicstaticLaunchergetLauncher(){returnlauncher;}privateClassLoaderloader;publicLauncher(){CreatetheextensionclassloaderClassLoaderextcl;try{extclExtClassLoader。getExtClassLoader();加载扩展类类加载器}catch(IOExceptione){thrownewInternalError(Couldnotcreateextensionclassloader,e);}Nowcreatetheclassloadertousetolaunchtheapplicationtry{loaderAppClassLoader。getAppClassLoader(extcl);加载应用程序类加载器,并设置parent为extClassLoader}catch(IOExceptione){thrownewInternalError(Couldnotcreateapplicationclassloader,e);}Thread。currentThread()。setContextClassLoader(loader);设置AppClassLoader为线程上下文类加载器}Returnstheclassloaderusedtolaunchthemainapplication。publicClassLoadergetClassLoader(){returnloader;}Theclassloaderusedforloadinginstalledextensions。staticclassExtClassLoaderextendsURLClassLoader{}Theclassloaderusedforloadingfromjava。class。path。runsinarestrictedsecuritycontext。staticclassAppClassLoaderextendsURLClassLoader{}
  其中loaderAppClassLoader。getAppClassLoader(extcl);的核心方法源码如下:privateClassLoader(Voidunused,ClassLoaderparent){this。parentparent;设置parentif(ParallelLoaders。isRegistered(this。getClass())){parallelLockMapnewConcurrentHashMap();package2certsnewConcurrentHashMap();assertionLocknewObject();}else{nofinergrainedlock;lockontheclassloaderinstanceparallelLockMapnull;package2certsnewHashtable();assertionLockthis;}}
  通过以上源码我们可以知晓:Launcher的ClassLoader是BootstrapClassLoader,在Launcher创建的同时,还会同时创建ExtClassLoader,AppClassLoader(并设置其parent为extClassLoader)。其中代码中sun。boot。class。path是BootstrapClassLoader加载的jar包路径。这几种类加载器都遵循双亲委派机制
  双亲委派机制说的其实就是,当一个类加载器收到一个类加载请求时,会去判断有没有加载过,如果加载过直接返回,否则该类加载器会把请求先委派给父类加载器。每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载。
  更多精彩文章在公众号小牛呼噜噜
  双亲委派模式优势:避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,这样保证了每个类只被加载一次。保护程序安全,防止核心API被随意篡改,比如java核心api中定义类型不会被随意替换
  我们这里看一个例子:
  我们新建一个自己的类String放在srcjavalang目录下publicclassString{static{System。out。println(自定义String类);}}
  新建StringTest类:publicclassStringTest{publicstaticvoidmain(String〔〕args){Stringstrnewjava。lang。String();System。out。println(starttest);}}
  结果:
  starttest
  可以看出,程序并没有运行我们自定义的String类,而是直接返回了String。class。像String,Integer等类是JAVA中的核心类,是不允许随意篡改的!ClassLoader
  ClassLoader是一个抽象类,负责加载类,像ExtClassLoader,AppClassLoader都是由该类派生出来,实现不同的类装载机制。这块的源码太多了,就不贴了。
  我们来看下它的核心方法loadClass(),传入需要加载的类名,它会帮你加载:protectedClasslt;?loadClass(Stringname,booleanresolve)throwsClassNotFoundException{synchronized(getClassLoadingLock(name)){一开始先检查是否已经加载该类Classlt;?cfindLoadedClass(name);if(cnull){longt0System。nanoTime();try{如果未加载过类,则遵循双亲委派机制,来加载类if(parent!null){cparent。loadClass(name,false);}else{如果父类是null就是BootstrapClassLoader,使用启动类类加载器cfindBootstrapClassOrNull(name);}}catch(ClassNotFoundExceptione){ClassNotFoundExceptionthrownifclassnotfoundfromthenonnullparentclassloader}if(cnull){longt1System。nanoTime();如果还是没有加载成功,调用findClass(),让当前类加载器加载cfindClass(name);thisisthedefiningclassloader;recordthestatssun。misc。PerfCounter。getParentDelegationTime()。addTime(t1t0);sun。misc。PerfCounter。getFindClassTime()。addElapsedTimeFrom(t1);sun。misc。PerfCounter。getFindClasses()。increment();}}if(resolve){resolveClass(c);}returnc;}}继承的子类得重写该方法protectedClasslt;?findClass(Stringname)throwsClassNotFoundException{thrownewClassNotFoundException(name);}
  loadClass()源码展示了,一般加载。class文件大致流程:先去缓存中检查是否已经加载该类,有就直接返回,避免重复加载;没有就下一步遵循双亲委派机制,来加载。class文件上面两步都失败了,调用findClass()方法,让当前类加载器加载
  注意:由于ClassLoader类是抽象类,而抽象类是无法通过new创建对象的,所以它最核心的findClass()方法,没有具体实现,只抛了一个异常,而且是protected的,这是应用了模板方法模式,具体的findClass()方法丢给子类实现,所以继承的子类得重写该方法。自定义类加载器编写一个自定义的类加载器
  那我们仿照ExtClassLoader,AppClassLoader来实现一个自定义的类加载器,我们同样是继承ClassLoader类
  编写一个测试类TestPersonpublicclassTestPerson{Stringnamexiaoming;publicvoidprint(){System。out。println(hellomynameis:name);}}
  接着编写一个自定义类加载器MyTestClassLoader:publicclassMyTestClassLoaderextendsClassLoader{finalStringclassNameSpecifyTestPerson;publicMyTestClassLoader(){}publicMyTestClassLoader(ClassLoaderparent){super(parent);}protectedClasslt;?findClass(Stringname)throwsClassNotFoundException{FilefilegetClassFile(name);try{byte〔〕bytesgetClassBytes(file);Classlt;?cthis。defineClass(name,bytes,0,bytes。length);returnc;}catch(Exceptione){e。printStackTrace();}returnsuper。findClass(name);}privateFilegetClassFile(Stringname){FilefilenewFile(D:ideaProjectssrcmainjavacomzjideaprojectstest2classNameSpecify。class);returnfile;}privatebyte〔〕getClassBytes(Filefile)throwsException{这里要读入。class的字节,因此要使用字节流FileInputStreamfisnewFileInputStream(file);FileChannelfcfis。getChannel();ByteArrayOutputStreambaosnewByteArrayOutputStream();WritableByteChannelwbcChannels。newChannel(baos);ByteBufferbyByteBuffer。allocate(1024);while(true){intifc。read(by);if(i0i1)break;by。flip();wbc。write(by);by。clear();}fis。close();returnbaos。toByteArray();}我们这边要打破双亲委派模型,重写整个loadClass方法OverridepublicClasslt;?loadClass(Stringname)throwsClassNotFoundException{Classlt;?cfindLoadedClass(name);if(cnullname。contains(classNameSpecify)){指定的类,不走双亲委派机制,自定义加载cfindClass(name);if(c!null){returnc;}}returnsuper。loadClass(name);}}
  最后在编写一个测试controller:RestControllerpublicclassTestClassController{RequestMapping(valuetestClass,method{RequestMethod。GET,RequestMethod。POST})publicvoidtestClassLoader()throwsClassNotFoundException,InstantiationException,IllegalAccessException,NoSuchMethodException,InvocationTargetException{MyTestClassLoadermyTestClassLoadernewMyTestClassLoader();Classlt;?c1Class。forName(com。zj。ideaprojects。test2。TestPerson,true,myTestClassLoader);Objectobjc1。newInstance();System。out。println(当前类加载器:obj。getClass()。getClassLoader());obj。getClass()。getMethod(print)。invoke(obj);}}
  先找到TestPerson所在的目录,执行命令:javacTestPerson,生成TestPerson。class这里没有使用idea的build,是因为我们代码的class读取路径是写死了的,不走默认CLASSPATH
  D:ideaProjectssrcmainjavacomzjideaprojectsest2TestPerson。class
  我们然后用postman调用testClassLoader()测试接口
  结果:
  当前类加载器:com。zj。ideaprojects。test2。MyTestClassLoader1d75e392hellomynameis:xiaoming
  然后修改TestPerson,将name改为xiaoniupublicclassTestPerson{Stringnamexiaoniu;publicvoidprint(){System。out。println(hellomynameis:name);}}
  然后在当前目录重新编译,执行命令:javacTestPerson,会在当前目录重新生成TestPerson。class不重启项目,直接用postman直接调这个测试接口结果:
  当前类加载器:com。zj。ideaprojects。test2。MyTestClassLoader7091bd27hellomynameis:xiaoniu
  这样就实现了热部署!!!
  为什么我们这边要打破双亲委派机制?
  如果不打破的话,结果当前类加载器会显示sun。misc。LauncherAppClassLoader,原因是由于idea启动项目的时候会自动帮我们编译,将class放到CLASSPATH路径下。其实可以把默认路径下的。class删除也行。这里也是为了展示如何打破双亲委派机制,才如此实现的。
  官方推荐我们自定义类加载器时,遵循双亲委派机制。但是凡事得看实际需求嘛
  自定义类加载器时,如何打破双亲委派机制
  通过上面的例子我们可以看出:1、如果不想打破双亲委派机制,我们自定义类加载器,那么只需要重写findClass方法即可2、如果想打破双亲委派机制,我们自定义类加载器,那么还得重写整个loadClass方法SPI机制与线程上下文类加载器
  如果你阅读到这里,你会发现双亲委派机制的各种好处,但万物都不是绝对正确的,我们需要一分为二地看待问题。
  在某些场景下双亲委派制过于局限,所以有时候必须打破双亲委派机制来达到目的。比如:SPI机制、线程上下文类加载器SPI(ServiceProviderInterface)服务提供接口。它是jdk内置的一种服务发现机制,将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是让服务定义与实现分离、解耦。
  线程上下文类加载器(contextclassloader)是可以破坏Java类加载委托机制,使程序可以逆向使用类加载器,使得java类加载体系显得更灵活。
  Java应用运行的初始线程的上下文类加载器是应用类加载器,在线程中运行的代码可以通过此类加载器来加载类和资源。Java。lang。Thread中的方法getContextClassLoader()和setContextClassLoader(ClassLoadercl)用来获取和设置线程的上下文类加载器。如果没有通过setContextClassLoader(ClassLoadercl)方法进行设置的话,线程将继承其父线程的上下文类加载器。
  更多精彩文章在公众号小牛呼噜噜
  SPI机制在框架的设计上应用广泛,下面举几个常用的例子:JDBC
  平时获取jdbc,我们可以这样:ConnectionconnectionDriverManager。getConnection(jdbc:localhost:3306);
  我们读DriverManager的源码发现:其实就是查询classPath下,所有METAINF下给定Class名的文件,并将其内容返回,使用迭代器遍历,这里遍历的内部使用Class。forName加载了类。
  其中有一处非常重要ServiceLoaderloadedDriversServiceLoader。load(Driver。class);我们看下它的实现:publicstaticSServiceLoaderSload(ClassSservice){ClassLoaderclThread。currentThread()。getContextClassLoader();important!returnServiceLoader。load(service,cl);}
  我们可以看出JDBC,DriverManager类和ServiceLoader类都是属于核心库rt。jar的,它们的类加载器是BootstrapClassLoader类加载器。而具体的数据库驱动相关功能却是第三方提供的,第三方的类不能被引导类加载器(BootstrapClassLoader)加载。
  所以java。util。ServiceLoader类进行动态装载时,使用了线程的上下文类加载器(ThreadContextClassLoader)让父级类加载器能通过调用子级类加载器来加载类,这打破了双亲委派机制。Tomcat
  Tomcat是web容器,我们把war包放到tomcat的webapp目录下,这意味着一个tomcat可以部署多个应用程序。
  不同的应用程序可能会依赖同一个第三方类库的不同版本,但是不同版本的类库中某一个类的全路径名可能是一样的。防止出现一个应用中加载的类库会影响另一个应用的情况。如果采用默认的双亲委派类加载机制,那么是无法加载多个相同的类。
  如果Tomcat本身的依赖和Web应用还需要共享,Common类加载器(CommonClassLoader)来装载实现共享Catalina类加载器(CatalinaClassLoader)用来隔绝Web应用程序与Tomcat本身的类Shared类加载器(SharedClassLoader):如果WebAppClassLoader自身没有加载到某个类,那就委托SharedClassLoader去加载WebAppClassLoader:为了实现隔离性,优先加载Web应用自己定义的类,所以没有遵照双亲委派的约定,每一个应用自己的类加载器WebAppClassLoader(多个应用程序,就有多个WebAppClassLoader),负责优先加载本身的目录下的class文件,加载不到时再交给CommonClassLoader以及上层的ClassLoader进行加载,这破坏了双亲委派机制。Jsp类加载器(JasperLoader):实现热部署的功能,修改文件不用重启就自动重新装载类库。JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个。Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能。
  我们来模拟一下tomcat多个版本代码共存:
  这边的例子换了个电脑,所以目录结构、路径与上面的例子有点变化
  我们先编写App类publicclassApp{Stringnamewebapp1;publicvoidprint(){System。out。println(thisisname);}}
  javacApp生成的App。class放入tomcatTestwar1comzjdemotestomcatTest目录下
  然后将name改为webapp2,重新生成的App。class放入tomcatTestwar2comzjdemotestomcatTest目录下
  更多精彩文章在公众号小牛呼噜噜
  然后我们编写类加载器:publicclassMyTomcatClassloaderextendsClassLoader{privateStringclassPath;publicMyTomcatClassloader(StringclassPath){this。classPathclassPath;}OverrideprotectedClasslt;?findClass(Stringname)throwsClassNotFoundException{FilefilegetClassFile(name);try{byte〔〕bytesgetClassBytes(file);Classlt;?cthis。defineClass(name,bytes,0,bytes。length);returnc;}catch(Exceptione){e。printStackTrace();}returnsuper。findClass(name);}privateFilegetClassFile(Stringname){namename。replaceAll(。,);FilefilenewFile(classPathname。class);拼接路径,找到class文件returnfile;}privatebyte〔〕getClassBytes(Filefile)throwsException{这里要读入。class的字节,因此要使用字节流FileInputStreamfisnewFileInputStream(file);FileChannelfcfis。getChannel();ByteArrayOutputStreambaosnewByteArrayOutputStream();WritableByteChannelwbcChannels。newChannel(baos);ByteBufferbyByteBuffer。allocate(1024);while(true){intifc。read(by);if(i0i1){break;}by。flip();wbc。write(by);by。clear();}fis。close();returnbaos。toByteArray();}我们这边要打破双亲委派模型,重写整个loadClass方法OverridepublicClasslt;?loadClass(Stringname)throwsClassNotFoundException{Classlt;?cfindLoadedClass(name);if(cnullname。contains(tomcatTest)){指定的目录下的类,不走双亲委派机制,自定义加载cfindClass(name);if(c!null){returnc;}}returnsuper。loadClass(name);}}
  最后编写测试controller:RestControllerpublicclassTestController{RequestMapping(valuetestTomcat,method{RequestMethod。GET,RequestMethod。POST})publicvoidtestclass()throwsClassNotFoundException,IllegalAccessException,InstantiationException,NoSuchMethodException,InvocationTargetException{MyTomcatClassloadermyTomcatClassloadernewMyTomcatClassloader(D:GiteeProjectsstudyjavademotestsrcmainjavacomzjdemotesttomcatTestwar1);ClassclmyTomcatClassloader。loadClass(com。zj。demotest。tomcatTest。App);Objectobjcl。newInstance();System。out。println(当前类加载器:obj。getClass()。getClassLoader());obj。getClass()。getMethod(print)。invoke(obj);MyTomcatClassloadermyTomcatClassloader22newMyTomcatClassloader(D:GiteeProjectsstudyjavademotestsrcmainjavacomzjdemotesttomcatTestwar2);Classcl22myTomcatClassloader22。loadClass(com。zj。demotest。tomcatTest。App);Objectobj22cl22。newInstance();System。out。println(当前类加载器:obj22。getClass()。getClassLoader());obj22。getClass()。getMethod(print)。invoke(obj22);}}
  然后postman调一下这个接口,结果:
  当前类加载器:com。zj。demotest。tomcatTest。MyTomcatClassloader18fbb876thisiswebapp1当前类加载器:com。zj。demotest。tomcatTest。MyTomcatClassloader5f7ed4a9thisiswebapp2
  我们发现2个同样的类能共存在同一个JVM中,互不影响。
  注意:同一个JVM内,2个相同的包名和类名的对象是可以共存的,前提是他们的类加载器不一样。所以我们要判断多个类对象是否是同一个,除了要看包名和类名相同,还得注意他们的类加载器是否一致SpringBootStarter
  springboot自动配置的原因是因为使用了EnableAutoConfiguration注解。
  当程序包含了EnableAutoConfiguration注解,那么就会执行下面的方法,然后会加载所有spring。factories文件,将其内容封装成一个map,spring。factories其实就是一个名字特殊的properties文件。
  在springboot应用启动时,会调用loadFactoryNames方法,其中传递的一个参数就是:org。springframework。boot。autoconfigure。EnableAutoConfigurationprotectedListStringgetCandidateConfigurations(AnnotationMetadatametadata,AnnotationAttributesattributes){ListStringconfigurationsSpringFactoriesLoader。loadFactoryNames(this。getSpringFactoriesLoaderFactoryClass(),this。getBeanClassLoader());Assert。notEmpty(configurations,NoautoconfigurationclassesfoundinMETAINFspring。factories。Ifyouareusingacustompackaging,makesurethatfileiscorrect。);returnconfigurations;}
  METAINFspring。factories会被读取到。
  它还使用了this。getBeanClassLoader()获取类加载器。所以我们立刻明白了文章一开始的例子,SpringBoot项目直接build项目,不重启项目,就能实现热部署效果。尾语
  类加载器是Java语言的一个创新,它使得动态安装和更新软件组件成为可能。同时我们应该了解双亲委派机制的优缺点和应用场景,这些可能比较难但对于我们来说却很重要。
  本篇文章到这里就结束啦,很感谢靓仔你能看到最后,如果觉得文章对你有帮助,别忘记关注我!更多精彩的文章
  计算机内功、JAVA源码、职业成长、项目实战、面试相关资料等更多精彩文章在公众号小牛呼噜噜
  参考资料:
  《深入理解Java虚拟机:JVM高级特性与最佳实践》
  https:www。cnblogs。comkeyip7203170。html
  https:www。cnblogs。comszlbmp5504631。html

2008年,那个唱了北京欢迎你第一句的9岁女孩,如今怎样了?2004年8月,5岁多的北京女孩陈天佳被张艺谋挑中,在雅典奥运会的闭幕仪式上,演唱茉莉花。8月30日凌晨,陈天佳手拿灯笼,在亿万人的关注下,唱了茉莉花。因为一首歌,她走红了,此时,写给对原生家庭失望的女孩遗憾之外有美好嗨这里是微梦小院,会分享关于个人成长情感心事等温暖治愈内容,累了乏了可以来这瞧瞧。希望与你,在这里,彼此成全,相伴成长。文姜茶图健听女孩她不是下凡的天使,可她救了我。姜茶大家都喜欢10月最美的6条自驾路线,条条经典,无需计划,赏秋不拥挤,出发10月是收获的季节!此时蟹肥菊黄,稻香谷丰,华夏大地一片丰收景象。10月也是自驾的季节!此时是一年中最好的黄金时代,三万里的秋光已遍染华夏大地。来吧,一起出发,跟随着走吧网的视角,我开始觉得,人生应该主动点我从心底深深的知道,我是一个非常普通的人,普通的外表,普通的家庭,普通的聪明,但我确实在我经历的每个环境中,都做到了主动,主动地抓取每个创造机会的可能,并且主动地持续保持学习。我把黄企生随笔慧语人生目标是人生成功不可忽缺的路径慧语人生目标是人生成功不可忽缺的路径作者黄企生所谓目标,就是指要达到的境界或标准,是对行动或活动预期结果的主观设想。人生要成功,必须要有正确恰当合理的目标,唯有这样,人生才有努力和20!日本队挑落世界第14强队,妖星队被打回原形,日媒狂赞北京时间9月23日,麒麟杯,日本队对阵美国队。最终,日本队20击败对手,赢得了一场颇具含金量的比赛。日本队目前世界排名第24位,在亚洲排名第二。美国队世界排名高居第14位,在中北美01!英格兰惨遭羞辱,意大利太狠,送世界第5降级!留力世界杯?北京时间9月24日,欧国联比赛继续进行,英格兰客场挑战意大利。结果主力尽出,派出豪阵的英格兰惨遭羞辱,被未打进世界杯的意大利10击败!意大利太狠了,送世界第5降至欧国联B级联赛,而恭喜全红蝉!职称升一级,工资涨3万,统治世界跳水,哥哥做网红要说现如今中国体坛人气最高的运动员,绝对是15岁的中国跳水名将全红蝉,自从2021年东京奥运会三跳满分466。2分历史最高分拿到10米跳台金牌以后,全红蝉的名字就享誉海内外。9月份世界杯卡塔尔冬季温度如何?除974体育场外,所有体育场都将在比赛期间为观众和球员配备空调,但可能不需要空调,因为11月和12月的气温不太可能超过摄氏30度。在比赛期间,看台的温度将被控制在摄氏21至22度之散文秋意渐深,感悟人生安暖作者子墨秋意渐浓,轻轻触碰着秋日晨曦,阳光明媚,透过树叶的缝隙,落在大地上,斑驳光影,交错陆离,好似落满往事的一幅小画。窗内,在一本泛黄的书中,翻阅着,寻找着,曾经的光阴,老去的故变性人李二毛,被男友抛弃后摘除假体,因感染艾滋孤独离世变性人李二毛被两任男友抛弃摘除假体后,患上ai滋病的他孤独地死在了离老家不足2里的出租屋内,被发现时已经过去了3天!他终究没有回到那个魂牵梦绕的家乡,尽管村里的人都厌弃他。他也想身
开年第一场红毯孙俪像微商老板娘,张靓颖让人看不懂,谢娜整容2月10日,女星齐聚成都,迎来了开年的第一次红毯秀。平均40岁的内娱女明星状态,颜值还是非常扛打,但是这造型就有些一言难尽了,可能是假期刚过还没有把精力投放到工作中?佟丽娅今年39经典的八五式军装之男干部长袖衬衣85干部长袖衬衣八五式军装可以说是我军历史上装备时间最短的一款军装,从装备到换装仅3年多时间,但也为恢复军衔制打下了良好基础。随着国家经济形势的发展,八五式军装相比较六五式增加了部内娱女明星最新红毯!谢娜绝美孙俪翻车,佟丽娅黑色礼服好高级昨晚的WSJ年度出色人物晚宴,大部分娱乐圈的明星都来到了现场,红毯上女明星们身穿华丽的服装,让现场更是尤为的星光璀璨,而很多女明星的造型也都十分的出彩,不再是常规的礼服造型,而是更一双草鞋14500元,真潮流还是智商税?!西班牙奢侈品牌罗意威(LOEWE)2023春夏男士系列运动鞋Grasssneaker悄悄上架了,已正式在中国内地门店发售。这双真草鞋国内售价为14500元,美国官网售价为1700美宗筋韧性变差?精气不足,不得濡养!一张补精固精方收好人到了一定年龄之后,总是羞于回看年轻时说过的话做过的事。都说人不会变,但随着年龄的增长,人的心境会发生很大的转变,再看自己年轻时说过的话做过的事,多数人都会觉得那不是真正的自己。年一条鱼的使命它是一条鱼,没错,可以叫它小鱼。小鱼是一条白鱼。它和它的伙伴,一般都住在一条大江里。人们都叫它们长江白鱼。它们在长江里无忧无虑的成长,看潮起潮落,赏孤帆远影,望白云悠悠。它们喜欢波3000余家企业带来明星产品,顺义新国展迎开年首展北京日报客户端记者王可心武亦彬2月11日,顺义新国展迎来2023年的首场展会,为期四天的第32届中国国际汽车服务用品及设备展览会暨首届中国国际新能源汽车供应链大会正式开幕,展会首日春运路上光与影春运路上光与影中国青年报客户端2月10日,北京西站,旅客在长廊中穿梭,准备进入各自的候车大厅。来自国务院联防联控机制春运工作专班数据显示,2月9日(春运第34天,农历正月十九)全国以红色名村建设助力乡村振兴习近平总书记指出红色资源是我们党艰辛而辉煌奋斗历程的见证,是最宝贵的精神财富。红色血脉是中国共产党政治本色的集中体现,是新时代中国共产党人的精神力量源泉。2020年以来,江西把红色小吃赛道不断扩容企业借连锁化快速扩张本报记者钟楚涵蒋政上海报小吃赛道正在升温。近年来,不少新品牌在小吃赛道崛起,比如喜姐炸串夸父炸串愚公炸串黑色经典等。其中有品牌已经获得资本数轮投资,在短短数年之间已经发展到了数千家1170亿元14项重大铁路公路机场项目获批据统计,近期又有14项重大铁路公路机场项目可研报告初步设计项目建设用地施工图设计核准建议书等获得国家省级发改委省交通厅自然资源部市政府等批复,项目总投资额约1170亿元。其中部分项
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网