JVM源码解析(2) ContextClassLoader与ClassUtil.forName()方法浅析

  created  by  鱼鱼 {{tag}}
创建于 2020年08月16日 00:53:49 最后修改于 2020年08月16日 13:39:14

    在Spring获取Context的源代码中,我们看到了对ClassUtil的方法调用,通过给定ClassName和ClassLoader进行Class的加载:

if (contextClassName != null) {
      try {
         return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
      }
      catch (ClassNotFoundException ex) {
         throw new ApplicationContextException(
               "Failed to load custom context class [" + contextClassName + "]", ex);
      }
   }

ClassUtil.forName()

    ClassUtil.forName是仅供于Spring内部使用的获取Class对象的方法,来看一下源码:

public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
      throws ClassNotFoundException, LinkageError {

   Assert.notNull(name, "Name must not be null");
   //缓存的name.length<=7简单类class,里面会先对长度做判断
   Class<?> clazz = resolvePrimitiveClassName(name);
   if (clazz == null) {
      //java.lang.*等其他简单类class
      clazz = commonClassCache.get(name);
   }
   if (clazz != null) {
      return clazz;
   }

   // "java.lang.String[]" style arrays
   //数组类型递归处理,不做详述
   if (name.endsWith(ARRAY_SUFFIX)) {
      String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
      Class<?> elementClass = forName(elementClassName, classLoader);
      return Array.newInstance(elementClass, 0).getClass();
   }

   // "[Ljava.lang.String;" style arrays
   if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) {
      String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1);
      Class<?> elementClass = forName(elementName, classLoader);
      return Array.newInstance(elementClass, 0).getClass();
   }

   // "[[I" or "[[Ljava.lang.String;" style arrays
   if (name.startsWith(INTERNAL_ARRAY_PREFIX)) {
      String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length());
      Class<?> elementClass = forName(elementName, classLoader);
      return Array.newInstance(elementClass, 0).getClass();
   }
   //获取默认的  类加载器   ClassLoader clToUse = classLoader;
   if (clToUse == null) {
      clToUse = getDefaultClassLoader();
   }
   try {
      return Class.forName(name, false, clToUse);
   }
   catch (ClassNotFoundException ex) {
      int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
      if (lastDotIndex != -1) {
         String innerClassName =
               name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
         try {
            return Class.forName(innerClassName, false, clToUse);
         }
         catch (ClassNotFoundException ex2) {
            // Swallow - let original exception get through
         }
      }
      throw ex;
   }
}

    首先 对于缓存的Class一块,在类的静态块中就能看出其逻辑:

static {
   primitiveWrapperTypeMap.put(Boolean.class, boolean.class);
   primitiveWrapperTypeMap.put(Byte.class, byte.class);
   primitiveWrapperTypeMap.put(Character.class, char.class);
   primitiveWrapperTypeMap.put(Double.class, double.class);
   primitiveWrapperTypeMap.put(Float.class, float.class);
   primitiveWrapperTypeMap.put(Integer.class, int.class);
   primitiveWrapperTypeMap.put(Long.class, long.class);
   primitiveWrapperTypeMap.put(Short.class, short.class);
   primitiveWrapperTypeMap.put(Void.class, void.class);

   // Map entry iteration is less expensive to initialize than forEach with lambdas
   for (Map.Entry<Class<?>, Class<?>> entry : primitiveWrapperTypeMap.entrySet()) {
      primitiveTypeToWrapperMap.put(entry.getValue(), entry.getKey());
      registerCommonClasses(entry.getKey());
   }

   Set<Class<?>> primitiveTypes = new HashSet<>(32);
   primitiveTypes.addAll(primitiveWrapperTypeMap.values());
   Collections.addAll(primitiveTypes, boolean[].class, byte[].class, char[].class,
         double[].class, float[].class, int[].class, long[].class, short[].class);
   for (Class<?> primitiveType : primitiveTypes) {
      primitiveTypeNameMap.put(primitiveType.getName(), primitiveType);
   }
    //这个方法的实现略,是将传入的对象放入commonClassCache中
   registerCommonClasses(Boolean[].class, Byte[].class, Character[].class, Double[].class,
         Float[].class, Integer[].class, Long[].class, Short[].class);
   registerCommonClasses(Number.class, Number[].class, String.class, String[].class,
         Class.class, Class[].class, Object.class, Object[].class);
   registerCommonClasses(Throwable.class, Exception.class, RuntimeException.class,
         Error.class, StackTraceElement.class, StackTraceElement[].class);
   registerCommonClasses(Enum.class, Iterable.class, Iterator.class, Enumeration.class,
         Collection.class, List.class, Set.class, Map.class, Map.Entry.class, Optional.class);

   Class<?>[] javaLanguageInterfaceArray = {Serializable.class, Externalizable.class,
         Closeable.class, AutoCloseable.class, Cloneable.class, Comparable.class};
   registerCommonClasses(javaLanguageInterfaceArray);
   javaLanguageInterfaces = new HashSet<>(Arrays.asList(javaLanguageInterfaceArray));
}

    在上面的resolvePrimitiveClassName方法中,先对长度做了一个判断,因为较长的packagename会影响执行的性能:

