根据一个指定的类的全限定名,找到对应的Class字节码文件,然后加载它转化成一个java.lang.Class类的一个实例。并且这个类对应的Class实例在堆区无论你加载多少次只会存在一个,除非使用不同的加载器去加载这个类,则会出现多个的效果。这是因为不同的加载器会出现命名空间的问题。
//获取到根加载器的加载目录
System.getProperty("sun.boot.class.path");
//获取到根加载器的加载目录
System.getProperty("java.ext.dirs");
Class<?> class = Class.forName("com.brycen.classloader.SimpleObject");
//获取其默认加载器
System.out.println(class.getClassLoader());
//获取其父加载器
System.out.println(class.getClassLoader().getParent());
//获取其根加载器,此处返回为null,因为根加载器是由C编写的,所有返回null
System.out.println(class.getClassLoader().getParent().getParent());
一个类加载器收到了类加载的请求,它不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,这样一层一层往上抛直到最顶层。如果最顶层没有找到则会交由子类加载器去完成,直至最后一个子类加载器。
提高系统的安全性,可以避免用户恶意破坏结构,如自己定义一个String类、Object类等,用户自定义的类加载器不可能加载应该由父加载器加载可靠类,因此可防止恶意代码的代替父加载器的可靠代码。
自定义的类加载器:
public class MyClassLoader extends ClassLoader {
private final static String DEFAULT_DIR = "E:\\classloader1";
private String dir = DEFAULT_DIR;
private String classLoaderName;
public MyClassLoader() {
super();
}
public MyClassLoader(String classLoaderName) {
super();
this.classLoaderName = classLoaderName;
}
//指定父加载器,不指定则默认是系统加载器
public MyClassLoader(String classLoaderName, ClassLoader parent) {
super(parent);
this.classLoaderName = classLoaderName;
}
@Override
protected Class<?> findClass(String name)
throws ClassNotFoundException {
String classPath = name.replace(".", "/");
File classFile = new File(dir, classPath + ".class");
if (!classFile.exists()) {
throw new ClassNotFoundException("The class " + name + " not found under " + dir);
}
byte[] classBytes = loadClassBytes(classFile);
if (null == classBytes || classBytes.length == 0)
throw new ClassNotFoundException("load the class " + name + " failed");
return this.defineClass(name, classBytes, 0, classBytes.length);
}
//读取类的二进制文件
private byte[] loadClassBytes(File classFile) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileInputStream fis = new FileInputStream(classFile)) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
baos.flush();
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public String getDir() {
return dir;
}
public void setDir(String dir) {
this.dir = dir;
}
public String getClassLoaderName() {
return classLoaderName;
}
}
自定义的类:
public class MyObject {
static {
System.out.println("My object static block");
}
public String hello(){
return "Hello World";
}
}
测试:
1.首先将编译好的MyObject.class的完整包名文件夹以及文件放入我们自定义类加载器加载的目录中
2.删除之前原目录编译好的MyObject.class,不删除的话则类加载器还是会使用系统加载器,而不是使用我们自定义的类加载器,因为父委托机制。
注意:类加载器加载类时中并不会导致初始化,因为类加载不属于类的主动使用,在类实例的时候才会初始化,实例属于类的主动使用。这点类加载器和反射还是有点不同的。关于类的主动使用和被动使用可参考我上一篇博客ClassLoader加载过程
public class MyClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
MyClassLoader classLoader = new MyClassLoader("MyClassLoader");
//注意在类加载时中并不会导致初始化,因为类加载不属于类的主动使用
Class<?> aClass = classLoader.loadClass("com.brycen.classloader.MyObject");
System.out.println(aClass);
System.out.println(aClass.getClassLoader());
//在类实例的时候才会初始化,实例属于类的主动使用
Object obj = aClass.newInstance();
Method method = aClass.getMethod("hello", new Class<?>[]{});
Object result = method.invoke(obj, new Object[]{});
System.out.println(result);
}
}
输出结果:
class com.brycen.classloader.MyObject
com.brycen.classloader.MyClassLoader@7adf9f5f
My object static block
Hello World
原理:就是先将编译后的class文件进行加密,随后用我们自定义的类加载器对其解密后再加载
前期准备:
package com.brycen.classloader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public final class EncryptUtils {
public static final byte ENCRYPT_FACTOR = (byte) 0xff;
private EncryptUtils() {
//empty
}
public static void doEncrypt(String source, String target) {
try (FileInputStream fis = new FileInputStream(source);
FileOutputStream fos = new FileOutputStream(target)) {
int data;
while ((data = fis.read()) != -1) {
fos.write(data ^ ENCRYPT_FACTOR);
}
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
doEncrypt("E:\\classloader2\\MyObject.class", "E:\\classloader2\\MyObject2.class");
}
}
其实是在第四章自定义加载器的代码中的读取字节时做了相应的解密工作
package com.brycen.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class DecryptClassLoader extends ClassLoader {
private final static String DEFAULT_DIR = "E:\\classloader2";
private String dir = DEFAULT_DIR;
public DecryptClassLoader() {
super();
}
public DecryptClassLoader(ClassLoader parent) {
super(parent);
}
@Override
protected Class<?> findClass(String name)
throws ClassNotFoundException {
String classPath = name.replace(".", "/");
File classFile = new File(dir, classPath + ".class");
if (!classFile.exists()) {
throw new ClassNotFoundException("The class " + name + " not found under directory [" + dir + "]");
}
byte[] classBytes = loadClassBytes(classFile);
if (null == classBytes || classBytes.length == 0) {
throw new ClassNotFoundException("load the class " + name + " failed");
}
return this.defineClass(name, classBytes, 0, classBytes.length);
}
//主要区别在这里,这里做了解密操作
private byte[] loadClassBytes(File classFile) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileInputStream fis = new FileInputStream(classFile)) {
int data;
while ((data = fis.read()) != -1) {
baos.write(data ^ EncryptUtils.ENCRYPT_FACTOR);
}
baos.flush();
return baos.toByteArray();
} catch (IOException e) {
return null;
}
}
public void setDir(String dir) {
this.dir = dir;
}
}
package com.brycen.classloader;
import java.lang.reflect.Method;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
DecryptClassLoader classLoader = new DecryptClassLoader();
//这里别忘了是加载我们加密之后的class文件
Class<?> aClass = classLoader.loadClass("com.brycen.classloader.MyObject2");
System.out.println(aClass);
Object obj = aClass.newInstance();
Method method = aClass.getMethod("hello", new Class<?>[]{});
Object result = method.invoke(obj, new Object[]{});
System.out.println(result);
}
}
输出结果:
class com.brycen.classloader.MyObject
My object static block
Hello World
在自定义加载器中只需要重写loadClass这个方法即可,让其子加载器优先于父加载器加载。虽然可以打破但是像java.lang.String等系统的类都是没有办法加载的,会出现Security的安全异常。
前期准备:
package com.brycen.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class SimpleClassLoader extends ClassLoader {
private final static String DEFAULT_DIR = "E:\\classloader3";
private String dir = DEFAULT_DIR;
private String classLoaderName;
public SimpleClassLoader() {
super();
}
public SimpleClassLoader(String classLoaderName) {
super();
this.classLoaderName = classLoaderName;
}
public SimpleClassLoader(String classLoaderName, ClassLoader parent) {
super(parent);
this.classLoaderName = classLoaderName;
}
@Override
protected Class<?> findClass(String name)
throws ClassNotFoundException {
String classPath = name.replace(".", "/");
File classFile = new File(dir, classPath + ".class");
if (!classFile.exists()) {
throw new ClassNotFoundException("The class " + name + " not found under " + dir);
}
byte[] classBytes = loadClassBytes(classFile);
if (null == classBytes || classBytes.length == 0)
throw new ClassNotFoundException("load the class " + name + " failed");
return this.defineClass(name, classBytes, 0, classBytes.length);
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = null;
//这里要过滤java开头的包名,这些包名必须由系统加载器加载,因为在我们自定义加载器的加载目录中没有这些class文件
if (name.startsWith("java.")) {
try {
ClassLoader system = ClassLoader.getSystemClassLoader();
clazz = system.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (Exception e) {
//ignore
}
}
try {
clazz = findClass(name);
} catch (Exception e) {
e.printStackTrace();
}
if (clazz == null && getParent() != null) {
getParent().loadClass(name);
}
return clazz;
}
private byte[] loadClassBytes(File classFile) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileInputStream fis = new FileInputStream(classFile)) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
baos.flush();
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public String getDir() {
return dir;
}
public void setDir(String dir) {
this.dir = dir;
}
public String getClassLoaderName() {
return classLoaderName;
}
}
测试
package com.brycen.classloader;
public class SimpleClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
SimpleClassLoader simpleClassLoader = new SimpleClassLoader();
Class<?> aClass = simpleClassLoader.loadClass("com.brycen.classloader.MyObject");
System.out.println(aClass.getClassLoader());
}
}
输出结果:
com.brycen.classloader.SimpleClassLoader@7adf9f5f
正式因为打破了双亲委托机制,所有这里是使用的自定义加载器加载的
运行时期一个类的包其实是由其classloader的命名空间及其其包名组成的
案例:
在第六章的代码基础上进行的实验
package com.brycen.classloader;
public class RuntimePackage {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
SimpleClassLoader simpleClassLoader = new SimpleClassLoader();
//这里的加载器使用的是我们自定义的加载器,即使原MyObject的class及其完整包名没有删除,因为我们使用的是打破双亲委托机制的类加载器。
Class<?> aClass = simpleClassLoader.loadClass("com.brycen.classloader.MyObject");
System.out.println(aClass.getClassLoader());
//根据运行时包名原则,这行就会报错,因为RuntimePackage这个类是由系统类加载器加载的,我们的aClass.newInstance()是由自定义加载器加载的
SimpleObject simpleObject = (SimpleObject) aClass.newInstance();
}
}
运行结果:
com.brycen.classloader.SimpleClassLoader@7adf9f5f
Exception in thread "main" java.lang.ClassCastException:com.brycen.classloader.MyObject cannot be cast to com.brycen.classloader.MyObject
JVM中的Class只有满足以下三个条件,才能被GC回收,也就是该Class被卸载。
GC的时机我们是不可控的,那么同样的我们对于Class的卸载也是不可控的。
可以通过线程上下文加载器打破加载器的双亲委托机制
Thread.currentThread().getContextClassLoader();
因为在java中JDBC的规范是由java定义的,都是一些接口,在java.sql包中,是由根加载器去加载的,而我们使用数据库的时候,那些实现类是由系统加载器去加载的。那么就会出现访问不到具体的实现这个问题。这时候就会用到我们的线程上下文加载器去打破这种双亲委托机制。
步骤:
Class.forName("com.mysql.jdbc.Driver");
registerDriver(new Driver());是将自身实例传递进去
下面这个代码是getConnection中的源码,将线程上下文加载器赋于callerCL
本文地址:https://blog.csdn.net/liuyu973971883/article/details/107588334
如对本文有疑问, 点击进行留言回复!!
Android 4.0使用Kotlin调用C语言以及汇编语言
Java Class.forName()用法和newInstance()方法原理解析
网友评论