Java类加载机制深度分析

为什么80%的码农都做不了架构师?>>>   hot3.png


Java类加载机制

类加载是Java程序运行的第一步,研究类的加载有助于了解JVM执行过程,并指导开发者采取更有效的措施配合程序执行。研究类加载机制的第二个目的是让程序能动态的控制类加载,比如热部署等,提高程序的灵活性和适应性。
  
在java.lang包里有个ClassLoader类,ClassLoader 的基本目标是对类的请求提供服务,按需动态装载类和资源,只有当一个类要使用(使用new 关键字来实例化一个类)的时候,类加载器才会加载这个类并初始化。一个Java应用程序可以使用不同类型的类加载器。例如Web Application Server中,Servlet的加载使用开发商自定义的类加载器, java.lang.String在使用JVM系统加载器。

在JVM里由类名和类加载器区别不同的Java类型。因此,JVM允许我们使用不同的加载器加载相同namespace的java类,而实际上这些相同namespace的java类可以是完全不同的类。这种机制可以保证JDK自带的java.lang.String是唯一的。

一、Java类加载器
  
顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。
  
二、java.class.ClassLoader类介绍

java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例。除此之外,ClassLoader还负责加载 Java 应用所需的资源,如图像文件和配置文件等。
  
ClassLoader 中与加载类相关的方法:

getParent()           返回该类加载器的父类加载器。
loadClass(String name)      加载名称为 name的类,返回的结果是 java.lang.Class类的实例。
findClass(String name)      查找名称为 name的类,返回的结果是 java.lang.Class类的实例。
findLoadedClass(String name)   查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。
defineClass(String name, byte[] b, int off, int len)   把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。
resolveClass(Class<?> c)     链接指定的 Java 类。

对于以上给出的方法,表示类名称的 name参数的值是类的二进制名称。需要注意的是内部类的表示,如 com.example.Sample$1和com.example.Sample$Inner等表示方式。
  
三、类加载器的树状组织结构

Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:

•引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
•扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载Java 类。
•系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过给出的 getParent()方法可以得到。对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器。因为类加载器 Java类如同其它的 Java 类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。树的根节点就是引导类加载器。

ClassLoaderTreeClassLoaderParent2ClassLoaderParent
如以上三图给出了一个典型的类加载器树状组织结构示意图,其中的箭头指向的是父类加载器。

image 
如上图给出了类加载器的树状组织结构演示代码。
  
四、类加载器的代理模式
  
类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载(loadClass)这个类,依次类推。在介绍代理模式背后的动机之前,首先需要说明一下 Java 虚拟机是如何判定两个 Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。
  
比如:一个 Java 类 com.example.Sample,编译之后生成了字节代码文件 Sample.class。两个不同的类加载器 ClassLoaderA和 ClassLoaderB分别读取了这个 Sample.class文件,并定义出两个 java.lang.Class类的实例来表示这个类。这两个实例是不相同的。对于 Java 虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常 ClassCastException。  
image 

如上图代码,但未出现上述的异常,因为FileSystemClassLoader已继承自java.class.ClassLoader类,从而使用了类加载的代理机制,两个类加载过程都是由父类加器完成的,所以不会抛出ClassCastException异常;此处先暂时假定会抛出ClassCastException异常;

有关ClassLoader还有很重要一点:
同一个ClassLoader加载的类文件,只有一个Class实例。但是,如果同一个类文件被不同的ClassLoader载入,则会有两份不同的ClassLoader实例(前提是着两个类加载器不能用相同的父类加载器)。
  
运行结果可以看到,运行时抛出了 java.lang.ClassCastException异常。虽然两个对象 obj1和 obj2的类的名字相同,但是这两个类是由不同的类加载器实例来加载的,因此不被 Java 虚拟机认为是相同的。了解了这一点之后,就可以理解代理模式的设计动机了。代理模式是为了保证 Java 核心库的类型安全。所有Java 应用都至少需要引用 java.lang.Object类,也就是说在运行的时候,java.lang.Object这个类需要被加载到 Java 虚拟机中。如果这个加载过程由 Java 应用自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object类,而且这些类之间是不兼容的。通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。不同的类加载器为相同名称的类创建了额外的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间。
  
五、加载类的过程
  
