用来加载.class
字节码文件,加载为Class对象存储到JVM内存中
.class
文件;.class
文件数据的正确性和代码逻辑static Integer num = 111
:此时num
的值只是赋了零值0
(初始化阶段才会赋值)static final Integer num = 111
:那么当前准备阶段就会直接赋值111
new
、getstatic
、putstatic
、invokestatic
这 4 条直接码指令时,比如 new一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
new
指令时会初始化类。即当程序创建一个类的实例对象getstatic
指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)putstatic
指令时会初始化类。即程序给类的静态变量赋值invokestatic
指令时会初始化类。即程序调用类的静态方法java.lang.reflect
包的方法对类进行反射调用时如 Class.forname("...")
, newInstance()
等等。如果类没初始化,触发其初始化。main
方法的那个类),虚拟机会先初始化这个类。%JAVA_HOME%/lib
目录下的rt.jar
、resources.jar
、charsets.jar
等jar包和类)以及被-Xbootclasspath
参数指定的路径下的所有类。
rt.jar
是Java基础类库,包含Java doc
里面看到的所有的类的类文件。也就是说,我们常用内置库 java.xxx.*
都在里面,比如java.util.*
、java.io.*
、java.nio.*
、java.lang.*
、java.sql.*
、java.math.*
。%JRE_HOME%/lib/ext
目录下的 jar 包和类以及被 java.ext.dirs
系统变量所指定的路径下的所有类。classpath
下的所有 jar 包和类。除了BootstrapClassLoader
是 JVM 自身的一部分(无需加载)之外,其他所有的类加载器都是在 JVM 外部实现的,并且全都继承自ClassLoader
抽象类。这样做的好处是用户可以自定义类加载器,以便让应用程序自己决定如何去获取所需的类。
扩展类加载器和系统类加载器由于也是类,也是JDK中提供的类,所以是由引导类加载器进行加载
每个 ClassLoader 可以通过getParent()
获取其父 ClassLoader,如果获取到 ClassLoader 为null
的话,那么该类是通过BootstrapClassLoader
加载的。
具体流程如下:
ClassNotFoundException
隔离加载类
在某些框架内进行中间件与应用的模块隔离,把类加载到不同的环境。比如:阿里内某容器框架通过自定义类加载器确保应用中依赖的jar包不会影响到中间件运行时使用的jar包。再比如:Tomcat这类Web应用服务器,内部自定义了好几种类加载器,用于隔离同一个Web应用服务器上的不同应用程序。
两个jar包内都存在相同类名且包名相同,如果没有隔离加载类,则会报错,如:两个版本的jar
修改类加载方式
扩展加载源
防止源码泄露
Java代码容易被编译和篡改,可以进行编译加密。那么类加载也需要自定义,还原加密的字节码。
通常Java系统想增加License(授权),就可以通过自定义类加载器实现。
自定义加载器的parent为AppClassLoader
Java提供了抽象类java.lang.ClassLoader
,所有用户自定义的类加载器都应该继承ClassLoader
类。
在自定义ClassLoader的子类时候,我们常见的会有两种做法
方式一:重写loadClass()
方法
方式二:重写findClass()
方法
这两种方法本质上差不多,毕竟
loadClass()
也会调用findClass()
,但是从逻辑上讲我们最好不要直接修改loadClass()
的内部逻辑。
loadClass()
这个方法是实现双亲委派模型逻辑的地方,擅自修改这个方法会导致模型被破坏,容易造成问题。
public class MyClassLoader extends ClassLoader{
private String classPath;
public MyClassLoader(String classPath){
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 解析字节码文件路径
String s = name.replaceAll("\\.", Matcher.quoteReplacement(File.separator));
String filePath = classPath + s + ".class";
// 读取字节码文件
FileInputStream inputStream = null;
ByteArrayOutputStream outputStream = null;
try {
inputStream = new FileInputStream(filePath);
outputStream = new ByteArrayOutputStream();
int len;
byte[] bytes = new byte[1024];
while ((len = inputStream.read(bytes)) != -1){
outputStream.write(bytes,0,len);
}
byte[] outBytes = outputStream.toByteArray();
// 使用defineClass根据字节码创建Class实例
return defineClass(name,outBytes,0,outBytes.length);
} catch (Exception e) {
e.printStackTrace();
return null;
}finally {
if (outputStream != null){
try {
outputStream.flush();
outputStream.close();
}catch (Exception e){
e.printStackTrace();
}
}
if (inputStream != null){
try {
inputStream.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
package top.ygang;
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
使用命令javac -encoding utf-8 Student.java
编译,并且将Student.class
拷贝到D:\\
下
注意:删除项目中的Student.java
!不然由于双亲委派机制,自定义类加载器向上委托,AppClassLoader
会在项目classPath
中找到该类并执行进行加载
public static void main(String[] args) throws Exception{
MyClassLoader myClassLoader = new MyClassLoader("D:\\");
Class<?> aClass = myClassLoader.loadClass("top.ygang.Student");
System.out.println(aClass.getClassLoader());
// classloader.MyClassLoader@4dd8dc3
Constructor<?> constructor = aClass.getConstructor(String.class, int.class);
Object lucy = constructor.newInstance("lucy", 12);
System.out.println(lucy);
// Student{name='lucy', age=12}
}
// 先获取类的字节码文件对象
Class clazz = Person.class;
// 通过字节码文件对象获取类加载器对象
ClassLoader classLoader = clazz.getClassLoader();
// 获取类加载器的上一层类加载器
ClassLoader classLoader1 = classLoader.getParent();
原始方法,通过Properties类API进行读取
// 在src的路径下,有一个jdbc.properties的配置文件
Properties p = new Properties();
p.load(new FileInputStream("src/jdbc.properties"));
String driver = p.getProperty("driver");
System.out.println(driver);
这种方法的弊端是如果配置文件在jar包内,也就是classpath下,则无法读取
使用类加载器,实际上就是AppClassLoader
,由于这个类加载器加载的是classpath下的类,所以同样也就可以用来读取classpath下的文件
// 获取类的字节码文件对象
Class clazz = Demo2.class;
// 获取类加载器的对象
ClassLoader classLoader = clazz.getClassLoader();
// 使用类加载器对象读取配置文件
// 注意:使用类加载器的方式读取配置文件,默认的根目录是相对于classpath目录
InputStream is = classLoader.getResourceAsStream("jdbc.properties");
Properties p = new Properties();
p.load(is);
String driver = p.getProperty("driver");
System.out.println(driver);
// 获取类的字节码文件对象
Class clazz = Demo1.class;
// 获取类加载器对象
ClassLoader classLoader = clazz.getClassLoader();
// 使用类加载器对象读取配置文件
// 注意:使用类加载器的方式读取配置文件,默认的根目录是相对于classpath目录
URL url = classLoader.getResource("jdbc.properties");
String path = url.getPath();
FileInputStream fis = new FileInputStream(path);
Properties p = new Properties();
p.load(fis);
String driver = p.getProperty("driver");
System.out.println(driver);