Spring源码学习(四)——ClassUtils.forName()

本文详细探讨了Spring框架中的ClassUtils.forName()方法,分析了其参数、类加载器的工作原理,以及方法内部如何处理简单类型、数组类型和异常情况。通过对源码的解读,揭示了该方法在动态加载类时的实现细节,特别是类加载器的双亲委派模型和对Java基本类型的处理机制。

    接上一篇,本篇针对ClassUtils.forName()方法进行研究。源码是这样事儿的:

public static Class<?> forName(String name, ClassLoader classLoader) throws ClassNotFoundException, LinkageError {
		Assert.notNull(name, "Name must not be null");

		Class<?> clazz = resolvePrimitiveClassName(name);
		if (clazz == null) {
			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 (clToUse != null ? clToUse.loadClass(name) : Class.forName(name));
		}
		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 (clToUse != null ? clToUse.loadClass(innerClassName) : Class.forName(innerClassName));
				}
				catch (ClassNotFoundException ex2) {
					// Swallow - let original exception get through
				}
			}
			throw ex;
		}
	}

     1.先看这个方法的两个参数,一个是要待获取类的类名:String name,一个是类加载器:ClassLoader classLoader。对于类加载器,从JVM的角度进行一下介绍:众所周知,java的类加载过程可以分为三个大步骤:装载、链接和初始化,类加载器就是来实现类加载的功能的。java为我们提供了三种不同类型的类加载器,BootstrapClassLoader、ExtensionClassLoader和AppClassLoader,三种类加载器分别针对核心类、扩展类和classPath下以及我们自定义的jar进行加载。类加载器根据双亲委派模型进行工作,即当一个类被加载时,先交由父加载器进行,如果父加载器无法找到这个类,再交给子加载器进行加载。当然,我们可以自定义子加载器进行个性化加载。如下图所示。ClassUtils.forName()这个方法通过反射机制实现动态加载,当然要指定一个类加载器了。

    2.说完参数,继续往下走。首先映入眼帘的是一个name不能为空的断言,这个也是调用的spring自己封装的断言类Assert。接下来,调用resolvePrimitiveClassName,如果name指定的是java的一个简单类型,那么就根据jvm的命名规则返回对应的类。进入resolvePrimitiveClassName(String name)方法里面,源码在这儿:

public static Class<?> resolvePrimitiveClassName(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() <= 8) {
			// Could be a primitive - likely.
			result = primitiveTypeNameMap.get(name);
		}
		return result;
	}

可以看到,有一个很巧妙的地方,因为java中大多数的类名加上包名都很长,而简单类型的相对要短很多,因此这个方法先根据name的长度对大多数的类进行过滤;接着就是从缓存里取值——primitiveTypeNameMap.get(name)。primitiveTypeNameMap是ClassUtils维护的一个map,用于存储涉及简单类型的所有的类和名称的关系映射,该map存储东西的源码如下:

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);

		for (Map.Entry<Class<?>, Class<?>> entry : primitiveWrapperTypeMap.entrySet()) {
			primitiveTypeToWrapperMap.put(entry.getValue(), entry.getKey());
			registerCommonClasses(entry.getKey());
		}

		Set<Class<?>> primitiveTypes = new HashSet<Class<?>>(32);
		primitiveTypes.addAll(primitiveWrapperTypeMap.values());
		primitiveTypes.addAll(Arrays.asList(new Class<?>[] {
				boolean[].class, byte[].class, char[].class, double[].class,
				float[].class, int[].class, long[].class, short[].class}));
		primitiveTypes.add(void.class);
		for (Class<?> primitiveType : primitiveTypes) {
			primitiveTypeNameMap.put(primitiveType.getName(), primitiveType);
		}

