一、概念
内省(Introspection)是Java Bean规范中的核心概念,它让程序能够在运行时自动分析Bean类的属性结构,这是Spring BeanUtils、JSON序列化、ORM框架等技术的基础。虽然内省提供了强大的动态能力,但在高并发场景下可能导致ClassLoader死锁(待确定,感觉不会,可能跨应用会,没有验证),如果发生死锁,可以通过预热机制来解决的问题。
Java内省(Introspection)机制是一种允许开发者在运行时获取和操作对象属性、事件和方法信息的机制。它基于Java的反射(Reflection)API,但更专注于Java Beans的特定规范。Java Beans是一种特殊的Java类,通常用于封装多个属性为一个单一的对象,并提供标准的getter和setter方法来访问这些属性。它允许我们在运行时检查和操作Java对象的内部结构和属性。
1.1 核心组件
Introspector类:这是Java内省机制的核心类,提供了获取Bean信息的静态方法。
BeanInfo类:该类包含了关于Java Bean的元数据信息,如属性、事件和方法等。
PropertyDescriptor类:该类描述了Java Bean的一个属性,包括其getter和setter方法。
1.2 工作原理
从作用上看,就相当于为类的元数据做了一层缓存机制,当有场景需要来获取类的元数据信息时,就可以从缓存中获取,加速这一过程。
Java内省机制主要依赖于java.beans包,其中核心的类是Introspector。这个类提供了用于获取Java Bean的元数据信息(如属性、事件和方法)的静态方法。当我们想要内省一个Java类时,Introspector会分析这个类的公共方法,并根据特定的命名规范(如getter和setter方法)来推断出类的属性。
内省机制不仅限于检查类的属性,它还可以用来探索类的方法、构造函数以及事件。然而,在实际应用中,内省最常用于处理Java Bean的属性,因为这些属性通常通过标准的getter和setter方法来访问。为了安全地使用内省机制,我们应该处理可能抛出的异常,如IntrospectionException
,这可能在分析类的结构时发生。
二、相关使用
在很多场景中都有应用:
- 对象序列化、反序列化
- 框架工具库开发
- 动态脚本插件系统等
- Bean拷贝
这些场景,都会获取类的元数据信息,所以都会使用到introspector。其中使用Bean拷贝来介绍:
2.1 Spring Bean拷贝
Spring的Bean拷贝机制中,使用 BeanUtils.copyProperties 方法,其中会使用:
// org.springframework.beans.BeanUtils
public static void copyProperties(Object source, Object target) throws BeansException {
copyProperties(source, target, null, (String[]) null);
}
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
@Nullable String... ignoreProperties) throws BeansException {
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
Class<?> actualEditable = target.getClass();
if (editable != null) {
// ...
}
// 关键点1:获取目标类的属性描述符
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
// 关键点2:获取源类的缓存内省结果
CachedIntrospectionResults sourceResults = (source.getClass() != actualEditable ?
CachedIntrospectionResults.forClass(source.getClass()) : null);
for (PropertyDescriptor targetPd : targetPds) {
// 属性复制逻辑...
}
}
// 3. getPropertyDescriptors() - 第一个触发点
public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException {
// 这里会调用CachedIntrospectionResults.forClass()
return CachedIntrospectionResults.forClass(clazz).getPropertyDescriptors();
}
// 4. CachedIntrospectionResults.forClass() - 关键缓存点
static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException {
CachedIntrospectionResults results = strongClassCache.get(beanClass);
if (results != null) {
return results;
}
// 如果缓存中没有,创建新的内省结果
results = new CachedIntrospectionResults(beanClass);
CachedIntrospectionResults existing = strongClassCache.putIfAbsent(beanClass, results);
return (existing != null ? existing : results);
}
// 5. CachedIntrospectionResults构造函数
private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
try {
// 关键点:这里调用Java标准的Introspector.getBeanInfo()
BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
// 处理属性描述符...
this.propertyDescriptors = new LinkedHashMap<>();
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
// ...
}
catch (IntrospectionException ex) {
throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex);
}
}
// 6. Introspector.getBeanInfo() - Java标准库
public static BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
return getBeanInfo(beanClass, null);
}
public static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass) throws IntrospectionException {
// 这里会调用getFromCache()
return getFromCache(beanClass);
}
// java.beans.Introspector (简化版)
private static BeanInfo getFromCache(Class<?> beanClass) {
// 这里会触发类加载,涉及ClassLoader同步
// 多个线程同时访问时,会在Class.forName0()处阻塞
// 内部会调用:
Class.forName0(Native Method)
}
其中,对于Introspection
类,会带有字段:
private static final WeakCache<Class<?>, Method[]> declaredMethodCache = new WeakCache<>();
// put方法
public void put(K key, V value) {
if (value != null) {
// value为弱引用
this.map.put(key, new WeakReference<V>(value));
}
else {
this.map.remove(key);
}
}
通过这个弱引用缓存,来加速获取类的元信息的速度。如果缓存中不存在对应类的元数据信息,则会调用本地方法进行获取。
其中就会加锁获取: TomcatEmbeddedWebappClassLoader
。
org.springframework.boot.loader.LaunchedURLClassLoader
是TomcatEmbeddedWebappClassLoader
的父类加载器 。是 Spring Boot 在其内嵌 Tomcat 容器中使用的一个类加载器(ClassLoader)。在 Spring Boot 应用中,当你选择将应用打包成可执行的 JAR 或 WAR 文件,并希望以内嵌的 Tomcat 服务器来运行这个应用时,TomcatEmbeddedWebappClassLoader
就扮演了关键角色。当检测到应用应该以内嵌 Tomcat 的形式运行时,Spring Boot 会自动配置 Tomcat 服务器,并选择合适的类加载器(如TomcatEmbeddedWebappClassLoader
)来加载应用的类和资源。
就这导致并发场景下,后续需要获取类的原信息的线程会进行阻塞等待,进入到操作系统的管程等待列表中进行Block。
结果就会造成瞬时大量线程的Block,jstack输出结果为:
"http-nio-6097-exec-200" #248 daemon prio=5 os_prio=0 tid=0x... nid=0x... waiting for monitor entry
java.lang.Thread.State: BLOCKED (on object monitor)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at java.beans.Introspector.getBeanInfo(Introspector.java:...)
at org.springframework.beans.CachedIntrospectionResults.<init>(CachedIntrospectionResults.java:...)
at org.springframework.beans.CachedIntrospectionResults.forClass(CachedIntrospectionResults.java:...)
at org.springframework.beans.BeanUtils.getPropertyDescriptors(BeanUtils.java:...)
at org.springframework.beans.BeanUtils.copyProperties(BeanUtils.java:...)
at com.zhuanzhuan.qc.brain.service.AuthService.getUserById(AuthService.java:197)
三、相关知识点
3.1 CachedIntrospectionResults 缓存结果的使用
CachedIntrospectionResults
是 Spring 框架中的一个内部工具类,主要用于缓存 JavaBeans 的 PropertyDescriptor
信息。
主要作用
- 缓存 Java 类的 JavaBeans 属性描述符信息
- 避免使用 JDK 系统级的 BeanInfo 缓存,防止在共享 JVM 中单个应用关闭时发生内存泄漏
- 提高 Spring 框架处理 bean 属性时的性能
底层运作机制
通过静态缓存存储类的内省结果,避免重复创建对象:
使用两种缓存策略:
strongClassCache
:强引用缓存,用于"安全"的类加载器加载的类softClassCache
:软引用缓存,用于"不安全"的类加载器加载的类
类加载器安全性判断:
如果类加载器是"安全的"(与 Spring 类在同一加载器或父类加载器中或被明确接受),使用强引用缓存.否则使用软引用缓存,允许垃圾回收。
主要字段作用
// 控制是否忽略 BeanInfo 类的系统属性
public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
// 存储 BeanInfoFactory 实例
private static final List<BeanInfoFactory> beanInfoFactories = SpringFactoriesLoader.loadFactories(
BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader());
// 被接受的类加载器集合,即使它们不符合缓存安全条件
static final Set<ClassLoader> acceptedClassLoaders = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
// 强引用缓存,用于缓存安全的 bean 类
static final ConcurrentMap<Class<?>, CachedIntrospectionResults> strongClassCache = new ConcurrentHashMap<>(64);
// 软引用缓存,用于非缓存安全的 bean 类
static final ConcurrentMap<Class<?>, CachedIntrospectionResults> softClassCache = new ConcurrentReferenceHashMap<>(64);
// 实际存放类元数据, 三个字段共同构成了 Spring 框架高效处理 Bean 属性的基础
/**
* 存储 Java 标准的 BeanInfo 对象,包含了 bean 类的所有内省信息
* 通过 Introspector.getBeanInfo() 或自定义 BeanInfoFactory 获取
* 包含了类的属性、方法、事件等元数据信息
*/
private final BeanInfo beanInfo;
/**
* 存储属性名到 PropertyDescriptor 的映射
* PropertyDescriptor 描述了 bean 的一个属性,包含读方法(getter)、写方法(setter)、属性类型等信息
* 使用 LinkedHashMap 保持属性的顺序
*/
private final Map<String, PropertyDescriptor> propertyDescriptors;
/**
* 存储 PropertyDescriptor 到 TypeDescriptor 的映射
* TypeDescriptor 提供了比 PropertyDescriptor 更丰富的类型信息,包括泛型信息
* 使用 ConcurrentReferenceHashMap 实现线程安全和内存敏感的缓存
*/
private final ConcurrentMap<PropertyDescriptor, TypeDescriptor> typeDescriptorCache;
主要方法逻辑
forClass(Class<?> beanClass)
static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException {
// 先从强引用缓存查找
CachedIntrospectionResults results = strongClassCache.get(beanClass);
if (results != null) {
return results;
}
// 再从软引用缓存查找
results = softClassCache.get(beanClass);
if (results != null) {
return results;
}
// 都没有找到,创建新的缓存结果
results = new CachedIntrospectionResults(beanClass);
// 决定使用哪种缓存
ConcurrentMap<Class<?>, CachedIntrospectionResults> classCacheToUse;
if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) ||
isClassLoaderAccepted(beanClass.getClassLoader())) {
// 类加载器安全性判断,则使用使用强引用缓存
classCacheToUse = strongClassCache;
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe");
}
// 否则则使用软引用缓存
classCacheToUse = softClassCache;
}
// 存入缓存并返回
CachedIntrospectionResults existing = classCacheToUse.putIfAbsent(beanClass, results);
return (existing != null ? existing : results);
}
构造函数 CachedIntrospectionResults(Class<?> beanClass)
/**
* Create a new CachedIntrospectionResults instance for the given class.
*
* 这个构造函数负责创建给定类的内省结果缓存,包括:
* 1. 获取类的BeanInfo
* 2. 提取并处理所有PropertyDescriptor
* 3. 处理接口中的属性
* 4. 处理无前缀访问器方法
*
* @param beanClass 要分析的bean类
* @throws BeansException 如果内省过程失败
*/
private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
try {
// 记录日志,表明正在获取类的BeanInfo
if (logger.isTraceEnabled()) {
logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]");
}
// 调用getBeanInfo方法获取类的BeanInfo
// 该方法会先尝试使用自定义的BeanInfoFactory,如果没有则使用标准的Introspector
this.beanInfo = getBeanInfo(beanClass);
// 记录日志,表明正在缓存PropertyDescriptor
if (logger.isTraceEnabled()) {
logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]");
}
// 初始化propertyDescriptors映射,使用LinkedHashMap保持属性的顺序
this.propertyDescriptors = new LinkedHashMap<>();
// 创建一个集合来存储已处理的读方法名称,用于后续检查
Set<String> readMethodNames = new HashSet<>();
// 获取BeanInfo中的所有PropertyDescriptor
// 这个调用比较慢,所以只执行一次
PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
// 特殊处理Class类的属性,只允许name相关的属性
if (Class.class == beanClass && (!"name".equals(pd.getName()) && !pd.getName().endsWith("Name"))) {
continue;
}
// 忽略ClassLoader和ProtectionDomain类型的属性
// 这些类型通常不需要进行数据绑定,且可能导致安全问题
if (pd.getPropertyType() != null && (ClassLoader.class.isAssignableFrom(pd.getPropertyType())
|| ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) {
continue;
}
// 记录找到的bean属性信息
if (logger.isTraceEnabled()) {
logger.trace("Found bean property '" + pd.getName() + "'" +
(pd.getPropertyType() != null ? " of type [" + pd.getPropertyType().getName() + "]" : "") +
(pd.getPropertyEditorClass() != null ?
"; editor [" + pd.getPropertyEditorClass().getName() + "]" : ""));
}
// 创建支持泛型的PropertyDescriptor
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
// 将属性描述符添加到映射中
this.propertyDescriptors.put(pd.getName(), pd);
// 记录读方法名称
Method readMethod = pd.getReadMethod();
if (readMethod != null) {
readMethodNames.add(readMethod.getName());
}
}
// 显式检查实现的接口中的setter/getter方法
// 特别是Java 8的默认方法
Class<?> currClass = beanClass;
while (currClass != null && currClass != Object.class) {
// 递归处理当前类实现的所有接口
introspectInterfaces(beanClass, currClass, readMethodNames);
// 向上遍历类层次结构
currClass = currClass.getSuperclass();
}
// 检查无前缀访问器方法,如"lastName()"
// - 直接引用同名实例字段的访问器方法
// - Java 15 record类的组件访问器使用相同的约定
introspectPlainAccessors(beanClass, readMethodNames);
// 初始化类型描述符缓存
// 使用ConcurrentReferenceHashMap提供线程安全和内存敏感的缓存
this.typeDescriptorCache = new ConcurrentReferenceHashMap<>();
}
catch (IntrospectionException ex) {
// 如果内省过程失败,抛出致命Bean异常
throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex);
}
}
构造函数完成了以下工作:
- 获取 BeanInfo:调用 getBeanInfo(beanClass) 获取类的 BeanInfo
- 初始化 propertyDescriptors 映射:
○ 处理标准 JavaBean 属性(通过 getter/setter 定义的属性)
○ 过滤掉不需要的属性(如 ClassLoader 和 ProtectionDomain 类型)
○ 使用 GenericTypeAwarePropertyDescriptor 增强属性描述符,支持泛型 - 处理接口中的属性:通过 introspectInterfaces 方法递归检查接口中定义的属性
- 处理无前缀访问器:通过 introspectPlainAccessors 方法处理 record 风格的访问器(如 lastName())
- 初始化 typeDescriptorCache:创建一个空的并发引用哈希映射
introspectInterfaces 方法
/**
* 检查指定类实现的接口中的属性
*
* 这个方法递归地检查类实现的所有接口,查找可能在接口中定义的属性
* 特别是处理Java 8引入的接口默认方法,这些方法可能定义了getter/setter
*
* @param beanClass 原始bean类
* @param currClass 当前正在处理的类
* @param readMethodNames 已处理的读方法名称集合
* @throws IntrospectionException 如果内省过程失败
*/
private void introspectInterfaces(Class<?> beanClass, Class<?> currClass, Set<String> readMethodNames)
throws IntrospectionException {
// 遍历当前类实现的所有接口
for (Class<?> ifc : currClass.getInterfaces()) {
// 跳过Java语言内置接口(如Serializable, Cloneable等)
if (!ClassUtils.isJavaLanguageInterface(ifc)) {
// 获取接口的所有属性描述符
for (PropertyDescriptor pd : getBeanInfo(ifc).getPropertyDescriptors()) {
// 检查该属性是否已存在于propertyDescriptors中
PropertyDescriptor existingPd = this.propertyDescriptors.get(pd.getName());
// 如果属性不存在,或者现有属性没有读方法但新属性有读方法,则使用新属性
if (existingPd == null ||
(existingPd.getReadMethod() == null && pd.getReadMethod() != null)) {
// GenericTypeAwarePropertyDescriptor会宽松地解析set*写方法
// 与声明的读方法匹配,所以这里优先使用有读方法的描述符
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
// 忽略ClassLoader和ProtectionDomain类型的属性
if (pd.getPropertyType() != null && (ClassLoader.class.isAssignableFrom(pd.getPropertyType())
|| ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) {
continue;
}
// 将属性添加到映射中
this.propertyDescriptors.put(pd.getName(), pd);
Method readMethod = pd.getReadMethod();
if (readMethod != null) {
readMethodNames.add(readMethod.getName());
}
}
}
// 递归处理接口的接口
introspectInterfaces(ifc, ifc, readMethodNames);
}
}
}
introspectPlainAccessors 方法
/**
* 检查无前缀访问器方法
*
* 这个方法查找符合以下条件的方法:
* 1. 方法名与类中的字段名相同
* 2. 没有参数
* 3. 返回类型不是void
* 4. 不是静态方法
* 5. 不是Object类中定义的方法
*
* 这种模式在Java 14+的record类中很常见,如record Point(int x, int y)
* 会生成x()和y()方法而不是getX()和getY()
*
* @param beanClass 要检查的bean类
* @param readMethodNames 已处理的读方法名称集合
* @throws IntrospectionException 如果内省过程失败
*/
private void introspectPlainAccessors(Class<?> beanClass, Set<String> readMethodNames)
throws IntrospectionException {
// 遍历类的所有公共方法
for (Method method : beanClass.getMethods()) {
// 检查方法名是否已作为属性名或读方法名处理过
if (!this.propertyDescriptors.containsKey(method.getName()) &&
!readMethodNames.contains((method.getName())) && isPlainAccessor(method)) {
// 创建新的PropertyDescriptor,将方法名作为属性名
// 将方法作为读方法,写方法为null
this.propertyDescriptors.put(method.getName(),
new GenericTypeAwarePropertyDescriptor(beanClass, method.getName(), method, null, null));
// 记录读方法名
readMethodNames.add(method.getName());
}
}
}
isPlainAccessor 方法
/**
* 判断一个方法是否是无前缀访问器
*
* 无前缀访问器需要满足以下条件:
* 1. 没有参数
* 2. 返回类型不是void
* 3. 不是Object类中定义的方法
* 4. 不是静态方法
* 5. 方法名与类中的字段名相同
*
* @param method 要检查的方法
* @return 如果方法是无前缀访问器则返回true
*/
private boolean isPlainAccessor(Method method) {
// 检查基本条件
if (method.getParameterCount() > 0 || method.getReturnType() == void.class ||
method.getDeclaringClass() == Object.class || Modifier.isStatic(method.getModifiers())) {
return false;
}
try {
// 检查方法名是否与类中的字段名相同
// 通过尝试获取同名字段来判断
method.getDeclaringClass().getDeclaredField(method.getName());
return true;
}
catch (Exception ex) {
// 如果找不到同名字段,则不是无前缀访问器
return false;
}
}
getBeanInfo()
getBeanInfo 方法是 CachedIntrospectionResults 类中的一个关键方法,负责获取指定类的 BeanInfo 对象。
/**
* 获取指定类的BeanInfo描述符
*
* 这个方法首先尝试使用所有注册的BeanInfoFactory来创建BeanInfo。
* 如果没有工厂能够创建BeanInfo,则回退到标准的Introspector。
*
* @param beanClass 要内省的目标类
* @return 结果BeanInfo描述符(永远不会为null)
* @throws IntrospectionException 如果底层Introspector发生异常
*/
private static BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
// 首先尝试使用所有注册的BeanInfoFactory
for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
// 调用工厂的getBeanInfo方法
BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanClass);
// 如果工厂成功创建了BeanInfo,直接返回
if (beanInfo != null) {
return beanInfo;
}
}
// 如果没有工厂能够创建BeanInfo,回退到标准的Introspector
// 根据配置决定是否忽略所有BeanInfo类
return (shouldIntrospectorIgnoreBeaninfoClasses ?
// 忽略所有BeanInfo类,只使用反射
Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
// 使用标准方式,包括查找自定义的BeanInfo类
Introspector.getBeanInfo(beanClass));
}
getBeanInfo 方法的主要目的是获取一个类的 BeanInfo 对象,这个对象包含了类的所有内省信息,如属性、方法、事件等。这是 Java Bean 规范的核心部分,Spring 框架利用这些信息来实现属性访问、数据绑定等功能。
beanInfoFactories:
private static final List<BeanInfoFactory> beanInfoFactories = SpringFactoriesLoader.loadFactories(
BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader());
通过 Spring 的工厂加载机制加载的所有 BeanInfoFactory 实现。这允许用户提供自定义的 BeanInfo 创建逻辑,扩展 Spring 的内省能力。
BeanInfoFactory 接口是 Spring 提供的一个扩展点,允许用户自定义 BeanInfo 的创建逻辑:
public interface BeanInfoFactory {
/**
* Return the bean info for the given class, if supported.
* @param beanClass the bean class
* @return the BeanInfo, or {@code null} if the given class is not supported
* @throws IntrospectionException in case of exceptions
*/
@Nullable
BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException;
}
可以实现这个接口,并通过 Spring 的 META-INF/spring.factories 机制注册,从而影响 Spring 的内省行为
shouldIntrospectorIgnoreBeaninfoClasses:
private static final boolean shouldIntrospectorIgnoreBeaninfoClasses =
SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);
这个标志控制是否忽略自定义的 BeanInfo 类。它由系统属性 spring.beaninfo.ignore 控制。
Introspector.IGNORE_ALL_BEANINFO
是 JDK Introspector 类的一个常量,指示 Introspector 忽略所有自定义的 BeanInfo 类,只使用反射来获取类信息。