Spring框架-ObjectProvider更加宽泛的依赖注入

简介: 从上面的过程中我们可以看出,但Spring中某个Bean的依赖类型为ObjectProvider时,我们不需要提供一个ObjectProvider类型的Bean到容器中,只需要提供一个T类型的Bean到容器中,容器会自动将其包装成一个ObjectProvider,然后注入到依赖中

ObjectProvider

首先浅说一下Spring的依赖注入 , 在使用Spring的过程中 , 我们有多种形式将一个类注入到另一个类中 , 比如@Autowared,@Resource等等 , 其中@Autowared有多种使用场景 , 比如


* 注解在构造函数

@Service
public class OrderServiceImpl implements OrderService {
    private UserService userService;
    @Autowired
    public OrderServiceImpl(UserService userService) {
        this.userService = userService;
    }
}

* 注解在属性

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private UserService userService;
}

* 注解在方法上


@Service
public class OrderServiceImpl implements OrderService {
    private UserService userService;
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

但在spring 4.3之后,引入了一个新特性:当构造方法的参数为单个构造参数时,可以不使用@Autowired进行注解。于是上面的代码就变成这样

@Service
public class OrderServiceImpl implements OrderService {
    private UserService userService;
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

没有使用@Autowared , 因为Spring会把SetUserService()解析 , 解析为userService , 然后当作属性 , 同样是在Spring4.3版本中,不仅隐式的注入了单构造参数的属性。还引入了ObjectProvider接口 , 它继承自ObjectFactory


f35f9079466b6fb13e15d08c3863fbdd_watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbGluZ2VyaW5nIGZlYXI=,size_15,color_FFFFFF,t_70,g_se,x_16.png


专门为注入点设计, 可以让注入变得的更加宽松 , 那么在什么时候使用ObjectProvider呢?


如果注入实例为空 , 则使用ObjectProvider可以避免强依赖导致的依赖不存在问题

如果有多个实例 , ObjectProvider方法会根据实现的Ordered接口或@Ordered来顺序 的寻找Bean对象

Spring5.1之后提供了Stream的orderedStream()方法来得到有序的Stream , 如果使用ObjectProvider ,上面的代码变为


@Service
public class OrderServiceImpl implements OrderService {
    private UserService userService;
    public void setUserService(ObjectProvider<UserService> userService) {
        this.userService = userService.getIfAvailable();
    }
}

解决的问题:


容器中没有Bean时,抛出Parameter 0 of constructor in com.example.demo.FooServicerequired a bean of type 'com.example.demo.FooRepository' that could not be found.。


或者这样

@Service
public class OrderServiceImpl implements OrderService {
    private UserService userService;
    public void setUserService(ObjectProvider<UserService> userService) {
        this.userService = userService.orderedStream().findFirst().orElse(null);
    }
}

这样的好处很明显 , 如果容器中不存在userService或者有多个时 , 可以从容的处理 , 但是 , 如果userService必须不为空 , 那么这个异常就从启动阶段转移到业务运行阶段


解决的问题:当容器存在多个Bean,我们可以调用它的流式方法获取一个自己想要的依赖


在Spring4.3之前 , 当我们的构造函数想依赖于其他的Bean时 , 需要使用@Autowared(这里不考虑自动注入) ,而在4.3之后很显然不用这样做 ,我么只需要提供一个构造函数 , 并且构造函数所需要的Bean在Spring容器中


实际上官网中也指出,如果依赖关系是强制的,那么最好使用构造函数进行注入


官方推荐理由


单一职责: 当使用构造函数注入的时候,你会很容易发现参数是否过多,这个时候需要考虑你这个类的职责是否过大,考虑拆分的问题;而当使用@Autowired注入field的时候,不容易发现问题


依赖不可变: 只有使用构造函数注入才能注入final


依赖隐藏:使用依赖注入容器意味着类不再对依赖对象负责,获取依赖对象的职责就从类抽离出来,IOC容器会帮你自动装备。这意味着它应该使用更明确清晰的公用接口方法或者构造器,这种方式就能很清晰的知道类需要什么和到底是使用setter还是构造器


降低容器耦合度: 依赖注入框架的核心思想之一是托管类不应依赖于所使用的DI容器。换句话说,它应该只是一个普通的POJO,只要您将其传递给所有必需的依赖项,就可以独立地实例化。这样,您可以在单元测试中实例化它,而无需启动IOC容器并单独进行测试(使用一个可以进行集成测试的容器)。如果没有容器耦合,则可以将该类用作托管或非托管类,甚至可以切换到新的DI框架。


那么不需要精确的指定@Autowared , 这无疑简化了我们的开发 , 但是同时也带来了问题 , 就比如上面的例子 , 如果Spring容器不存在或者存在多个Bean是会报错的 ,那么为了解决这个问题 , ObejectProvider出场了 , 使用ObjectProvider则避免了强依赖导致的依赖对象不存在异常;如果有多个实例,ObjectProvider的方法可以根据Bean实现的Ordered接口或@Order注解指定的先后顺序获取一个Bean ,主要在org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency()


方法中使用


@Override
public Object resolveDependency(DependencyDescriptor descriptor, String requestingBeanName, 
                                Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    // descriptor代表当前需要注入的那个字段,或者方法的参数,也就是注入点
    // ParameterNameDiscovery用于解析方法参数名称
    descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
    // 1. Optional<T>
    if (Optional.class == descriptor.getDependencyType()) {
        return createOptionalDependency(descriptor, requestingBeanName);
        // 2. ObjectFactory<T>、ObjectProvider<T>
    } else if (ObjectFactory.class == descriptor.getDependencyType() ||
            ObjectProvider.class == descriptor.getDependencyType()) {
        return new DependencyObjectProvider(descriptor, requestingBeanName);
        // 3. javax.inject.Provider<T>
    } else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
        return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
    } else {
        // 4. @Lazy
        Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
                descriptor, requestingBeanName);
        // 5. 正常情况
        if (result == null) {
            result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
        }
        return result;
    }
}


