【记录版】SpringBoot下ObjectProvider类源码及其执行机制解读

文章探讨了SpringBoot中ObjectProvider的使用,它在自动配置类的Bean方法中频繁出现,封装了依赖注入的逻辑。ObjectProvider提供了一致的接口,处理了实例存在性、唯一性和多实例情况。文章深入解析了ObjectProvider的源码和依赖解析过程,强调了其在简化代码和保证安全稳定方面的作用。

主题: SpringBoot + ObjectProvider + DependencyDescriptor + ConstructorResolver

背景: SpringBoot是目前最受欢迎的开源框架之一,其自动配置特性让我们从以前繁复的配置中解放出来,如果愿意了解某组件封装的机制,其自动配置类源码阅读必不可少。在自动配置类源码中,@Bean注解的方法中,ObjectProvider类参数出现的频率极高,为什么框架层喜欢使用这个类呢?基于好奇,个人对此相关源码进行阅读,并在此记录和个人解读。

内置容器自动配置框架代码示例:

package org.springframework.boot.autoconfigure.web.servlet;

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
    @ConditionalOnMissingBean(value = {ServletWebServerFactory.class},search = SearchStrategy.CURRENT)
    static class EmbeddedTomcat {
        @Bean
        TomcatServletWebServerFactory tomcatServletWebServerFactory(ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers, ObjectProvider<TomcatContextCustomizer> contextCustomizers, ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
            TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
            factory.getTomcatConnectorCustomizers().addAll((Collection)connectorCustomizers.orderedStream().collect(Collectors.toList()));
            factory.getTomcatContextCustomizers().addAll((Collection)contextCustomizers.orderedStream().collect(Collectors.toList()));
            factory.getTomcatProtocolHandlerCustomizers().addAll((Collection)protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
            return factory;
        }
    }
}

可以看到tomcatServletWebServerFactory方法的三个参数都是都是ObjectProvider类型,泛型为各特性自定义类。

一、ObjectProvider接口定义

public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
    // ObjectFactory接口方法,获取无参构造参数化泛型类实例,没有则报NoSuchBeanDefinitionException
	T getObject() throws BeansException;

    // 根据构造参数获取对应参数化泛型类实例,没有则报NoSuchBeanDefinitionException
	T getObject(Object... var1) throws BeansException;

	// 获取无参构造参数化泛型类实例,没有则返回空
    @Nullable
    T getIfAvailable() throws BeansException;

	..................省略.........................
	
	// 唯一限制,如果存在多个实例,则返回null
    @Nullable
    T getIfUnique() throws BeansException;

	// 多参数化包装类实例,作为迭代器使用
    default Iterator<T> iterator() {
        return this.stream().iterator();
    }

	// 参数化包装类集合流式使用
    default Stream<T> stream() {
        throw new UnsupportedOperationException("Multi element access not supported");
    }

	// 参数化包装类集合有序流式使用
    default Stream<T> orderedStream() {
        throw new UnsupportedOperationException("Ordered element access not supported");
    }
}

接口定义部分方法已隐藏,根据ObjectProvider的接口可以看到,此类其实将其泛型参数化类的注入处理做了一层封装,将泛型参数化类实现类是否存在、是否唯一、多实例流式处理等场景归纳为统一的接口方法,提升框架层代码的抽象和整洁。

二、ObjectProvider解析源码
仅通过类参数定义ObjectProvider实例是没有意义的,其底层肯定会有对应的解析,我们真正需要的还是参数化类型中的实例对象,这其中要依靠BeanFactory和DependencyDescriptor等相关类的支持。
我们知道@Configuration + @Bean 注解发布的实例和普通@Componet注解发布的实例,无论解析还是实例化的逻辑都存在差异,@Bean注解的方法实例,其解析后的BeanDefinition的factoryMethodName属性为对应的方法名,会使用BeanFactory.instantiateUsingFactoryMethod(beanName, mbd, args) 方法实例化,方法参数则作为dependsOn依赖注入并完成其初始化。

执行流程如下:
1、BeanFactory实例化@Bean注解Bean

BeanFactory.instantiateUsingFactoryMethod(beanName, mbd, args)

2、构造器解析类包装实例化流程

(new ConstructorResolver(BeanFactory)).instantiateUsingFactoryMethod(beanName, mbd, explicitArgs)

