自定义类加载器

系统管理员 2022-08-30 01:46 355阅读 0赞

导航

  • 一、loadClass(String) 方法
  • 二、findClass(String) 方法
  • 三、自定义类加载器
  • 四、打破双亲委派机制

一、loadClass(String) 方法

想要把一个类加载到内存中,只需要调用 ClassLoader 的 loadClass 方法:

  1. public class TestLoadClass {
  2. public static void main(String[] args) throws ClassNotFoundException {
  3. Class<?> cls = TestLoadClass.class.getClassLoader()
  4. .loadClass("test86.LoadClass");
  5. System.out.println(cls.getName());
  6. }
  7. }
  8. output:
  9. test86.LoadClass

cls.getName 可以成功取得类名,而不是抛出 NPE,说明类已经被成功加载。

loadClass 方法如果成功执行,会将磁盘上的字节码文件加载到内存,并生成一个对应的 Class 实例,然后将这个 Class 对象返回

loadClass 方法的源码如下:

  1. public Class<?> loadClass(String name) throws ClassNotFoundException {
  2. return loadClass(name, false);
  3. }
  4. protected Class<?> loadClass(String name, boolean resolve)
  5. throws ClassNotFoundException
  6. {
  7. synchronized (getClassLoadingLock(name)) {
  8. // First, check if the class has already been loaded
  9. Class<?> c = findLoadedClass(name);
  10. if (c == null) {
  11. long t0 = System.nanoTime();
  12. try {
  13. if (parent != null) {
  14. c = parent.loadClass(name, false);
  15. } else {
  16. c = findBootstrapClassOrNull(name);
  17. }
  18. } catch (ClassNotFoundException e) {
  19. // ClassNotFoundException thrown if class not found
  20. // from the non-null parent class loader
  21. }
  22. if (c == null) {
  23. // If still not found, then invoke findClass in order
  24. // to find the class.
  25. long t1 = System.nanoTime();
  26. c = findClass(name);
  27. }
  28. }
  29. return c;
  30. }
  31. }

loadClass 的处理过程实际上就是双亲委派机制的实现:
双亲委派机制流程
而 loadClass 最终会调用 findClass 来完成类加载的工作。

二、findClass(String) 方法

在 ClassLoader 中它的定义如下:

  1. protected Class<?> findClass(String name) throws ClassNotFoundException {
  2. throw new ClassNotFoundException(name);
  3. }

显而易见,该方法应该由 ClassLoader 的子类来实现,否则就会抛出 ClassNotFound 异常。(参考模板方法模式)

AppClassLoader 和 ExtClassLoader 都继承了 URLClassLoader,它们的 findClass 也是在URLClassLoader 中定义的:

  1. protected Class<?> findClass(final String name)
  2. throws ClassNotFoundException
  3. {
  4. try {
  5. return AccessController.doPrivileged(
  6. new PrivilegedExceptionAction<Class<?>>() {
  7. public Class<?> run() throws ClassNotFoundException {
  8. String path = name.replace('.', '/').concat(".class");
  9. Resource res = ucp.getResource(path, false);
  10. if (res != null) {
  11. try {
  12. return defineClass(name, res);
  13. } catch (IOException e) {
  14. throw new ClassNotFoundException(name, e);
  15. }
  16. } else {
  17. throw new ClassNotFoundException(name);
  18. }
  19. }
  20. }, acc);
  21. } catch (java.security.PrivilegedActionException pae) {
  22. throw (ClassNotFoundException) pae.getException();
  23. }
  24. }

当然,这段代码不需要细扣,只需要了解这个方法会根据传入的类引用全名转化后的磁盘路径最终由 defineClass 方法返回。

三、自定义类加载器

了解了 loadClass 和 findClass 方法后,如何自定义一个类加载器呢?

loadClass方法实现了双亲委派模型的处理逻辑,在不需要打破这种底层机制的情况下是不需要重写的,而 findClass 使用的模板方法模式要求 ClassLoader 的子类都必须重写 findClass 方法。

因此,自定义类加载器必须要重写 findClass,一般情况也只需要重写 findClass 。

首先,我们将需要加载的类先编译好,放入指定目录下:

在这里插入图片描述
然后尝试继承 ClassLoader 并重写 findClass:

  1. public class MortyClassLoader extends ClassLoader {
  2. public static void main(String[] args) throws ClassNotFoundException {
  3. MortyClassLoader mortyClassLoader = new MortyClassLoader();
  4. Class<?> cls = mortyClassLoader.loadClass("test86.LoadClass");
  5. System.out.println(cls.getName());
  6. }
  7. @Override
  8. protected Class<?> findClass(String name) throws ClassNotFoundException {
  9. File clsFile = new File("D:/workspace/morty/",
  10. name.replace(".", "/").concat(".class"));
  11. try {
  12. FileInputStream fis = new FileInputStream(clsFile);
  13. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  14. int b = 0;
  15. while ((b = fis.read()) != 0) {
  16. baos.write(b);
  17. }
  18. byte[] bytes = baos.toByteArray();
  19. // 此处的 close 并不严谨
  20. baos.close();
  21. fis.close();
  22. return defineClass(name, bytes, 0, bytes.length);
  23. } catch (Exception e) {
  24. e.printStackTrace();
  25. }
  26. // throws ClassNotFoundException
  27. return super.findClass(name);
  28. }
  29. }
  30. output:
  31. test86.LoadClass

执行后可以正常取得类名信息。

四、打破双亲委派机制

双亲委派机制是 JVM 保证系统安全性的一个重要手段,但这种机制并不牢靠。换句话说,如果情景需要,依然是可以绕开双亲委派机制。

历史上有三次大规模的“破坏”双亲委派机制。

  1. JDK 1.2 之前,还没有“双亲委派模型”,用户可以继承 ClassLoader 并重写 loadClass 方法。
  2. JDK 1.2 之后,已不提倡用户再去覆盖 loadClass 方法。但是双亲委派模型有其自身的缺陷,双亲委派模型很好的解决了基础类,如Object 等的统一问题,一般而言,基础类往往总是被用户代码调用,但是JNDI 服务中,API 定义服务的统一接口,JNDI 的目的就是对资源进行集中管理和查找。它需要调用实现了特定 API 接口的独立厂商实现并部署在用户应用程序classpath下的 JNDI 接口提供者(SPI,Service Provider Interface),例如各种符合 JDBC 接口规范的数据库厂商的驱动包。而启动类加载器是无法加载这些类库的,为解决这个问题,引入了线程上下文类加载器(Thread Context ClassLoader)。而JNDI 服务就可以使用这个 线程上下文类加载器去加载所需要的SPI代码,就是 父类加载器请求子类加载器去完成类加载动作,已经违背了双亲委派模型的一般性原则。如 JDBC、JNDI 等。
  3. 热加载、热部署等也是破坏了双亲委派模型。

发表评论

表情:
评论列表 (有 0 条评论,355人围观)

还没有评论,来说两句吧...

相关阅读

    相关 定义

    自定义类的的应用场景: 1.加密:Java代码可以轻易的被反编译,如果你需要把自己的代码进行加密以防止反编译,可以先将编译后的代码用某种加密算法加密,类加载后就不能再用

    相关 定义

    1. 双亲委派模型 1.1 什么是双亲委派模型? 首先,先要知道什么是类加载器。简单说,类加载器就是根据指定全限定名称将class文件加载到JVM内存,转为Clas

    相关 定义

    loadClass方法实现了双亲委派模型。 1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。 2. 如果此类没有加载过,那么,再判