Spring自学笔记:依赖注入

概念

依赖注入(DI)是一个过程;
通过该过程,对象仅通过构造函数参数工厂方法的参数,或其被构造创建从工厂方法返回的对象实例上设置的属性来定义其依赖关系(即: 与它们一起工作的其他对象)。 然后,容器在创建bean时注入那些依赖项。

DI有两种方法:

① 基于构造函数的依赖注入

通过容器调用具有多个参数的构造函数来完成的,每个参数表示一个依赖项。调用带有特定参数的静态工厂方法来构造Bean几乎是等效的。

构造函数参数解析:根据参数的类型顺序进行匹配。
构造函数参数解析匹配通过使用参数的类型进行,在没有潜在的歧义的情况下,配置元数据提供参数的顺序就是构造函数参数的顺序;

当类型存在歧义的情况:可以通过type或者index来显示指定。
如:
type的方式

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

xml的方式

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

index 是从0开始的

消除歧义的另一种方法,使用构造函数参数名称

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

要使用上面参数名称的功能,需要启用debug flag,以便Spring可以从构造函数中查找参数名称。
但是如果你不想启用,那么就得使用注解的方式,(使用@ConstructorProperties注解)如下:

package examples;
public class ExampleBean {
    // Fields omitted
    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

② 基于Setter的依赖项注入

xml配置如下:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

当使用无参构造器或无参数的静态工厂方法bean后,容器通过在bean上调用setter方法来完成基于setterDI

public class SimpleMovieLister {
    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;
    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

即:ApplicationContext在已经通过构造函数方法注入了某些依赖项之后,它还支持基于setterDI

依赖注入的底层原理:
使用XML bean定义,带注解的组件(即以@Component@Controller等进行注释的类)或在基于@Configuration类中使用@Bean方法
然后将这些资源(配置元数据)在内部转换为BeanDefinition实例,并用于加载整个Spring IoC容器实例。

因为可以混合使用基于构造函数的DI基于setter的DI;因此,比较好的经验法则是:
强制性依赖项使用构造函数setter方式用于可选依赖,需要注意的是,在setter方法上使用@Required注释会使该属性变成必需的依赖(不建议这么用)。

Spring官方建议,使用构造函数注入,因为它使你可以将应用程序组件实现为不可变对象,并确保所需的依赖项不为null。此外,注入构造函数的组件始终以完全初始化的状态返回到客户端(调用)代码。但是如果构造函数参数特别多的话,那么这将是怀代码的味道,这表明该类承担了太多的职责,应该对其进行重构拆分。

依赖项解析过程

1、使用配置元数据来创建并初始化ApplicationContext。(配置元数据:描述所有Bean的东东)
2、对于每个bean,其依赖项都以属性构造函数参数static-factory方法的参数的形式表示,等待实际创建bean时,会将这些依赖项提供给bean
3、每个属性或构造函数参数要有实际的定义或者是容器中另一个bean的引用;说白了就是防止出现null的情况,要么自己new一个,要么赋值引用;
4、值的每个属性或构造函数参数都将从指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring可以将字符串类型的值转为所有内置类型,例如:int,long,String,boolean等。

在创建容器时,Spring容器会验证每个bean的配置。 但是,在实际创建Bean之前,不会设置Bean属性本身。 创建容器时,将创建具有单例作用域并设置为预先实例化(默认)的Bean。 范围在bean范围中定义。 否则,仅在请求时才创建Bean。 创建和分配bean的依赖关系及其依赖关系(依此类推)时,可能会潜在的创建一个了bean图(关系图、或者说是依赖图)。

请注意,这些依赖项之间不匹配的问题可能会在后期出现,即在第一次创建受影响的bean时。

Q:循环依赖的问题?
A: 如果主要使用构造函数注入,则可能会创建无法解决的循环依赖方案。
解决办法:
1、要么改代码
2、要么使用setter注入;(当然,这种注入方式我们一般是不推荐的)

构造注入为啥解决不了循环依赖?

构造注入解决不了:因为构造方法创建实例,每次都要new一个要构造的实例bean,而A创建时,依赖B,就去创建B,B又依赖了A,继续构造A,如此循环下去 A(B) B(A) A(B)->…

那么我们得思考下,为什么setter注入不会产生循环依赖的问题?
解决办法:使用三级缓存。

单例模式下,Spring使用三级缓存来解决循环依赖的问题。

在这里插入图片描述
我们假设现在有这样的场景AService依赖BServiceBService依赖AService

    1. `AService`首先实例化,实例化通过`ObjectFactory`半成品暴露在三级缓存中

    2. 填充属性`BService`,发现`BService`还未进行过加载,就会先去加载`BService`

    3. 再加载`BService`的过程中,实例化,也通过`ObjectFactory`半成品暴露在三级缓存