他其实都会调用doResolveDependency()方法,我们着重关注上面的第二种情况,可以看到当注入点为ObjectFactory或者ObjectProvider时,会new一个DependencyObjectProvider返回出去,那么返回的这个DependencyObjectProvider是什么呢?看看继承关系

55369d33a84d1103d72b4ba244be8d64_watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbGluZ2VyaW5nIGZlYXI=,size_17,color_FFFFFF,t_70,g_se,x_16.png

然后看看内部方法getIfAvailable();


public Object getIfAvailable() throws BeansException {
    // 用于解决嵌套的情况,像这种:ObjectProvider<Optional<T>>
    if (this.optional) {
        return createOptionalDependency(this.descriptor, this.beanName);
    }
    else {
        DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) {
            @Override
            public boolean isRequired() {
                return false;
            }
        };
        // 最终还是会调用这个方法解决依赖
        return doResolveDependency(descriptorToUse, this.beanName, null, null);
    }
}

从上面的过程中我们可以看出,但Spring中某个Bean的依赖类型为ObjectProvider时,我们不需要提供一个ObjectProvider类型的Bean到容器中,只需要提供一个T类型的Bean到容器中,容器会自动将其包装成一个ObjectProvider,然后注入到依赖中

相关文章
|
3月前
|
安全 Java Ruby
我尝试了所有后端框架 — — 这就是为什么只有 Spring Boot 幸存下来
作者回顾后端开发历程,指出多数框架在生产环境中难堪重负。相比之下,Spring Boot凭借内置安全、稳定扩展、完善生态和企业级支持,成为构建高可用系统的首选,真正经受住了时间与规模的考验。
288 2
|
2月前
|
安全 前端开发 Java
《深入理解Spring》:现代Java开发的核心框架
Spring自2003年诞生以来,已成为Java企业级开发的基石,凭借IoC、AOP、声明式编程等核心特性,极大简化了开发复杂度。本系列将深入解析Spring框架核心原理及Spring Boot、Cloud、Security等生态组件,助力开发者构建高效、可扩展的应用体系。(238字)
|
4月前
|
XML JSON Java
Spring框架中常见注解的使用规则与最佳实践
本文介绍了Spring框架中常见注解的使用规则与最佳实践,重点对比了URL参数与表单参数的区别,并详细说明了@RequestParam、@PathVariable、@RequestBody等注解的应用场景。同时通过表格和案例分析,帮助开发者正确选择参数绑定方式,避免常见误区,提升代码的可读性与安全性。
|
2月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
143 8
|
3月前
|
监控 Kubernetes Cloud Native
Spring Batch 批处理框架技术详解与实践指南
本文档全面介绍 Spring Batch 批处理框架的核心架构、关键组件和实际应用场景。作为 Spring 生态系统中专门处理大规模数据批处理的框架,Spring Batch 为企业级批处理作业提供了可靠的解决方案。本文将深入探讨其作业流程、组件模型、错误处理机制、性能优化策略以及与现代云原生环境的集成方式,帮助开发者构建高效、稳定的批处理系统。
434 1
|
5月前
|
安全 Java 微服务
Java 最新技术和框架实操:涵盖 JDK 21 新特性与 Spring Security 6.x 安全框架搭建
本文系统整理了Java最新技术与主流框架实操内容,涵盖Java 17+新特性(如模式匹配、文本块、记录类)、Spring Boot 3微服务开发、响应式编程(WebFlux)、容器化部署(Docker+K8s)、测试与CI/CD实践,附完整代码示例和学习资源推荐,助你构建现代Java全栈开发能力。
616 0
|
4月前
|
Cloud Native Java API
Java Spring框架技术栈选和最新版本及发展史详解(截至2025年8月)-优雅草卓伊凡
Java Spring框架技术栈选和最新版本及发展史详解(截至2025年8月)-优雅草卓伊凡
843 0
|
5月前
|
缓存 安全 Java
第五章 Spring框架
Spring IOC(控制反转)通过工厂模式管理对象的创建与生命周期,DI(依赖注入)则让容器自动注入所需对象,降低耦合。常见注解如@Component、@Service用于声明Bean,@Autowired用于注入。Bean默认单例,作用域可通过@Scope配置,如prototype、request等。Spring通过三级缓存解决循环依赖问题,但构造函数循环依赖需用@Lazy延迟加载。AOP通过动态代理实现,用于日志、事务等公共逻辑。事务通过@Transactional实现,需注意异常处理及传播行为。
91 0
|
5月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
189 0
|
5月前
|
存储 缓存 NoSQL
Spring Cache缓存框架
Spring Cache是Spring体系下的标准化缓存框架,支持多种缓存(如Redis、EhCache、Caffeine),可独立或组合使用。其优势包括平滑迁移、注解与编程两种使用方式,以及高度解耦和灵活管理。通过动态代理实现缓存操作,适用于不同业务场景。
464 0