在前面介绍类加载器的代理模式的时候,提到过类加载器会首先代理给其它类加载器来尝试加载(loadClass)某个类。这就意味着真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。真正完成类的加载工作是通过调用 defineClass来实现的;而启动类的加载过程是通过调用 loadClass来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器(此句可与第四部分一起理解哦)。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器

两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。有点晦涩,这句话可以举例说明:ClassA的类加载器为ClassLoaderA,ClassLoaderB是ClassLoaderA父类加载器,那么当ClassLoaderA初始加载ClassA时,由于类加载器的代理模式,则会调用父类加载器ClassLoaderB来定义ClassA,所以ClassLoaderA叫做ClassA的初始加载器,而ClassLoaderB叫做ClassA的定义加载器,然而ClassA中引用了ClassC,那么当父类加载器ClassLoaderB定义ClassLoaderA时,会初始加载ClassC,所以ClassLoaderB又叫做ClassC的初始加载器,又由于类加载器的代理模式,则会调用ClassLoaderB的父类加载器--系统类加载器来定义ClassC,所以最后系统类加载器叫作ClassC的定义加载器)。

如类 com.example.Outer引用了类 com.example.Inner,则由类 com.example.Outer的定义加载器负责启动类 com.example.Inner的加载过程。
  
方法 loadClass()抛出的是 java.lang.ClassNotFoundException异常;方法 defineClass()抛出的是 java.lang.NoClassDefFoundError异常。类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass方法不会被重复调用(可借类加载这一特点,实现线程安全哦)。

一般简单加载过程:
Java程序运行的场所是内存,当在命令行下执行:java HelloWorld命令的时候,JVM会将HelloWorld.class加载到内存中,并形成一个Class的对象HelloWorld.class。

其中的过程就是类加载过程:

1、寻找jre目录,寻找jvm.dll,并初始化JVM;
2、产生一个Bootstrap Loader(引导类加载器);
3、Bootstrap Loader自动加载Extended Loader(扩展类加载器),并将其父Loader设为Bootstrap Loader。
4、Bootstrap Loader自动加载AppClass Loader(系统类加载器),并将其父Loader设为Extended Loader。
5、最后由AppClass Loader加载HelloWorld类。

image 
如图,给出了类加载器各自搜索目录代码。

类加载器特点:

1、运行一个程序时,总是由AppClass Loader(系统类加载器)开始加载指定的类。
2、在加载类时,每个类加载器会将加载任务上交给其父,如果其父找不到,再由自己去加载。
3、Bootstrap Loader(引导类加载器)是最顶级的类加载器了,其父加载器为null。

六、线程上下文类加载器

类 java.lang.Thread中的方法 getContextClassLoader()setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。

在线程中运行的代码可以通过此类加载器来加载类和资源。
  
前面提到的类加载器的代理模式并不能解决 Java 应用开发中会遇到的类加载器的全部问题。如:Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,如 JAXP 的 SPI 接口定义包含在 javax.xml.parsers包中。这些 SPI 的实现代码很可能是作为 Java 应用所依赖的 jar 包被包含进来,可以通过类路径(CLASSPATH)来找到,如实现了 JAXP SPI 的 Apache Xerces所包含的 jar 包。SPI 接口中的代码经常需要加载具体的实现类。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory类中的 newInstance()方法用来生成一个新的 DocumentBuilderFactory的实例。这里的实例的真正的类是继承自 javax.xml.parsers.DocumentBuilderFactory,由 SPI 的实现所提供的。如在 Apache Xerces 中,实现的类是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。而问题在于,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的SPI 实现的 Java 类一般是由系统类加载器来加载的引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式无法解决这个问题。
  
线程上下文类加载器正好解决了这个问题。如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。此处理解很晦涩,简单说明下个人理解:

1. 首先说明类加载器代理机制解决不了的问题:引导类加载器仅会加载Java核心库,如SPI接口,但是对于第三方对SPI接口实现的Java类,引导类加载器却无法加载(引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库),它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。所以导致代理机制解决不了该问题。但是SPI 实现的 Java 类一般会被系统类加载器来加载(通过类路径CLASSPATH找到),虽然加载了SPI 实现的 Java 类,但导致了SPI接口被引导类加载器加载,SPI接口实现的Java类被系统类加载器加载的困境
2. 那么面对该问题,使用线程上下文类加载器可解决,因为Java 应用的线程的上下文类加载器默认就是系统上下文类加载器,在 SPI 接口的代码中使用线程上下文类加载器(即引导类加载器),就可以成功的加载到 SPI 实现的类。从而引导类加载器将SPI接口与SPI实现的Java类一同加载了。该问题解决。
  