    4. 填充属性`AService`的时候,这时候能够从三级缓存中拿到半成品的`ObjectFactory`

参考地址:Spring的Bean加载和三级缓存解决循环依赖

循环依赖的解决

使用三个缓存来解决循环依赖的问题的自我的理解:

spring使用三级缓存来解决。
如果只是为了解决简单的循环依赖,那么其实只要二级缓存就行了,在bean被实例化之后,就会以早期依赖提前暴露出去。
举例:serviceA 实例化话后,接着做属性填充,发现依赖了serviceB,这个时候把serviceA半成品(未完成)放入earlySingletonObjects集合中,接着实例化serviceB,在对其进行属性填充时,发现依赖serviceA,然后去二级缓存(earlySingletonObjects)中找,发现找到后,完成初始化,接着又去完成serviceA的初始化工作。

复杂情况 :aop代理的情况

serviceA需要依赖serviceB,需要的是其代理,这个时候如果只使用二级缓存,那么那么就得在实例化serviceB之后,立马又生成代理对象,这不符合spring设计(应该在最后处理代理的情况),所以引入了三级缓存,在这个三级缓存是存放的是工厂对象,通过这些工厂对象,调用工厂方法getObject()方法来获取早期依赖bean,如果是代理,该方法就会生成并返回代理对象。

小结:

  1. bean在实例化之后,会写入三级缓存相应的工厂方法,如果没有aop,那么方法返回的就是原始bean,否则就返回代理对象(里面包含target即原始bean)。
  2. 之后再删除三级缓存,把早期bean移到二级缓存中;
  3. 在完成填充和初始化后,移动到一级缓存中。

Q: 默认情况下ApplicationContext实现会预先实例化单例bean呢?

A: 这是因为在实际创建Bean时,Spring设置属性并尽可能晚地解决依赖关系。
这意味着,如果创建对象或其依赖项之一有问题,则在正确加载了Spring的容器以后,当请求对象时会生成异常-例如,由于缺少或无效,bean引发异常属性。这可能会延迟某些配置问题的可见性。为了解决这个问题,在实际需要这些bean之前需要花一些前期时间和内存来创建它们,会在创建ApplicationContext时发现配置问题,而不是稍后。当然,我们可以覆盖此默认行为(预先实例化),以便单例bean延迟初始化,而不是预先实例化。

如果是不存循环依赖的情况,比如beanA依赖beanB,则springIOC容器会调用setter方法之前完全配置beanB。换句话说,一旦实例化该bean(如果它不是预先实例化的单例),则设置其依赖关系,并调用相关的生命周期方法(例如配置的init方法或InitializingBean回调方法)。

如果是循环依赖的话, 不会立马设置其依赖关系,而是会提前暴露一个半成品。

对其他Bean的引用(协作者)

<ref/><constructor-arg/> or <property/> 都有,用于引用容器中的另一个bean,如果引用的bean没有初始化,那么就会对其进行初始化再设置。(如果是单例的话,容器可能早已初始化了)。

<ref bean="someBean"/>

这个someBean可以是另一个beanid或者name,都行。

还有一个层次用法,子容器里引用父容器中的bean

父容器

<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>

这里可能会问,什么是父子容器?

父子容器

典型的父子容器就是springspringmvc同时使用的时候。分别ContextLoaderListener 创建的 容器是父容器,DispatcherServlet 创建的容器是子容器。

而父容器和子容器的区别。比如父容器有a.b.c三个bean对象,子容器有d.e.f三个bean对象,子容器就可以通过getBean方法调用父容器的a.b.c bean对象,而父容器不能通过getBean拿到子容器的d.e.f三个bean对象。但是这里有一个例外property-placeholder,容器中读取的配置文件就是私有的,互相不能访问。其中也要弄清楚的是父子容器并不是一种包含关系,而是平行关系,但是在子容器中有一个parent,指向父容器,也就是说子容器在通过getBean访问父容器中的bean对象时是通过parent访问。

这种做法的实际意思就是在一个JVM,只有一个树状结构的容器树。可以通过子容器访问父容器资源。就比如在实际开发中使用ssm框架,spring可以管理servicemapperspringmvc管理controllermybatis编写mappercontroller就需要调用serviceservice调用mapper,因为springmvc容器是spring的子容器,可以通过父容器找到servicemapper,但是在service中却是找不到controller的。保证一种资源的局部性。

参考地址: https://www.cnblogs.com/xuelin1221/p/10053059.html

内部bean

<property/> or <constructor-arg/>标签内使用<bean>包裹的元素就是内部bean

<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

内部bean定义不需要定义的ID名称。 如果指定,则容器不使用该值作为标识符。 容器在创建时也将忽略作用域标志,因为内部bean始终是匿名的,并且始终与外部bean一起创建。 不能独立访问内部bean,也不能将它们注入到协作bean中除非封装在里面。

内部bean通常只共享其包含bean的作用域。但是也有特殊情况,可以从自定义作用域中接收销毁回调,例如对于单例bean中包含的请求范围内的bean。 内部bean实例的创建与其包含的bean绑定在一起,但是销毁回调使它可以参与请求范围的生命周期,当然这种情况不常见。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山鬼谣me

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值