3、ConstructorResolver解析方法参数

ArgumentsHolder argsHolder = ConstructorResolver.createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames, candidate, autowiring, ((List)candidates).size() == 1);

// createArgumentArray方法的核心内容,可以看到按顺序逐个解析方法参数
try {
    convertedValue = ConstructorResolver.resolveAutowiredArgument(methodParam, beanName, autowiredBeanNames, (TypeConverter)converter, fallback);
    args.rawArguments[paramIndex] = convertedValue;
    args.arguments[paramIndex] = convertedValue;
    args.preparedArguments[paramIndex] = autowiredArgumentMarker;
    args.resolveNecessary = true;
} catch (BeansException var24) {
  	.....................
}

createArgumentArray方法参数说明:

  • beanName:方法名
  • mbd:@Bean扫描后生成的RootBeanDefinition
  • resolvedValues:null
  • bw:BeanWrapperImpl
  • paramTypes|paramNames:参数Class类型|参数名-集合
  • candidate:@Bean注解的Method对象实例
  • autowiring:true
  • fallback:true

如上展示了Bean方法参数大体解析流程,针对ObjectProvider类型,肯定会有其对应的适配解析流程,如下:

// DefaultListableBeanFactory 实例方法
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    descriptor.initParameterNameDiscovery(this.getParameterNameDiscoverer());
    if (Optional.class == descriptor.getDependencyType()) {
        return this.createOptionalDependency(descriptor, requestingBeanName);
    } else if (ObjectFactory.class != descriptor.getDependencyType() && ObjectProvider.class != descriptor.getDependencyType()) {
        if (javaxInjectProviderClass == descriptor.getDependencyType()) {
            return (new Jsr330Factory()).createDependencyProvider(descriptor, requestingBeanName);
        } else {
            Object result = this.getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);
            if (result == null) {
                result = this.doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
            }
            return result;
        }
    } else {
		// 执行此处
        return new DependencyObjectProvider(descriptor, requestingBeanName);
    }
}

上面一段if…else…语句,我们可以看到对Optional、ObjectFactory、ObjectProvider都做了判断,其中ObjectProvider类型参数被DependencyObjectProvider封装,至此我们知道了在@Bean方法实际执行的时候,其ObjectProvider参数的值其实就是封装了方法及参数元数据的DependencyObjectProvider对象,这个对象实现了我们开头所列举的getObject等一系列方法,本质和Proxy类似。

关于ObjectProvider的解析基本结束,最后创建@Bean方法的实例时,其实就是通过Method.invoke(args) 反射机制来完成实例化,args就是上述解析完的参数,方法体相当于类的init等初始化方法,至此一个完整的@Bean实例就创建好了。

总结:
1、 关于DependencyObjectProvider类的源码不作过多解读,各位有兴趣自行查看,核心就是根据方法解析的元数据,通过BeanFactory提供的实例化能力和ObjectProvider接口的暴露能力,供第三方类使用。
2、 DependencyObjectProvider针对泛型有特殊处理,其构造函数中通过构造NestedDependencyDescriptor实例,完成了MethodParameter内部变量nestingLevel值+1,进而在MethodParameter.getNestedGenericParameterType方法中,获取了ParameterizedType泛型化类,BeanFactory在其doResolveDependency方法中完成泛型类的实例创建和获取。
3、 通过以上说明,ObjectProvider其实就是完成BeanFactory对某一类实例【是否存在、单个获取、流式获取、条件获取】的封装,避免我们在类组合的过程中掺杂过多的条件判断,保证了代码的安全和稳定性。
4、 除了在方法参数中这样定义,类实例变量也同样支持,目测在Autowired的Processor处理中依然会生成DependencyObjectProvider对象【未实际验证】,HttpServletRequest全局注入也有类似的特性【Proxy实现】
5、 ObjectProvider的getObject(Object… args)需要注意,这个方法我们普通业务开发应该用不到,因为参数如果是容器内存在的实例,会自动注入,如果是普通String或int参数,容器实例化后也会报错,只能在此类上加@Lazy注解,然后再通过getObject(String, int)的形式获取其实例。即确保注入类既被扫描到生成BeanDefinition,又要延迟实例化。
6、 关于@Bean方式的实例定义和解析,后续会在ConfigurationClassParser解读中再详细说明