七、另外一种加载类的方法:Class.forName

Class.forName是一个静态方法,同样可以用来加载类。

该方法有两种形式:Class.forName(String name, boolean initialize, ClassLoader loader)和 Class.forName(String className)。

第一种形式的参数 name表示的是类的全名;initialize表示是否初始化类;loader表示加载时使用的类加载器。
第二种形式则相当于设置了参数 initialize的值为 true,loader的值为当前类的类加载器(只要不是自定义的类加载器,便是系统类加载)。

Class.forName的一个很常见的用法是在加载数据库驱动的时候。如 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()用来加载 Apache Derby 数据库的驱动。
  
调用只有一个参数的forName()方法等效于 Class.forName(className, true, loader)。这两个方法,最后都要连接到原生方法forName0(),其定义如下:

private static native Class forName0(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException;

只有一个参数的forName()方法,最后调用的是:forName0(className, true, ClassLoader.getCallerClassLoader());而三个参数的forName(),最后调用的是:forName0(name, initialize, loader);
所以,不管使用的是new 來实例化某个类、或是使用只有一个参数的Class.forName()方法,内部都隐含了“载入类 + 运行静态代码块”的步骤。而使用具有三个参数的Class.forName()方法时,如果第二个参数为false,那么类加载器只会加载类,而不会初始化静态代码块,只有当实例化这个类的时候,静态代码块才会被初始化;如果第二个参数为true,那么类加载器加载类同时,会初始化静态代码块。

注:类加载有三种方式:不同方式加载时,会影响静态代码块执行顺序。

1、命令行启动应用时候由JVM初始化加载。
2、通过Class.forName()方法动态加载。
3、通过ClassLoader.loadClass()方法动态加载。

image

image 

如图,给出了各种加载方式与静态块执行顺序测试类代码。

八、开发自定义类加载

在绝大多数情况下,系统默认提供的类加载器实现已经可以满足需求。在某些情况下,您还是需要为应用开发出自己的类加载器。比如您的应用通过网络来传输 Java 类的字节代码,为了保证安全性,这些字节代码经过了加密处理。这个时候您就需要自己的类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出要在 Java 虚拟机中运行的类来。

文件系统类加载器:

image image
image

网络类加载器:

image image image 
注:大家在看此处时,不要真的以为是由两个自定义加载器加载的类文件,有很大误导,此处是由父类加载器加载完成的,因为加载的类文件与父类加载器是处在同一个project中,导致子类加载器代理给父类加载器时,父类加载器是可以加载到类文件的,从而不会再调用子类加载器的findClass()方法,大家可调试得出结论。我们在使用自定义加载器类时,首先明确的前提是,被加载的类文件,不是当前project产生的,或者是由网络得来,或者是其他本地类文件,否则会产生以上误区。
  
九、类加载器与 Web 容器

对于运行在 Java EE™容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。
  
绝大多数情况下,Web 应用的开发人员不需要考虑与类加载器相关的细节。下面给出几条简单的原则:

•每个 Web 应用自己的 Java 类文件和使用的库的 jar 包,分别放在 WEB-INF/classes和 WEB-INF/lib目录下面。
•多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由所有 Web 应用共享的目录下面。
•当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。

十、总结
  
类加载器是 Java 语言的一个创新。它使得动态安装和更新软件组件成为可能。本文详细介绍了类加载器的相关话题,包括基本概念、代理模式、线程上下文类加载器、与 Web 容器的关系等。开发人员在遇到 ClassNotFoundException和 NoClassDefFoundError等异常的时候,应该检查抛出异常的类的类加载器和当前线程的上下文类加载器,从中可以发现问题的所在。在开发自己的类加载器的时候,需要注意与已有的类加载器组织结构的协调。

GIT@OSC工程路径:http://git.oschina.net/taomk/king-training/tree/master/class-loader

转载于:https://my.oschina.net/xianggao/blog/70826

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/302338.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

北大清华团队编写!200多个科学实验+视频,和爸爸一起在家做

自从2017年2月份教育部从小学一年级起将科学课列入必修课&#xff0c;学校、家长都意识到科学素养对于孩子成长的重要性。好多家长都跃跃欲试&#xff0c;想陪孩子把科学“玩”起来。可是具体到如何给孩子做科学启蒙&#xff0c;面对的问题还真不少&#xff1a;生活中有哪些科学…

如何搭建一个指标体系

2019独角兽企业重金招聘Python工程师标准>>> 今天跟大家聊聊&#xff0c;如何搭建一个指标体系。 1、什么是指标体系 “指标体系”这个概念是应用比较广泛的&#xff0c;我们从正式出版物中摘取一个定义&#xff1a; 指标体系&#xff0c;即统计指标体系&#xff0c…

2018年最后一个月最值得关注的13个优质公号

全世界有3.14 % 的人已经关注了数据与算法之美在这个知识千变万化的时代只有不断学习、充实自我&#xff0c;才能跟上时代以下13个顶级公众号能让你扩宽视野&#xff0c;紧跟时代的潮流近现代史研究通讯ID&#xff1a;jxsdyjtx2015▲长按二维码“识别”关注设置为星标近现代史研…

.NET Worker Service 如何优雅退出

上一篇文章中我们了解了 .NET Worker Service 的入门知识[1]&#xff0c;今天我们接着介绍一下如何优雅地关闭和退出 Worker Service。Worker 类从上一篇文章中&#xff0c;我们已经知道了 Worker Service 模板为我们提供三个开箱即用的核心文件&#xff0c;其中 Worker 类是继…

大数据告诉你,中国女人有多勤奋

全世界只有3.14 % 的人关注了数据与算法之美前段时间&#xff0c;美国国家统计局发布了一组关于世界各国劳动参与率的数据&#xff0c;中国赫然位列世界第一&#xff0c;劳动总量世界第一&#xff0c;劳动参与率世界第一。所谓劳动总量&#xff0c;就是所有工作的人的工作时间的…

get+php+mysql_Apache+PHP+MySql 的安装及配置

每一项技术用的人多了&#xff0c;就会有人将其进行优化&#xff0c;做成一个简单、实用、大众化的工具&#xff0c;这对于初识者来说是非常方便的&#xff0c;但是对于长久学习或工作这方面的人技术人员来说是不可取的&#xff0c;所以还是要学习基础的实用方法。因此&#xf…

记一次 .NET 车联网云端服务 CPU爆高分析

一&#xff1a;背景 1. 讲故事前几天有位朋友wx求助&#xff0c;它的程序CPU经常飙满&#xff0c;没找到原因&#xff0c;希望帮忙看一下。这些天连续接到几个cpu爆高的dump&#xff0c;都看烦了????????????&#xff0c;希望后面再来几个其他方面的dump&#xff0…

java swing 示例_JAVA简单Swing图形界面应用演示样例

JAVA简单Swing图形界面应用演示样例package org.rui.hello;import javax.swing.JFrame;/*** 简单的swing窗体* author lenovo**/public class HelloSwing {public static void main(String[] args) {JFrame framenew JFrame("hello Swing");frame.setDefaultCloseOpe…

.NET上海社区线下Meetup - 5.22 Blazor Day

Blazor 是一个 Web UI 框架&#xff0c;Blazor 旨在简化快速的单页面 .Net 浏览器应用的构建过程&#xff0c;它虽然使用了诸如 CSS 和 HTML 之类的 Web 技术&#xff0c;但它使用 C&#xff03;语言和 Razor 语法代替 JavaScript 来构建可组合的 Web UI 。通过提供用于编译到 …

入门机器学习,开启人工智能大门!

AI这个词相信大家都非常熟悉&#xff0c;近几年来人工智能圈子格外热闹&#xff0c;光是AlphoGo就让大家对它刮目相看。今天小天就来跟大家唠一唠如何进军人工智能的第一步——机器学习。在机器学习领域&#xff0c;Python已经成为了主流。一方面因为这门语言简单易上手&#x…

java集合框架的结构_集合框架(Collections Framework)详解及代码示例

简介集合和数组的区别&#xff1a;数组存储基础数据类型&#xff0c;且每一个数组都只能存储一种数据类型的数据&#xff0c;空间不可变。集合存储对象&#xff0c;一个集合中可以存储多种类型的对象。空间可变。严格地说&#xff0c;集合是存储对象的引用&#xff0c;每个对象…

Unity3D OpenVR 虚拟现实 保龄球打砖块游戏开发

据说水哥买了 Valve Index 设备&#xff0c;既然这个设备这么贵&#xff0c;不开发点有&#xff08;zhi&#xff09;趣&#xff08;zhang&#xff09;游戏就感觉对不起这个设备。本文将来开始着手开发一个可玩性不大&#xff0c;观赏性极强的保龄球打砖块游戏。这仅仅只是一个入…

mac mysql 移动硬盘_MAC一些高能过程记录(一些没必要的坑)

搞计算机的&#xff0c;谁电脑上没个数据库&#xff0c;不管用不用的着&#xff0c;有时候总需要&#xff0c;比如调试下博客呀之类的, 毕竟一般都会觉得数据库很好玩啊1.MySql安装&#xff1a;dmg、pkg什么的直接装吧&#xff0c;结束后会给你一个提示&#xff0c;上面会有密…

数学思维比数学运算更重要

全世界只有3.14 % 的人关注了数据与算法之美数学的证明依靠严密的逻辑推理&#xff0c;一经证明就永远正确&#xff0c;所以&#xff0c;数学证明是绝对的。相对而言&#xff0c;科学的证明则依赖于观察、实验数据和理解力&#xff0c;科学理论的证明难以达到数学定理证明所具有…

多年前那些优秀的工程师,后来都去哪儿了?

这是头哥侃码的第241篇原创上周末&#xff0c;我读初中的儿子突然问我&#xff1a;“爸爸&#xff0c;你是不是从好买离职了&#xff1f;”我听完&#xff0c;忙惊讶地问他是怎么知道的。他朝我做了个鬼脸&#xff0c;然后指了指我的手机说&#xff1a;“你的文章写的如此生动&…

使用easyUI 格式化datagrid列

author YHC 以下示例格式化在easyui DataGrid 里的列数据,和使用自定义列formatter ,如果价格小于20就将文本变为红色. 查看 Demo 格式化一个DataGrid 列,我们需要设置formatter 属性它是一个函数,这个格式化函数包含三个参数: value: 当前列对应字段值.row: 当前的row(行)记录…

收藏 | 分享 3 种脑洞大开的Excel技巧

全世界只有3.14 % 的人关注了数据与算法之美身为职场人&#xff0c;Excel基本是每天都会打开的软件&#xff0c;如果把对它的使用熟练程度分个等级&#xff0c;大概可以分为几下几种&#xff1a;Level 1&#xff1a;对Excel的基本功能已经有所了解&#xff0c;但还不熟练&#…

yaml for java_细数Java项目中用过的配置文件(YAML篇)

灵魂拷问&#xff1a;YAML&#xff0c;在项目中用过没&#xff1f;它与 properties 文件啥区别&#xff1f;目前 SpringBoot、SpringCloud、Docker 等各大项目、各大组件&#xff0c;在使用过程中几乎都能看到 YAML 文件的身影。2017 年的时候&#xff0c;我才真正把 YAML 文件…

超全面!8 种互联网常用生命周期完整指南~

什么是生命周期&#xff1f; 百度给出的定义是&#xff1a;生命周期就是指一个对象的生老病死。 生命周期的概念应用很广泛&#xff0c;特别是在政治、经济、环境、技术、社会等诸多领域经常出现&#xff0c;其基本涵义可以通俗地理解为“从摇篮到坟墓”的整个过程。对于某个…

技术分享|集成开放平台使用Consul Watch机制实现配置热更新

源宝导读&#xff1a;在微服务架构体系中&#xff0c;由于微服务众多&#xff0c;服务之间又有互相调用关系&#xff0c;因此&#xff0c;一个通用的分布式配置管理是必不可少的。本文将介绍如何使用Consul Watch机制实现配置集中管理与热更新。前言随着程序功能的日益复杂&…