光速网络网站,做的网站浏览器提示不安全,建站哪家好wordpress,中国十大物联网公司文章目录 类的加载过程加载阶段链接阶段初始化 类的加载器测试代码中获取对应的加载器获取加载器加载的路径不同类对应的加载器自定义加载器自定义加载器的方式 获取类的加载器的方式双亲委派机制双亲委派机制的好处 Java 的 SPI 机制1. 接口定义2. 具体实现3. 配置 META-INF/s… 文章目录 类的加载过程加载阶段链接阶段初始化 类的加载器测试代码中获取对应的加载器获取加载器加载的路径不同类对应的加载器自定义加载器自定义加载器的方式 获取类的加载器的方式双亲委派机制双亲委派机制的好处 Java 的 SPI 机制1. 接口定义2. 具体实现3. 配置 META-INF/services 文件4. 接口的使用 类的加载过程 类的加载过程分为3个阶段
加载阶段链接阶段初始化阶段 类的加载器只负责加载 .class 文件至于能不能执行是执行引擎决定的。 加载阶段 通过类的全限定名获取此类的二进制流将此类的二进制流中静态储存结构存储在运行时数据区的方法区元空间 7.0 或永久代 7.0在内存中生成一个 java.lang.Class 的对象作为方法区在这个类的各种数据的访问入口 虽然一般情况下JVM 加载的是 .class 文件其实只要是符合 JVM 的字节码都可以进行加载。比如.jar 包中的文件动态代理生成的字节码可在运行时动态生成还比如加密的 .class 文件通过 JVM 解密后加载可有效防止反编译。 链接阶段
验证Verify
每个 .class 文件都有一个特殊的开头用以表示该文件是 JVM 支持的 .class 文件。验证的目的在于确保被加载的类的正确性。主要验证方式文件格式验证、元数据验证、字节码验证、符号引用验证
准备Prepare
为类变量分配内存并且设置类变量的初始值注意初始值为 0。如private static int a 10;这个时候 a 的值为 0特殊的如果变量用 final 修饰如private final static int b 10;这个时候 b 的值为 10因为 final 修饰表示 b 为常量在编译期初始化。此时成员变量不会初始化static 修饰的 a 为类变量类变量在类加载的时候初始化如 private int c 10c 是成员变量是和对象一起被分配到 Java 堆中的。
解析Resolve
将常量池内的符号引用转换为直接引用的过程解析主要针对类或接口、字段、各种方法类方法、接口方法等
初始化 初始化静态变量和静态块此对应执行类构造器方法clinit()此方法的指令按源文件中的顺序执行。如果没有相关静态变量或静态块可能不会有 clinit() 方法。成员变量和局部变量对应 JVM 下的 init()方法。 类的加载器
引导类启动类加载器Bootstrap ClassLoder负责加载 Java 的核心类库JAVA_HOME/jre/lib/rt.jar、resource.jar、sun、java、javax 包开头的类 使用 C/C 实现的并不继承自 ClassLoader 类。自定义类加载器直接或间接继承自 ClassLoader 类的类加载器。是由 Java 实现的。 扩展类加载器sun.misc.Launcher$ExtClassLoaderExtClassLoader 是 Launcher 对象的内部类其父类加载器为引导类加载器其加载的是系统属性 java.ext.dirs 所指定的目录 或者 从 JAVA_HOME/jre/lib/ext 目录下加载类。我们自定义的 jar 包放在此目录也会被加载系统类应用程序类加载器sun.misc.Launcher$AppClassLoader其父类加载器为扩展类加载器负责加载环境变量 classpath 或 系统属性 java.class.path 所指定的目录下的类。其他自定义的类加载器 注意他们不是继承关系我们可以称他们为扩展关系。 测试代码中获取对应的加载器 public static void main(String[] args) {ClassLoader app ClassLoader.getSystemClassLoader();System.out.println(app);ClassLoader ext app.getParent();System.out.println(ext);ClassLoader bootstrap ext.getParent();System.out.println(bootstrap);}结果
sun.misc.Launcher$AppClassLoader18b4aac2
sun.misc.Launcher$ExtClassLoader6d03e736
null程序中的默认类加载器为 AppClassLoader 。一般情况下Java 应用中的类都是由 AppClassLoader 加载器加载。其通过 ClassLoader.getSystemClassLoader() 方法获取。 Java 代码中不能直接获取引导类加载器实例。所以示例中 bootstrap 为 null 获取加载器加载的路径 // 获取 Bootstrap 加载器的加载路径URL[] urls Launcher.getBootstrapClassPath().getURLs();for(URL url : urls){System.out.println(url.getFile());}System.out.println();// 获取扩展类加载器加载的的路径String dirs System.getProperty(java.ext.dirs);System.out.println(dirs);
结果
/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/resources.jar
/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/rt.jar
/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/sunrsasign.jar
/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jsse.jar
/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jce.jar
/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/charsets.jar
/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jfr.jar
/C:/Program%20Files/Java/jdk1.8.0_181/jre/classesC:\Program Files\Java\jdk1.8.0_181\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\extBootstrap 加载器的加载路径下的 jar 包文件包含的类由 Bootstrap 加载器加载两个 ext 目录下的 jar 包下的类由 ExtClassLoader 加载器加强我们的应用程序classpath 属性下的类由 AppClassLoader 加载。 不同类对应的加载器 ClassLoader classLoader String.class.getClassLoader();System.out.println(classLoader);ClassLoader classLoader1 ExecutorTest.class.getClassLoader();System.out.println(classLoader1);结果
null
sun.misc.Launcher$AppClassLoader18b4aac2说明 String 类是由引导类加载器加载其引导类加载器无法在代码中获取所以为 null 。ExecutorTest 是我们的示例对象其由 AppClassLoader 加载器加载。当然我们在 ext 目录下找到的 jar 包中的类由 ExtClassLoader 加载器加载。 自定义加载器 一般的 Java 程序中使用引导类加载器、扩展类加载器、系统类加载器相互作用即可。几乎不需要自定义类的加载器我们可以在某些情况下进行自定义加载器。 修改类的加载方式还比如我们对编译的源码进行了加密在类加载时需要解密等
自定义加载器的方式
继承 ClassLoader 重写 findClass(String name) 方法按照 URLClassLoader.FactoryURLClassLoader 继承 URLClassLoader 的方式继承 URLClassLoader 来实现即可。 URLClassLoader 可以加载 jar 包下的类
package com.yyoo.jvm;import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;Getter
Setter
AllArgsConstructor
public class MyClassLoader extends ClassLoader{/*** 当前加载器的 class 文件根路径*/private String classRootPath;Overrideprotected Class? findClass(String name) throws ClassNotFoundException {// 使用 io 流读取.class 字节码文件然后使用父类的 defineClass 方法返回为 Class 类try (InputStream in new FileInputStream(getClassRootPath()\\name.replaceAll(.,\\).class)){byte[] b new byte[1024];int len in.read(b);return defineClass(name,b,0,len);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}throw new ClassNotFoundException();}} name 参数使用class的全类名即可。注我们示例使用了 lombok 插件。 获取类的加载器的方式
// 获取当前类的ClassLoader
Object o new Object();
System.out.println(o.getClass().getClassLoader());// 获取当前线程上下文的ClassLoder
System.out.println(Thread.currentThread().getContextClassLoader());// 获取当前系统的 ClassLoder
System.out.println(ClassLoader.getSystemClassLoader());双亲委派机制 类的加载过程
类的加载器收到类的加载请求时它不会立马去加载而且把加载请求委托给父类加载器去执行。如果父类加载器还有父类加载器则会进一步委托给父类加载器加载直到最顶层的类加载器。如果父类加载器可以完成类的加载则由父类加载器加载该类如果父类加载器无法加载子加载器才会尝试自己加载 以上整个过程称为双亲委派机制。 示例1 public static void main(String[] args) throws ClassNotFoundException {String classRootPath D:\\work\\code\\mytest\\peixun\\target\\classes;// 同一个 ClassLoader 实例加载同一个 Class 得到的是同一个 Class 对象ClassLoader my new MyClassLoader(classRootPath);Class a my.loadClass(com.yyoo.jvm.MyEmp);Class b my.loadClass(com.yyoo.jvm.MyEmp);System.out.println(a b);// true// 同一个 ClassLoader 的不同实例加载同一个 Class 文件得到的也是同一个 Class 对象ClassLoader my1 new MyClassLoader(classRootPath);Class c my1.loadClass(com.yyoo.jvm.MyEmp);System.out.println(a c);// true// 不同的 ClassLoader 加载同一个 Class 文件得到的也是同一个 Class 对象ClassLoader app ClassLoader.getSystemClassLoader();Class d app.loadClass(com.yyoo.jvm.MyEmp);System.out.println(a d);// trueSystem.out.println(a.getClassLoader());// 系统类加载器System.out.println(d.getClassLoader());// 系统类加载器}注MyClassLoader 即为我们上面自定义的 ClassLoader。 根据双亲委派机制来解释该现象我们自定义加载器的 classRootPath 其实就是我们应用的 classPath而 classPath 下的类是由系统类加载器加载的而且其加载的始终是 classPath 下的类而我们的 ClassRootPath 下的类永远不会加载除非我们自定义加载的类和系统类加载器加载的类全类名有不同的地方 或者 classPath 下没有该类。 示例2 示例1的前提是MyEmp 的 .class 文件存在于我们应用的 classPath 路径下示例 2 我们将 classPath 路径下的 .class 文件删除并按包路径创建文件夹在 D 盘根路径下Java 的几大类加载器加载的路径和目录之外再次执行 String classRootPath D:;
// 同一个 ClassLoader 实例加载同一个 Class 得到的是同一个 Class 对象
ClassLoader my new MyClassLoader(classRootPath);
Class a my.loadClass(com.yyoo.jvm.MyEmp);
Class b my.loadClass(com.yyoo.jvm.MyEmp);System.out.println(a b);// 同一个 ClassLoader 的不同实例加载同一个 Class 文件得到的不是同一个 Class 对象
ClassLoader my1 new MyClassLoader(classRootPath);
Class c my1.loadClass(com.yyoo.jvm.MyEmp);
System.out.println(a c);System.out.println(a.getClassLoader());
System.out.println(c.getClassLoader());结果
true
false
com.yyoo.jvm.MyClassLoader3f99bd52
com.yyoo.jvm.MyClassLoader3a71f4dd可以看到在 系统类加载器、扩展类加载器、启动类加载器加载的路径之外的 .class 文件会使用我们自定义的加载器加载且不同的 MyClassLoader 实例加载的 Class 对象不是同一个。 双亲委派机制的好处
避免类的重复加载保护程序安全防止核心 API Java 核心类被随意篡改
Java 的 SPI 机制 SPIservice provider interface是 jdk 内置的一种服务提供发现机制。可以用来启用框架扩展和替换组件主要是被框架的开发人员使用比如java.sql.Driver接口,常用的关系型数据有 Mysql、Oracle、SQLServer、DB2 等这些不同类型的数据库使用的驱动程序各不相同那 JDK 不可能把所有厂商的驱动都实现只能制定一个标准接口其他不同厂商可以针对同一接口做出不同的实现各个数据库厂商根据标准来实现自己的驱动这就是SPI机制。 通俗点来说SPI 就是某个应用程序只提供接口规则具体的实现需要调用方通常该接口会有多个实现否则也就用不着 SPI 了在使用时自行实现其实现方式遵循 Java 的 SPI 机制。 比如我们要提供一个文件上传的接口其实现有如下几种方式服务器本地存储、上传到 ftp 服务器、上传到 minio、上传到云服务等等 1. 接口定义
public interface FileUpload {/*** 上传文件*/void upload();}2. 具体实现
public class LocalFileUpload implements FileUpload{Overridepublic void upload() {System.out.println(将文件存储到服务器本地路径下);}
}
public class MinIOFileUpload implements FileUpload{Overridepublic void upload() {System.out.println(将文件上传到 MinIOn 服务器);}
}3. 配置 META-INF/services 文件 在应用的 META-INF/services/ 目录下目录不存在自行创建即可创建文件名称为 com.yyoo.spi.FileUpload 的文本文件 文件内容为
com.yyoo.spi.LocalFileUpload
com.yyoo.spi.MinIOFileUpload4. 接口的使用
// load 方法如果不传 ClassLoader 则默认使用当前线程上下文的 Thread.currentThread().getContextClassLoader()
// 这里即是系统类加载器我们也可以使用重载方法 load(ClassS service,ClassLoader loader) 来指定加载器
ServiceLoaderFileUpload serviceLoader ServiceLoader.load(FileUpload.class);// 获取迭代器或者直接使用 foreach 语句即可获取 META-INF/services/com.yyoo.spi.FileUpload 文件中配置的所有实现
for (FileUpload fileUpload : serviceLoader){System.out.println(fileUpload.getClass());System.out.println(fileUpload.getClass().getClassLoader());// 使用系统类加载器加载fileUpload.upload();
}执行结果
class com.yyoo.spi.LocalFileUpload
sun.misc.Launcher$AppClassLoader18b4aac2
将文件存储到服务器本地路径下
class com.yyoo.spi.MinIOFileUpload
sun.misc.Launcher$AppClassLoader18b4aac2
将文件上传到 MinIOn 服务器ServiceLoader 是 java.util 包提供的工具类其主要作用就是通过读取 META-INF/services 下的配置然后根据配置通过系统类加载器加载对应配置的实现类。 SPI 允许应用程序外部提供内部接口的实现从而改变了内部接口的行为在某种程度上规避了双亲委派机制思想核心 API 实现被改变。