@Nullable
public static Class<?> resolvePrimitiveClassName(@Nullable String name) {
   Class<?> result = null;
   // Most class names will be quite long, considering that they
   // SHOULD sit in a package, so a length check is worthwhile.
   if (name != null && name.length() <= 7) {
      // Could be a primitive - likely.
      result = primitiveTypeNameMap.get(name);
   }
   return result;
}

ContextClassLoader

    最终加载Class依旧是通过ClassLoader的,先来看一下获取ClassLoader的方法实现:

public static ClassLoader getDefaultClassLoader() {
   ClassLoader cl = null;
   try {
       //优先获取ContextClassLoader
      cl = Thread.currentThread().getContextClassLoader();
   }
   catch (Throwable ex) {
      // Cannot access thread context ClassLoader - falling back...
   }
   if (cl == null) {
      // No thread context class loader -> use class loader of this class.
      //获取类本身的ClassLoader
      cl = ClassUtils.class.getClassLoader();
      if (cl == null) {
         // getClassLoader() returning null indicates the bootstrap ClassLoader
         try {
             //全都没有 则获取SystemClassLoader(默认是AppClassLoader)
            cl = ClassLoader.getSystemClassLoader();
         }
         catch (Throwable ex) {
            // Cannot access system ClassLoader - oh well, maybe the caller can live with null...
         }
      }
   }
   return cl;
}

      此处优先使用了ContextClassLoader作为 类加载器而非默认的AppClassLoader,在JVM源码解析 从 Launcher类浅谈ClassLoader中,提到了关于 类加载器的相关知识,使用ContextClassLoader是为了弥补双亲委派加载机制的对于自定义 类加载器的缺憾:那些自定义的 类加载器并没有机会上场,在使用了AppClassLoader后我们的自定义ClassLoader所加载的Class是无法被加载进去的,使用ContextClassLoader,我们可以在定义线程时,通过Thread的init方法(子线程调用,私有方法)或是setContextClassLoader直接指定使用自定义的ClassLoader。

    此外,ContextClassLoader的读取策略是默认读取父线程的(即创建当前线程的线程)contextClassLoader,当然 这是可以覆盖的,在由父线程调用的init方法中可以看到:

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;

    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    //…………部分代码略
    //读取父线程的contextClassLoader
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
     //…………部分代码略
}

SPI和JNDI

    ContextClassLoader其实并不是为我们日常开发提供的,JDK设计ContextClassLoader起初是由于 SPI的存在, SPI是由Java实现的一系列逻辑,对外开放了一系列接口以供扩展,但是由于 SPI由JDK实现,其由BootstrapClassLoader实现加载,对于第三方扩展是由AppClassLoader实现的,在 SPI的实现中并不能直接加载第三方Class,我们需要手动进行加载,譬如最开始的jdbc中,想要获得Connection:

Connection conn=null;
try {
    //手动加载驱动类,对应不同数据库其驱动也不相同
  Class.forName("com.mysql.jdbc.Driver",
                true, Thread.currentThread().getContextClassLoader());
  conn=DriverManager.getConnection("jdbc:mysql://dbserver?user=dolly&password=dagger");
  /* use the connection here */
  c.close();
} 
catch(Exception e) {
  e.printStackTrace();
} 
finally {
  if(conn!=null) {
    try {
      conn.close();
    } catch(SQLException e) {}
  }
}

    在JDK中,想要在没有AppClassLoader实现的环境下进行jdbc的加载,就需要用到ContextClassLoader了。所以在DriverManager的获取数据库连接方法中:

private static Connection getConnection(
    String url, java.util.Properties info, Class<?> caller) throws SQLException {
    /*
     * When callerCl is null, we should check the application's
     * (which is invoking this class indirectly)
     * classloader, so that the JDBC driver class outside rt.jar
     * can be loaded from here.
     */
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        // synchronize loading of the correct classloader.
        if (callerCL == null) {
            //使用ContextClassLoader
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }
    //……
}

参考文章:深入探讨 Java 类加载器-IBM  Developer

评论区
评论
{{comment.creator}}
{{comment.createTime}} {{comment.index}}楼
评论