<think>我们正在分析SpringBoot2.6.15本中关于`LettuceConnectionFactory`配置的代码报错原因。用户提供的代码片段尝试通过反射调用`LettuceConnectionConfiguration`的方法来创建`LettuceConnectionFactory`,这可能导致兼容性问题。##报错原因分析1.**反射调用内部方法(高风险)**用户代码通过反射调用`LettuceConnectionConfiguration`的私有方法`redisConnectionFactory`。在SpringBoot2.6.x中,该的内部实现可能已发生变化,导致反射调用失败[^1]。2.**构造函数参数不匹配**`LettuceConnectionConfiguration`的构造函数在SpringBoot2.6.x中已更新为:```javapublicLettuceConnectionConfiguration(RedisPropertiesproperties,ObjectProvider<RedisSentinelConfiguration>sentinelConfiguration,ObjectProvider<RedisClusterConfiguration>clusterConfiguration,ObjectProvider<LettuceClientConfigurationBuilderCustomizer>builderCustomizers)//新增参数```而用户代码仍使用旧三参数构造函数:```javaConstructor<?>constructor=instanceClass.getDeclaredConstructor(RedisProperties.class,ObjectProvider.class,//RedisSentinelConfiguration的ObjectProviderObjectProvider.class//RedisClusterConfiguration的ObjectProvider);```缺少第四个`ObjectProvider<LettuceClientConfigurationBuilderCustomizer>`参数[^1]。3.**自动配置冲突**使用`@Primary`注解的Bean与SpringBoot自动配置产生的Bean冲突,导致容器启动失败(常见错误:`Parameter0ofmethodredisTemplateinRedisAutoConfigurationrequiredasinglebean,but2werefound`)[^2]。##解决方案###方案1:使用标准配置方式(推荐)直接通过配置创建`LettuceConnectionFactory`,避免反射:```java@ConfigurationpublicclassRedisConfig{@Bean@PrimarypublicLettuceConnectionFactoryredisLettuceConnectionFactory(RedisPropertiesredisProperties,ObjectProvider<LettuceClientConfigurationBuilderCustomizer>customizers){//从配置或自定义逻辑获取节点信息RedisStandaloneConfigurationconfig=newRedisStandaloneConfiguration();config.setHostName(redisProperties.getHost());config.setPort(redisProperties.getPort());config.setPassword(redisProperties.getPassword());LettuceClientConfigurationclientConfig=LettuceClientConfiguration.builder().commandTimeout(redisProperties.getTimeout()).apply(customizers.orderedStream())//应用所有定制器.build();returnnewLettuceConnectionFactory(config,clientConfig);}}```###方案2:修复反射调用(兼容旧)调整反射代码以匹配SpringBoot2.6.15的构造函数:```java//获取四参数构造函数Constructor<?>constructor=instanceClass.getDeclaredConstructor(RedisProperties.class,ObjectProvider.class,//RedisSentinelConfigurationObjectProvider.class,//RedisClusterConfigurationObjectProvider.class//LettuceClientConfigurationBuilderCustomizer);//传入四个参数ObjectlettuceConfiguration=constructor.newInstance(redisProperties,sentinelConfigurationProvider,clusterConfigurationProvider,builderCustomizers//新增第四个参数);```>**注意**:反射方案存在本脆弱性,SpringBoot本升级可能导致再次失效。##关键配置检查点1.**排除自动配置冲突**在启动禁用Redis自动配置:```java@SpringBootApplication(exclude={RedisAutoConfiguration.class,LettuceConnectionConfiguration.class})```2.**验证Bean优先级**确保自定义Bean被优先加载:```java@Bean@Primary@Order(Ordered.HIGHEST_PRECEDENCE)publicLettuceConnectionFactorycustomFactory(...){...}```##最佳实践建议1.**使用`application.yml`集中配置**:```yamlspring:redis:host:192.168.1.100port:6379password:securepasstimeout:3000mslettuce:pool:max-active:8max-idle:4```2.**集群模式配置示例**:```java@BeanpublicRedisClusterConfigurationclusterConfiguration(RedisPropertiesproperties){RedisClusterConfigurationconfig=newRedisClusterConfiguration(properties.getCluster().getNodes());config.setMaxRedirects(properties.getCluster().getMaxRedirects());returnconfig;}@BeanpublicLettuceConnectionFactoryconnectionFactory(RedisClusterConfigurationclusterConfig,LettuceClientConfigurationclientConfig){returnnewLettuceConnectionFactory(clusterConfig,clientConfig);}```>**本适配提示**:SpringBoot2.6.x要求JDK11+,请确认运行环境兼容性[^1]。§§相关问题§§1.SpringBoot2.6中如何正确配置Redis集群?2.如何解决SpringBoot多个Redis连接工厂的冲突问题?3.SpringBoot2.6的Redis自动配置有哪些变化?</think>### Spring Boot 2.6.15 LettuceConnectionFactory 源码报错分析与解决方案 #### 核心问题分析 1. **反射调用兼容性问题** 您的代码尝试通过反射调用`LettuceConnectionConfiguration`的私有构造函数和方法。在Spring Boot 2.6.x中,这些内部API已发生变更: ```java // 问题点:Spring Boot 2.6.15的构造函数参数已增加 public LettuceConnectionConfiguration(RedisProperties properties, ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider, ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider, ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers) { // 新增参数 // ... } ``` 您的代码使用旧三参数构造函数,但2.6.15需要**四个参数**(新增`builderCustomizers`)[^1]。 2. **自动配置冲突** 使用`@Primary`和`@Bean`手动创建`LettuceConnectionFactory`时,可能与Spring Boot自动配置冲突: ```log Parameter 0 of method redisTemplate in RedisAutoConfiguration required a single bean, but 2 were found ``` 3. **配置处理方式变更** Spring Boot 2.6.x对`RedisProperties`的处理逻辑有优化,直接修改其属性可能导致意外行为[^1]。 --- ### 分步解决方案 #### 方案1:使用标准配置API(推荐) ```java @Configuration public class RedisConfig { @Bean @Primary public LettuceConnectionFactory redisLettuceConnectionFactory( RedisProperties redisProperties, ObjectProvider<LettuceClientConfigurationBuilderCustomizer> customizers) { // 1. 创建基础配置 RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(); config.setHostName(redisProperties.getHost()); config.setPort(redisProperties.getPort()); config.setPassword(redisProperties.getPassword()); // 2. 构建Lettuce客户端配置 LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() .commandTimeout(redisProperties.getTimeout()) .apply(customizers.orderedStream()) // 应用定制器 .build(); // 3. 直接创建连接工厂 return new LettuceConnectionFactory(config, clientConfig); } } ``` #### 方案2:修复反射调用(兼容旧) ```java // 修改反射部分匹配新构造函数 Constructor<?> constructor = instanceClass.getDeclaredConstructor( RedisProperties.class, ObjectProvider.class, // RedisSentinelConfiguration ObjectProvider.class, // RedisClusterConfiguration ObjectProvider.class // LettuceClientConfigurationBuilderCustomizer (新增) ); // 传入四个参数 Object lettuceConfiguration = constructor.newInstance( redisProperties, sentinelConfigurationProvider, clusterConfigurationProvider, builderCustomizers // 新增第四个参数 ); ``` #### 方案3:排除自动配置 在启动禁用冲突的自动配置: ```java @SpringBootApplication(exclude = { RedisAutoConfiguration.class, LettuceConnectionConfiguration.class }) public class Application { ... } ``` --- ### 关键配置建议 1. **集群模式处理** ```java if (nodearray.length > 1) { RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration(); for (String node : nodearray) { String[] parts = node.split(":"); clusterConfig.clusterNode(parts[0], Integer.parseInt(parts[1])); } // 直接使用集群配置创建工厂 return new LettuceConnectionFactory(clusterConfig); } ``` 2. **属性加载最佳实践** ```yaml # application.yml spring: redis: host: redis-host port: 6379 timeout: 3000ms password: ${REDIS_PASSWORD} cluster: nodes: host1:6379,host2:6380 max-redirects: 3 ``` --- ### 本适配说明 | Spring Boot 本 | 关键变更 | |----------------|---------| | 2.5.x | 三参数构造函数 | | **2.6.0+** | **四参数构造函数**(新增`builderCustomizers`)[^1] | | 3.0.x | 迁移到`RedisConnectionFactory`统一接口 | > **重要提示**:Spring Boot 2.6.x默认禁止循环依赖,如果存在Redis组件与其他Bean的循环依赖,需在`application.properties`中添加: > ```properties > spring.main.allow-circular-references=true > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值