在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作为
此外,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起初是由于
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(); } } //…… }