JVM源码解析(2) ContextClassLoader与ClassUtil.forName()方法浅析

JVM源码解析(2) ContextClassLoader与ClassUtil.forName()方法浅析

    在Spring获取Context的源代码中,我们看到了对ClassUtil的方法调用,通过给定ClassName和ClassLoader进行Class的加载:

if (contextClassName != null) {
      try {
         return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
      }
      catch (ClassNotFoundException ex) {
         throw new ApplicationContextException(
               "Failed to load custom context class [" + contextClassName + "]", ex);
      }
   }

ClassUtil.forName()

    ClassUtil.forName是仅供于Spring内部使用的获取Class对象的方法,来看一下源码:

public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
      throws ClassNotFoundException, LinkageError {

   Assert.notNull(name, "Name must not be null");
   //缓存的name.length<=7简单类class,里面会先对长度做判断
   Class<?> clazz = resolvePrimitiveClassName(name);
   if (clazz == null) {
      //java.lang.*等其他简单类class
      clazz = commonClassCache.get(name);
   }
   if (clazz != null) {
      return clazz;
   }

   // "java.lang.String[]" style arrays
   //数组类型递归处理,不做详述
   if (name.endsWith(ARRAY_SUFFIX)) {
      String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
      Class<?> elementClass = forName(elementClassName, classLoader);
      return Array.newInstance(elementClass, 0).getClass();
   }

   // "[Ljava.lang.String;" style arrays
   if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) {
      String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1);
      Class<?> elementClass = forName(elementName, classLoader);
      return Array.newInstance(elementClass, 0).getClass();
   }

   // "[[I" or "[[Ljava.lang.String;" style arrays
   if (name.startsWith(INTERNAL_ARRAY_PREFIX)) {
      String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length());
      Class<?> elementClass = forName(elementName, classLoader);
      return Array.newInstance(elementClass, 0).getClass();
   }
   //获取默认的  类加载器   ClassLoader clToUse = classLoader;
   if (clToUse == null) {
      clToUse = getDefaultClassLoader();
   }
   try {
      return Class.forName(name, false, clToUse);
   }
   catch (ClassNotFoundException ex) {
      int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
      if (lastDotIndex != -1) {
         String innerClassName =
               name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
         try {
            return Class.forName(innerClassName, false, clToUse);
         }
         catch (ClassNotFoundException ex2) {
            // Swallow - let original exception get through
         }
      }
      throw ex;
   }
}

    首先 对于缓存的Class一块,在类的静态块中就能看出其逻辑:

static {
   primitiveWrapperTypeMap.put(Boolean.class, boolean.class);
   primitiveWrapperTypeMap.put(Byte.class, byte.class);
   primitiveWrapperTypeMap.put(Character.class, char.class);
   primitiveWrapperTypeMap.put(Double.class, double.class);
   primitiveWrapperTypeMap.put(Float.class, float.class);
   primitiveWrapperTypeMap.put(Integer.class, int.class);
   primitiveWrapperTypeMap.put(Long.class, long.class);
   primitiveWrapperTypeMap.put(Short.class, short.class);
   primitiveWrapperTypeMap.put(Void.class, void.class);

   // Map entry iteration is less expensive to initialize than forEach with lambdas
   for (Map.Entry<Class<?>, Class<?>> entry : primitiveWrapperTypeMap.entrySet()) {
      primitiveTypeToWrapperMap.put(entry.getValue(), entry.getKey());
      registerCommonClasses(entry.getKey());
   }

   Set<Class<?>> primitiveTypes = new HashSet<>(32);
   primitiveTypes.addAll(primitiveWrapperTypeMap.values());
   Collections.addAll(primitiveTypes, boolean[].class, byte[].class, char[].class,
         double[].class, float[].class, int[].class, long[].class, short[].class);
   for (Class<?> primitiveType : primitiveTypes) {
      primitiveTypeNameMap.put(primitiveType.getName(), primitiveType);
   }
    //这个方法的实现略,是将传入的对象放入commonClassCache中
   registerCommonClasses(Boolean[].class, Byte[].class, Character[].class, Double[].class,
         Float[].class, Integer[].class, Long[].class, Short[].class);
   registerCommonClasses(Number.class, Number[].class, String.class, String[].class,
         Class.class, Class[].class, Object.class, Object[].class);
   registerCommonClasses(Throwable.class, Exception.class, RuntimeException.class,
         Error.class, StackTraceElement.class, StackTraceElement[].class);
   registerCommonClasses(Enum.class, Iterable.class, Iterator.class, Enumeration.class,
         Collection.class, List.class, Set.class, Map.class, Map.Entry.class, Optional.class);

   Class<?>[] javaLanguageInterfaceArray = {Serializable.class, Externalizable.class,
         Closeable.class, AutoCloseable.class, Cloneable.class, Comparable.class};
   registerCommonClasses(javaLanguageInterfaceArray);
   javaLanguageInterfaces = new HashSet<>(Arrays.asList(javaLanguageInterfaceArray));
}

    在上面的resolvePrimitiveClassName方法中,先对长度做了一个判断,因为较长的packagename会影响执行的性能:

