spring依赖注入的三种方式,Springboot常用注解
大家好,今天来为大家解答spring依赖注入的三种方式这个问题的一些问题点,包括Springboot常用注解也一样很多人还不知道,因此呢,今天就来为大家分析分析,现在让我们一起来看看吧!如果解决了您的问题,还望您关注下本站哦,谢谢~
spring 中依赖注入有哪几种注解
Spring对于Bean的依赖注入,支持多种注解方式:
@Resource
javax.annotation
JSR250(Common Annotations for Java)
@Inject
javax.inject
JSR330(Dependency Injection for Java)
@Autowired
org.springframework.bean.factory
Spring
直观上看起来,@Autowired是Spring提供的注解,其他几个都是JDK本身内建的注解,Spring对这些注解也进行了支持。但是使用起来这三者到底有什么区别呢?笔者经过方法的测试,发现一些有意思的特性。总结如下:
1.@Autowired有个required属性,可以配置为false,这种情况下如果没有找到对应的bean是不会抛异常的。@Inject和@Resource没有提供对应的配置,所以必须找到否则会抛异常。
2.@Autowired和@Inject基本是一样的,因为两者都是使用AutowiredAnnotationBeanPostProcessor来处理依赖注入。但是@Resource是个例外,它使用的是CommonAnnotationBeanPostProcessor来处理依赖注入。当然,两者都是BeanPostProcessor。
深层理解Spring 控制反转和依赖注入,这一篇文够了
控制反转(IOC)旨在提供一种更简单的机制,来设置组件的依赖项,并在整个生命周期管理这些依赖项。通常,控制反转可以分成两种子类型:依赖注入(DI)和依赖查找(DL),这些子类型各自又可以被进一步分解为 IOC服务的具体实现
依赖拉取(Dependency Pull),即根据需要,从注册表中提取依赖项,以下代码显示了基于 Spring的依赖拉取
上下文依赖查找(contextualized dependency lookup,CDL),同样属于依赖查找的子类型,和依赖拉取有点类似,但在 CDL中,查找是针对管理资源的容器执行的,这个容器通常由应用程序服务器或框架(Tomcat、JBoss、Spring)提供,比如以下代码显示了一个提供依赖查找服务的容器接口
组件需要实现该接口,当容器准备好将依赖项传递给组件时,会依次调用每个组件的 performLookup()方法,然后组件就可以使用 Container接口查找所需的依赖项
当在组件的构造函数中提供依赖项时,就会发生构造函数依赖注入
Ioc容器通过 JavaBean样式的 setter方法注入组件的依赖项
在 Spring中,还支持另一种被称为字段注入(field injection)的注入类型,在后面学习使用@Autowire注解进行自动装配时将介绍该注入类型
Spring的依赖注入容器的核心是 BeanFactory,它负责管理组件,包括依赖项以及它们的生命周期。如果我们想获得一个组件(Bean),就必须创建一个实现了 BeanFactory接口的实例,并对其进行配置
虽然 BeanFactory可以通过编程方式配置,但更常见的做法是使用某种配置文件在外部对其进行配置。Bean配置可以由实现 BeanDefinition接口的类的实例来表示,对于任何实现了 BeanDefinitionReader接口的 BeanFactory实现类来说,都可以使用 PropertiesBeanDefinitionReader或 XmlBeanDefinitionReader从配置文件读取 BeanDefinition数据
定义一组接口:
接下来我们来看一看,Spring的 BeanFactory如何被初始化并用于获取 Bean实例
ApplicationContext接口是 BeanFactory的一个扩展,除了 DI服务外,还提供其他如事务和 AOP等服务。在开发基于 Spring的应用程序时,建议通过 ApplicationContext接口与 Spring交互
标记告诉 Spring扫描代码,从而找到@Component等注解注入的 bean,以及支持在指定包(及其所有子包)下使用@Autowire等注解的 bean
配置类使用@Configuration注解,并包含用@Bean注解的方法,这些方法由 IOC容器直接调用来实例化 bean,bean名称与用于创建它的方法的名称相同
如果想从该类中读取配置信息,需要一个不同的 ApplicationContext实现
使用 XML配置来配置 setter注入,需要在标记下指定标记,为其注入一个依赖项
如果使用注解,只需要向 setter方法添加一个@Autowired注解
使用 XML方式注入
图文并茂,揭秘 Spring 的 Bean 的加载过程
目录
Spring作为 Ioc框架,实现了依赖注入,由一个中心化的 Bean工厂来负责各个 Bean的实例化和依赖管理。各个 Bean可以不需要关心各自的复杂的创建过程,达到了很好的解耦效果。
我们对 Spring的工作流进行一个粗略的概括,主要为两大环节:
我们假设所有的配置和扩展类都已经装载到了 ApplicationContext中,然后具体的分析一下 Bean的加载流程。
思考一个问题,抛开 Spring框架的实现,假设我们手头上已经有一套完整的 Bean Definition Map,然后指定一个 beanName要进行实例化,需要关心什么?即使我们没有 Spring框架,也需要了解这两方面的知识:
Spring进行了抽象和封装,使得作用域和依赖关系的配置对开发者透明,我们只需要知道当初在配置里已经明确指定了它的生命周期和依赖了谁,至于是怎么实现的,依赖如何注入,托付给了 Spring工厂来管理。
Spring只暴露了很简单的接口给调用者,比如 getBean:
那我们就从 getBean方法作为入口,去理解 Spring加载的流程是怎样的,以及内部对创建信息、作用域、依赖关系等等的处理细节。
上面是跟踪了 getBean的调用链创建的流程图,为了能够很好地理解 Bean加载流程,省略一些异常、日志和分支处理和一些特殊条件的判断。
从上面的流程图中,可以看到一个 Bean加载会经历这么几个阶段(用绿色标记):
整个流程最为复杂的是对循环依赖的解决方案,后续会进行重点分析。
而在我们解析完配置后创建的 Map,使用的是 beanName作为 key。见 DefaultListableBeanFactory:
BeanFactory.getBean中传入的 name,有可能是这几种情况:
为了能够获取到正确的 BeanDefinition,需要先对 name做一个转换,得到 beanName。
见 AbstractBeanFactory.doGetBean:
如果是 alias name,在解析阶段,alias name和 bean name的映射关系被注册到 SimpleAliasRegistry中。从该注册器中取到 beanName。见 SimpleAliasRegistry.canonicalName:
如果是 factorybean name,表示这是个工厂 bean,有携带前缀修饰符&的,直接把前缀去掉。见 BeanFactoryUtils.transformedBeanName:
我们从配置文件读取到的 BeanDefinition是 GenericBeanDefinition。它的记录了一些当前类声明的属性或构造参数,但是对于父类只用了一个 parentName来记录。
接下来会发现一个问题,在后续实例化 Bean的时候,使用的 BeanDefinition是 RootBeanDefinition类型而非 GenericBeanDefinition。这是为什么?
答案很明显,GenericBeanDefinition在有继承关系的情况下,定义的信息不足:
为了能够正确初始化对象,需要完整的信息才行。需要递归合并父类的定义:
见 AbstractBeanFactory.doGetBean:
在判断 parentName存在的情况下,说明存在父类定义,启动合并。如果父类还有父类怎么办?递归调用,继续合并。
见 AbstractBeanFactory.getMergedBeanDefinition方法:
每次合并完父类定义后,都会调用 RootBeanDefinition.overrideFrom对父类的定义进行覆盖,获取到当前类能够正确实例化的全量信息。
什么是循环依赖?
举个例子,这里有三个类 A、B、C,然后 A关联 B,B关联 C,C又关联 A,这就形成了一个循环依赖。如果是方法调用是不算循环依赖的,循环依赖必须要持有引用。
循环依赖根据注入的时机分成两种类型:
如果是构造器循环依赖,本质上是无法解决的。比如我们准调用 A的构造器,发现依赖 B,于是去调用 B的构造器进行实例化,发现又依赖 C,于是调用 C的构造器去初始化,结果依赖 A,整个形成一个死结,导致 A无法创建。
如果是设值循环依赖,Spring框架只支持单例下的设值循环依赖。Spring通过对还在创建过程中的单例,缓存并提前暴露该单例,使得其他实例可以引用该依赖。
Spring不支持原型模式的任何循环依赖。检测到循环依赖会直接抛出 BeanCurrentlyInCreationException异常。
使用了一个 ThreadLocal变量 prototypesCurrentlyInCreation来记录当前线程正在创建中的 Bean对象,见 AbtractBeanFactory#prototypesCurrentlyInCreation:
在 Bean创建前进行记录,在 Bean创建后删除记录。见 AbstractBeanFactory.doGetBean:
见 AbtractBeanFactory.beforePrototypeCreation的记录操作:
见 AbtractBeanFactory.beforePrototypeCreation的删除操作:
为了节省内存空间,在单个元素时 prototypesCurrentlyInCreation只记录 String对象,在多个依赖元素后改用 Set集合。这里是 Spring使用的一个节约内存的小技巧。
了解了记录的写入和删除过程好了,再来看看读取以及判断循环的方式。这里要分两种情况讨论。
这两个地方的实现略有不同。
如果是构造函数依赖的,比如 A的构造函数依赖了 B,会有这样的情况。实例化 A的阶段中,匹配到要使用的构造函数,发现构造函数有参数 B,会使用 BeanDefinitionValueResolver来检索 B的实例。见 BeanDefinitionValueResolver.resolveReference:
我们发现这里继续调用 beanFactory.getBean去加载 B。
如果是设值循环依赖的的,比如我们这里不提供构造函数,并且使用了@Autowire的方式注解依赖(还有其他方式不举例了):
加载过程中,找到无参数构造函数,不需要检索构造参数的引用,实例化成功。接着执行下去,进入到属性填充阶段 AbtractBeanFactory.populateBean,在这里会进行 B的依赖注入。
为了能够获取到 B的实例化后的引用,最终会通过检索类 DependencyDescriptor中去把依赖读取出来,见 DependencyDescriptor.resolveCandidate:
发现 beanFactory.getBean方法又被调用到了。
在这里,两种循环依赖达成了同一。无论是构造函数的循环依赖还是设置循环依赖,在需要注入依赖的对象时,会继续调用 beanFactory.getBean去加载对象,形成一个递归操作。
而每次调用 beanFactory.getBean进行实例化前后,都使用了 prototypesCurrentlyInCreation这个变量做记录。按照这里的思路走,整体效果等同于建立依赖对象的构造链。
prototypesCurrentlyInCreation中的值的变化如下:
调用判定的地方在 AbstractBeanFactory.doGetBean中,所有对象的实例化均会从这里启动。
判定的实现方法为 AbstractBeanFactory.isPrototypeCurrentlyInCreation:
所以在原型模式下,构造函数循环依赖和设值循环依赖,本质上使用同一种方式检测出来。Spring无法解决,直接抛出 BeanCurrentlyInCreationException异常。
Spring也不支持单例模式的构造循环依赖。检测到构造循环依赖也会抛出 BeanCurrentlyInCreationException异常。
和原型模式相似,单例模式也用了一个数据结构来记录正在创建中的 beanName。见 DefaultSingletonBeanRegistry:
会在创建前进行记录,创建化后删除记录。
见 DefaultSingletonBeanRegistry.getSingleton
记录和判定的方式见 DefaultSingletonBeanRegistry.beforeSingletonCreation:
这里会尝试往 singletonsCurrentlyInCreation记录当前实例化的 bean。我们知道 singletonsCurrentlyInCreation的数据结构是 Set,是不允许重复元素的,所以一旦前面记录了,这里的 add操作将会返回失败。
比如加载 A的单例,和原型模式类似,单例模式也会调用匹配到要使用的构造函数,发现构造函数有参数 B,然后使用 BeanDefinitionValueResolver来检索 B的实例,根据上面的分析,继续调用 beanFactory.getBean方法。
所以拿 A,B,C的例子来举例 singletonsCurrentlyInCreation的变化,这里可以看到和原型模式的循环依赖判断方式的算法是一样:
单例模式下,构造函数的循环依赖无法解决,但设值循环依赖是可以解决的。
这里有一个重要的设计:提前暴露创建中的单例。
我们理解一下为什么要这么做。
还是拿上面的 A、B、C的的设值依赖做分析,
=> 1. A创建-> A构造完成,开始注入属性,发现依赖 B,启动 B的实例化
=> 2. B创建-> B构造完成,开始注入属性,发现依赖 C,启动 C的实例化
=> 3. C创建-> C构造完成,开始注入属性,发现依赖 A
重点来了,在我们的阶段 1中, A已经构造完成,Bean对象在堆中也分配好内存了,即使后续往 A中填充属性(比如填充依赖的 B对象),也不会修改到 A的引用地址。
所以,这个时候是否可以提前拿 A实例的引用来先注入到 C,去完成 C的实例化,于是流程变成这样。
=> 3. C创建-> C构造完成,开始注入依赖,发现依赖 A,发现 A已经构造完成,直接引用,完成 C的实例化。
=> 4. C完成实例化后,B注入 C也完成实例化,A注入 B也完成实例化。
这就是 Spring解决单例模式设值循环依赖应用的技巧。流程图为:
为了能够实现单例的提前暴露。Spring使用了三级缓存,见 DefaultSingletonBeanRegistry:
这三个缓存的区别如下:
从 getBean("a")开始,添加的 SingletonFactory具体实现如下:
可以看到如果使用该 SingletonFactory获取实例,使用的是 getEarlyBeanReference方法,返回一个未初始化的引用。
读取缓存的地方见 DefaultSingletonBeanRegistry:
先尝试从 singletonObjects和 singletonFactory读取,没有数据,然后尝试 singletonFactories读取 singletonFactory,执行 getEarlyBeanReference获取到引用后,存储到 earlySingletonObjects中。
这个 earlySingletonObjects的好处是,如果此时又有其他地方尝试获取未初始化的单例,可以从 earlySingletonObjects直接取出而不需要再调用 getEarlyBeanReference。
从流程图上看,实际上注入 C的 A实例,还在填充属性阶段,并没有完全地初始化。等递归回溯回去,A顺利拿到依赖 B,才会真实地完成 A的加载。
获取到完整的 RootBeanDefintion后,就可以拿这份定义信息来实例具体的 Bean。
具体实例创建见 AbstractAutowireCapableBeanFactory.createBeanInstance,返回 Bean的包装类 BeanWrapper,一共有三种策略:
使用工厂方法创建,会先使用 getBean获取工厂类,然后通过参数找到匹配的工厂方法,调用实例化方法实现实例化,具体见 ConstructorResolver.instantiateUsingFactoryMethod:
使用有参构造函数创建,整个过程比较复杂,涉及到参数和构造器的匹配。为了找到匹配的构造器,Spring花了大量的工作,见 ConstructorResolver.autowireConstructor:
使用无参构造函数创建是最简单的方式,见 AbstractAutowireCapableBeanFactory.instantiateBean:
我们发现这三个实例化方式,最后都会走 getInstantiationStrategy().instantiate(...),见实现类 SimpleInstantiationStrategy.instantiate:
虽然拿到了构造函数,并没有立即实例化。因为用户使用了 replace和 lookup的配置方法,用到了动态代理加入对应的逻辑。如果没有的话,直接使用反射来创建实例。
创建实例后,就可以开始注入属性和初始化等操作。
但这里的 Bean还不是最终的 Bean。返回给调用方使用时,如果是 FactoryBean的话需要使用 getObject方法来创建实例。见 AbstractBeanFactory.getObjectFromBeanInstance,会执行到 doGetObjectFromFactoryBean:
实例创建完后开始进行属性的注入,如果涉及到外部依赖的实例,会自动检索并关联到该当前实例。
Ioc思想体现出来了。正是有了这一步操作,Spring降低了各个类之间的耦合。
属性填充的入口方法在 AbstractAutowireCapableBeanFactory.populateBean。
可以看到主要的处理环节有:
如果我们的 Bean需要容器的一些资源该怎么办?比如需要获取到 BeanFactory、ApplicationContext等等。
Spring提供了 Aware系列接口来解决这个问题。比如有这样的 Aware:
Spring在初始化阶段,如果判断 Bean实现了这几个接口之一,就会往 Bean中注入它关心的资源。
见 AbstractAutowireCapableBeanFactory.invokeAwareMethos:
在 Bean的初始化前或者初始化后,我们如果需要进行一些增强操作怎么办?
这些增强操作比如打日志、做校验、属性修改、耗时检测等等。Spring框架提供了 BeanPostProcessor来达成这个目标。比如我们使用注解@Autowire来声明依赖,就是使用 AutowiredAnnotationBeanPostProcessor来实现依赖的查询和注入的。接口定义如下:
实现该接口的 Bean都会被 Spring注册到 beanPostProcessors中,见 AbstractBeanFactory:
只要 Bean实现了 BeanPostProcessor接口,加载的时候会被 Spring自动识别这些 Bean,自动注册,非常方便。
然后在 Bean实例化前后,Spring会去调用我们已经注册的 beanPostProcessors把处理器都执行一遍。
这里使用了责任链模式,Bean会在处理器链中进行传递和处理。当我们调用 BeanFactory.getBean的后,执行到 Bean的初始化方法 AbstractAutowireCapableBeanFactory.initializeBean会启动这些处理器。
自定义初始化有两种方式可以选择:
见 AbstractAutowireCapableBeanFactory.invokeInitMethods:
Bean已经加载完毕,属性也填充好了,初始化也完成了。
在返回给调用者之前,还留有一个机会对 Bean实例进行类型的转换。见 AbstractBeanFactory.doGetBean:
抛开一些细节处理和扩展功能,一个 Bean的创建过程无非是:
获取完整定义->实例化->依赖注入->初始化->类型转换。
作为一个完善的框架,Spring需要考虑到各种可能性,还需要考虑到接入的扩展性。
所以有了复杂的循环依赖的解决,复杂的有参数构造器的匹配过程,有了 BeanPostProcessor来对实例化或初始化的 Bean进行扩展修改。
先有个整体设计的思维,再逐步击破针对这些特殊场景的设计,整个 Bean加载流程迎刃而解。
好了,文章到这里就结束啦,如果本次分享的spring依赖注入的三种方式和Springboot常用注解问题对您有所帮助,还望关注下本站哦!