打断点调试可以看到,primitiveTypeNameMap存放的内容是这个:{void=void, double=double, byte=byte, [B=class [B, [C=class [C, [D=class [D, [F=class [F, float=float, int=int, long=long, [I=class [I, [J=class [J, boolean=boolean, char=char, [S=class [S, short=short, [Z=class [Z},我们可以看到,除了存放简单类型,还存放了简单类型的数组([C等类似表达是JNI字段描述符的表达,比如[C代表char[],下图是对应表)。

    3.显然,javax.inject.provider不是简单类型,接着往下走,如果不是基本类型,就会从commonClassCache里去取值,commonClassCathe存储了所有Java.lang包中的类,显而易见我们这个不在lang包里,继续往下走。

   4.对类是否为数组对象、二进制数组对象和二维数组对象进行判断,显然都不是,那么断点来到这里:

ClassLoader clToUse = classLoader;

搞一个类加载器。继续往下走,如果传进来的类加载器是null,就弄一个默认的类加载器,进方法看看:

public static ClassLoader getDefaultClassLoader() {
		ClassLoader cl = null;
		try {
			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.
			cl = ClassUtils.class.getClassLoader();
			if (cl == null) {
				// getClassLoader() returning null indicates the bootstrap ClassLoader
				try {
					cl = ClassLoader.getSystemClassLoader();
				}
				catch (Throwable ex) {
					// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
				}
			}
		}
		return cl;
	}

如果可以的话,先返回contextClassLoader,也就是先默认获取线程上下文类加载器,如果当前线程的如果拿不到,就返回加载ClassUtils的加载器,如果还拿不到,就返回java提供的systemClassLoader。接着回去看。我们调用forName方法的时候传进去的classLoader是这个:sun.misc.Launcher$AppClassLoader@18b4aac2,这个是AppclassLoader,然后发现,javax.inject.provider无法通过AppclassLoader拿到,报ClassNotFoundException异常,catch到以后接着往下走。

5.catch到以后,程序就将name代表的class作为内部类进行处理,也就是说name变为javax.inject$Provider,然后调用classloader传入的加载器对其进行加载,也就是AppCLassLoader;最后抛出异常,由上一层继续捕捉,至此ClassUtils.forName()执行完毕。

<think>我们正在讨论SpringBoot中动态加载类与构造函数的代码逻辑。根据用户的问题,重点在于理解`ClassUtils.forName`和`getDeclaredConstructor`的使用。首先,我们需要知道`ClassUtils.forName`是Spring框架提供的一个工具方法,用于加载类。它比Java原生的`Class.forName`更加强大,特别是在处理原始类型和数组类时,同时也能够适应不同的类加载器环境。然后,`getDeclaredConstructor`是`Class`类的一个方法,用于获取类的特定构造函数(通过参数类型来匹配)。用户提到的代码可能是这样的:```javaClass<?>clazz=ClassUtils.forName("org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration",classLoader);Constructor<?>constructor=clazz.getDeclaredConstructor(Class.class,ObjectProvider.class,ObjectProvider.class);```下面逐步解释:1.**使用`ClassUtils.forName`加载类**:-第一个参数是类的全限定名(FullyQualifiedName),例如`"org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration"`。-第二个参数是类加载器(`classLoader`),通常使用当前线程的上下文类加载器或特定的类加载器。-这个方法会尝试加载指定的类。如果找不到类,它会抛出`ClassNotFoundException`。2.**使用`getDeclaredConstructor`获取构造函数**:-在得到类的`Class`对象后,调用`getDeclaredConstructor`方法,传入构造函数的参数类型(以`Class`对象的形式)。-例如,如果构造函数有三个参数,分别是`Class`类型、`ObjectProvider`类型和`ObjectProvider`类型,那么我们就传入这三个类型的`Class`对象。-注意:这里需要确保传入的参数类型顺序和数量与目标构造函数完全一致。3.**获取到构造函数后**:-通常,如果构造函数是私有的,我们还需要设置可访问性(`constructor.setAccessible(true)`)。-然后就可以使用`constructor.newInstance(...)`来创建实例。在SpringBoot中,这种动态加载和实例化的方式常用于条件化配置。例如,`LettuceConnectionConfiguration`类可能在类路径中存在Lettuce客户端时才被加载和初始化。现在,结合用户提供的引用:引用[1]提到SpringBoot在初始化阶段会加载`ApplicationContextInitializer`和`ApplicationListener`,这些内建的实现类是通过`spring.factories`文件配置的。同样,动态加载配置类也是类似的机制,通过读取`spring.factories`中的配置,然后使用反射加载并实例化。引用[2]和[3]提到了构造器注入和Bean的实例化问题。在Spring中,如果类有多个构造函数,需要指定使用哪一个(例如通过`@Autowired`注解),否则会报错。而在动态加载的情况下,我们显式指定了要调用的构造函数,因此不会出现歧义。因此,这段代码的含义是:动态加载`LettuceConnectionConfiguration`类,并获取其特定的构造函数(参数类型为`Class,ObjectProvider,ObjectProvider`),以便后续通过该构造函数创建实例。相关问题:1.SpringBoot中,动态加载类时如何处理类不存在的情况?2.除了`ClassUtils.forName`,Spring框架还提供了哪些类加载工具?3.在获取构造函数时,如果参数类型不匹配会怎样?如何避免?</think>### Spring Boot 中动态加载类与构造函数的逻辑解析 在 Spring Boot 中,`ClassUtils.forName` 和 `getDeclaredConstructor` 用于实现**动态类加载和实例化**,这是框架实现自动配置的关键机制。以下是具体逻辑分析: #### 1. **`ClassUtils.forName` 动态加载类** ```java // 示例代码片段 Class<?> clazz = ClassUtils.forName( "org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration", classLoader ); ``` - **作用**:根据全限定类名动态加载类 - **关键点**: - 使用 Spring 的 `ClassUtils` 而非原生 `Class.forName()`,增强容错性(如处理原始类型、数组类型) - 第二个参数 `classLoader` 指定类加载器(通常为当前线程的上下文类加载器) - 若类不存在或加载失败,抛出 `ClassNotFoundException` 或 `LinkageError` - **应用场景**:在自动配置阶段,通过 `META-INF/spring.factories` 中配置的类名动态加载配置类[^1] #### 2. **`getDeclaredConstructor` 获取构造函数** ```java // 获取特定参数类型的构造函数 Constructor<?> constructor = clazz.getDeclaredConstructor( RedisProperties.class, ObjectProvider.class, ObjectProvider.class ); ``` - **作用**:反射获取类的特定构造函数 - **关键点**: - 参数列表必须与目标构造函数的参数类型**完全匹配**(包括顺序和类型) - 可访问私有构造函数(需配合 `setAccessible(true)`) - 若找不到匹配的构造函数,抛出 `NoSuchMethodException` - **设计意义**:解决 Spring 依赖注入中的构造器选择问题(当存在多个构造函数时需明确指定)[^3] #### 3. **完整实例化流程 ```java // 典型使用场景 Constructor<?> constructor = clazz.getDeclaredConstructor(RedisProperties.class, ...); constructor.setAccessible(true); // 允许访问私有构造 Object instance = constructor.newInstance(redisProperties, ...); ``` 1. 通过 `spring.factories` 发现配置类名 2. 动态加载目标类(如 `LettuceConnectionConfiguration`) 3. 反射获取匹配依赖项的构造函数 4. 实例化对象并注入所需依赖(如 `RedisProperties`) #### 4. **设计动机** - **解耦**:避免硬编码类引用,支持条件化配置(如类路径存在时才加载) - **扩展性**:允许第三方库通过 `spring.factories` 注册自动配置类 - **依赖管理**:通过构造函数参数显式声明依赖,符合 Spring 的依赖注入原则[^3] #### 5. **异常处理** - `ClassNotFoundException`:检查类路径是否包含目标库(如 `lettuce-core`) - `NoSuchMethodException`:确认构造函数参数类型与依赖是否匹配 - `InstantiationException`:检查目标类是否抽象类或接口 > **典型场景**:当 Spring Boot 检测到 `redis` 和 `lettuce` 在类路径中时,通过此机制动态创建 `LettuceConnectionConfiguration` Bean,完成 Redis 连接的自动化配置[^1]。
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值