@Nullable
public static Class<?> resolvePrimitiveClassName(@Nullable String name) {
   Class<?> result = null;
   // Most class names will be quite long, considering that they
   // SHOULD sit in a package, so a length check is worthwhile.
   if (name != null && name.length() <= 7) {
      // Could be a primitive - likely.
      result = primitiveTypeNameMap.get(name);
   }
   return result;
}

ContextClassLoader

    最终加载Class依旧是通过ClassLoader的,先来看一下获取ClassLoader的方法实现:

public static ClassLoader getDefaultClassLoader() {
   ClassLoader cl = null;
   try {
       //优先获取ContextClassLoader
      cl = Thread.currentThread().getContextClassLoader();
   }
   catch (Throwable ex) {
      // Cannot access thread context ClassLoader - falling back...
   }
   if (cl == null) {
      // No thread context class loader -> use class loader of this class.
      //获取类本身的ClassLoader
      cl = ClassUtils.class.getClassLoader();
      if (cl == null) {
         // getClassLoader() returning null indicates the bootstrap ClassLoader
         try {
             //全都没有 则获取SystemClassLoader(默认是AppClassLoader)
            cl = ClassLoader.getSystemClassLoader();
         }
         catch (Throwable ex) {
            // Cannot access system ClassLoader - oh well, maybe the caller can live with null...
         }
      }
   }
   return cl;
}

      此处优先使用了ContextClassLoader作为 类加载器而非默认的AppClassLoader,在JVM源码解析 从 Launcher类浅谈ClassLoader中,提到了关于 类加载器的相关知识,使用ContextClassLoader是为了弥补双亲委派加载机制的对于自定义 类加载器的缺憾:那些自定义的 类加载器并没有机会上场,在使用了AppClassLoader后我们的自定义ClassLoader所加载的Class是无法被加载进去的,使用ContextClassLoader,我们可以在定义线程时,通过Thread的init方法(子线程调用,私有方法)或是setContextClassLoader直接指定使用自定义的ClassLoader。

    此外,ContextClassLoader的读取策略是默认读取父线程的(即创建当前线程的线程)contextClassLoader,当然 这是可以覆盖的,在由父线程调用的init方法中可以看到:

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;

    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    //…………部分代码略
    //读取父线程的contextClassLoader
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
     //…………部分代码略
}

SPI和JNDI

    ContextClassLoader其实并不是为我们日常开发提供的,JDK设计ContextClassLoader起初是由于 SPI的存在, SPI是由Java实现的一系列逻辑,对外开放了一系列接口以供扩展,但是由于 SPI由JDK实现,其由BootstrapClassLoader实现加载,对于第三方扩展是由AppClassLoader实现的,在 SPI的实现中并不能直接加载第三方Class,我们需要手动进行加载,譬如最开始的jdbc中,想要获得Connection:

Connection conn=null;
try {
    //手动加载驱动类,对应不同数据库其驱动也不相同
  Class.forName("com.mysql.jdbc.Driver",
                true, Thread.currentThread().getContextClassLoader());
  conn=DriverManager.getConnection("jdbc:mysql://dbserver?user=dolly&password=dagger");
  /* use the connection here */
  c.close();
} 
catch(Exception e) {
  e.printStackTrace();
} 
finally {
  if(conn!=null) {
    try {
      conn.close();
    } catch(SQLException e) {}
  }
}

    在JDK中,想要在没有AppClassLoader实现的环境下进行jdbc的加载,就需要用到ContextClassLoader了。所以在DriverManager的获取数据库连接方法中:

private static Connection getConnection(
    String url, java.util.Properties info, Class<?> caller) throws SQLException {
    /*
     * When callerCl is null, we should check the application's
     * (which is invoking this class indirectly)
     * classloader, so that the JDBC driver class outside rt.jar
     * can be loaded from here.
     */
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        // synchronize loading of the correct classloader.
        if (callerCL == null) {
            //使用ContextClassLoader
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }
    //……
}

参考文章:深入探讨 Java 类加载器-IBM  Developer


JVM源码解析(2) ContextClassLoader与ClassUtil.forName()方法浅析2020-08-16鱼鱼

{{commentTitle}}

评论   ctrl+Enter 发送评论