当前位置: 代码迷 >> 综合 >> 【Java.Spring.Core】【IoC】Bean依赖注入- Dependencies
  详细解决方案

【Java.Spring.Core】【IoC】Bean依赖注入- Dependencies

热度:42   发布时间:2023-12-14 18:58:20.0

依赖注入 - Dependency Injection

当容器创建bean的时候会注入这些依赖。

当为对象提供依赖对象时,代码会比较整洁同时会解耦。对象不需要查找它们的依赖,也不需要知道这些依赖所对应的类或位置。

(1)基于构造函数的依赖注入

基于构造函数的依赖通过容器调用构造函数来实现,构造函数可能包含若干个参数,每一个代表一个依赖。


例如:

package x.y;public class Foo {public Foo(Bar bar, Baz baz) {// ...}}

当使用参数类型匹配时,如果不存在歧义,默认的,bean定义中构造函数参数的顺序就是实际传入参数时的顺序。

假设Bar 和 Baz是不相关的类型,则不存在类型歧义。因此,下列配置是合法的,不需要在<constructor-arg>元素中显式的指定构造函数参数索引或者类型,

<beans><bean id="foo" class="x.y.Foo"><constructor-arg ref="bar"/><constructor-arg ref="baz"/></bean><bean id="bar" class="x.y.Bar"/><bean id="baz" class="x.y.Baz"/>
</beans>


当使用简单类型时,例如<value>true</value>,Spring不能确定该值的类型,此时需要指定其他的属性,例如:

package examples;public class ExampleBean {// Number of years to calculate the Ultimate Answerprivate int years;// The Answer to Life, the Universe, and Everythingprivate String ultimateAnswer;public ExampleBean(int years, String ultimateAnswer) {this.years = years;this.ultimateAnswer = ultimateAnswer;}}


在这个例子中,可以使用 type 属性来指定构造函数参数的类型,

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


或者使用 Index 属性显式地指定构造函数参数的索引,

<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编译选项,否则Spring无法找到该构造函数的参数名。如果没有开启 debug 编译选项,可以使用 @ConstructProperties 注解显式的指定构造函数的参数名,例如:

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


(2)基于Setter方法的依赖注入

该方法在使用无参数的构造函数或者无参数的厂方法实例化bean之后,通过容器调用该bean的 setter 方法来完成依赖注入

例如:

public class SimpleMovieLister {// the SimpleMovieLister has a dependency on the MovieFinderprivate MovieFinder movieFinder;// a setter method so that the Spring container can inject a MovieFinderpublic void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}// business logic that actually uses the injected MovieFinder is omitted...}


ApplicationContext处理支持基于构造函数的注入和基于 setter 方法的注入外,也支持在使用构造函数注入部分依赖后,再使用基于 setter 方法来注入其余的依赖


依赖解析过程

容器如下完成bean的依赖解析:

  • ApplicationContext被创建,并使用配置元数据来初始化
  • 对于每一个bean, 它的依赖可能是属性,构造函数的参数,静态厂方法的参数等形式;当bean实际创建的时候,这些依赖被提供给bean
  • 每一个属性或者构造函数的参数都是一个实际的值,或者是存在于容器中的另一个bean
  • 每一个属性或者构造函数的参数,如果其是值类型,那么该值被转换为属性或参数所需的实际的类型

当容器被创建的时候,Spring容器会验证每一个 bean 的配置;但是,对于bean的属性,直到bean被创建的时候才会被设置。

单例范围的bean 和提前初始化的bean在容器被创建的时候就会被创建。否则,直到该bean被请求的时候才会被创建

当创建 bean 的时候,其依赖即依赖的依赖都会被创建并赋值。


依赖注入示例

  • 基于 setter 方法的依赖注入:

<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"/>

public class ExampleBean {private AnotherBean beanOne;private YetAnotherBean beanTwo;private int i;public void setBeanOne(AnotherBean beanOne) {this.beanOne = beanOne;}public void setBeanTwo(YetAnotherBean beanTwo) {this.beanTwo = beanTwo;}public void setIntegerProperty(int i) {this.i = i;}}

  • 基于构造函数的依赖注入:

<bean id="exampleBean" class="examples.ExampleBean"><!-- constructor injection using the nested ref element --><constructor-arg><ref bean="anotherExampleBean"/></constructor-arg><!-- constructor injection using the neater ref attribute --><constructor-arg ref="yetAnotherBean"/><constructor-arg type="int" value="1"/>
</bean><bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

public class ExampleBean {private AnotherBean beanOne;private YetAnotherBean beanTwo;private int i;public ExampleBean(AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {this.beanOne = anotherBean;this.beanTwo = yetAnotherBean;this.i = i;}}

  • 使用厂方法的依赖注入:

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance"><constructor-arg ref="anotherExampleBean"/><constructor-arg ref="yetAnotherBean"/><constructor-arg value="1"/>
</bean><bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

public class ExampleBean {// a private constructorprivate ExampleBean(...) {...}// a static factory method; the arguments to this method can be// considered the dependencies of the bean that is returned,// regardless of how those arguments are actually used.public static ExampleBean createInstance (AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {ExampleBean eb = new ExampleBean (...);// some other operations...return eb;}}

Bean依赖的配置细节

Spring可以定义bean的属性和构造函数的参数为其他beans的引用; Spring的基于XML格式的配置元数据支持在 <property/> 和 <construct-arg/>元素中添加子元素:

字面量(primitive, Strings等)

<property/>元素包含一个属性 value,用来以可读的字符串形式指定bean的属性或者构造函数的参数。

Spring的conversion service用来将这些值由String类型转换为属性或参数的实际类型。

例如:

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"><!-- results in a setDriverClassName(String) call --><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mydb"/><property name="username" value="root"/><property name="password" value="masterkaoli"/>
</bean>

idref 元素

dref元素用来将容器内其它bean的id传给<constructor-arg/>或<property/>元素,同时提供错误验证功能.


引用其他的beans

ref 元素位于 <construct=arg/> 或者 <property/> 元素定义的内部。用来设置一个bean的指定属性为引用容器中的其他bean。

通过使用<ref/>元素的 bean 属性指定目标bean是最常见的情形,该属性可以用来创建一个位于同一个或父容器(不同XML文件)中的bean的引用。bean属性的值可以是引用bean的id属性,或者是引用bean的name的值。

<ref bean="someBean"/>

通过parent属性来指定引用的bean,该引用bean位于当前容器的父容器。引用bean必须位于当前容器的父容器,例如:

<!-- in the parent context -->
<bean id="accountService" class="com.foo.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>

内部beans

位于<property/>或者<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>

内部不需要id 或者 name, 容器会忽略这些值。也会忽略 scope 属性。 内部bean通常是匿名bean,通常被外部bean所创建。

集合Collections

使用<list>, <set>, <map>, <props>元素,设置Java集合类型的属性或构造函数参数(List, Set, Map, Properties):

<bean id="moreComplexObject" class="example.ComplexObject"><!-- results in a setAdminEmails(java.util.Properties) call --><property name="adminEmails"><props><prop key="administrator">administrator@example.org</prop><prop key="support">support@example.org</prop><prop key="development">development@example.org</prop></props></property><!-- results in a setSomeList(java.util.List) call --><property name="someList"><list><value>a list element followed by a reference</value><ref bean="myDataSource" /></list></property><!-- results in a setSomeMap(java.util.Map) call --><property name="someMap"><map><entry key="an entry" value="just some string"/><entry key ="a ref" value-ref="myDataSource"/></map></property><!-- results in a setSomeSet(java.util.Set) call --><property name="someSet"><set><value>just some string</value><ref bean="myDataSource" /></set></property>
</bean>


强类型集合

public class Foo {private Map<String, Float> accounts;public void setAccounts(Map<String, Float> accounts) {this.accounts = accounts;}
}

<beans><bean id="foo" class="x.y.Foo"><property name="accounts"><map><entry key="one" value="9.99"/><entry key="two" value="2.75"/><entry key="six" value="3.99"/></map></property></bean>
</beans>

当bean foo的属性 accounts准备注入的时候,字符串值 9.99, 2.75, 3.99 被转换成实际的Float类型。

Null 和空字符串值

Spring将为空值的属性看作是空的字符串,例如:

<bean class="ExampleBean"><property name="email" value=""/>
</bean>

等价与下列的代码:

exampleBean.setEmail("")

<null/>元素表示 null, 例如:

<bean class="ExampleBean"><property name="email"><null/></property>
</bean>

其等价于:

exampleBean.setEmail(null)

depends-on

如果一个bean是另一个bean的依赖,意味着这个bean是另一个bean的属性。通常,使用<ref/>元素进行设置。但,有时bean之间的依赖不是非常直接,例如,一个类内部的静态初始化器,depends-on属性可以显式的另这些bean在该bean初始化之前被初始化。

例如:

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

或:

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao"><property name="manager" ref="manager" />
</bean><bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

惰性初始化beans

默认,ApplicationContext的实现类会在初始化的过程中创建并配置全部的单件singleton beans。

我们可以使用惰性初始化来禁止单件bean的提前初始化过程。

惰性初始化通知容器在请求这个bean的时候再进行初始化。

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>

但是,当一个惰性初始化bean是一个非惰性初始化bean的依赖时, ApplicationContext仍然会在启动的时候创建这个惰性初始化bean,应为需要满足单件bean的依赖注入。


Autowiring collaborators自动装配

Spring容器可以在bean之间进行自动装配。Spring可以通过检测ApplicationContext的内容来自动解析相互合作的beans。

自动装配的优点:

  • 减少属性或构造函数参数的显式指定
  • 当对象的配置发生改变时,可以自动识别

当使用基于XML的配置元数据时,通过指定<bean/>元素的 autowire属性可以指定bean的自动装配模式。

自动装配功能有5中模式,可以为每个bean的定义指定单独的自动装配属性:

  • no - 不自动装配,默认为 no
  • byName - 根据属性名称自动装配;Spring查找相同属性名称的bean;例如,一个bean定义设置自动装配模式为by name,其包含一个 master 属性,Spring会查找一个名为 master 的bean定义,并使用它作为属性
  • byType - 使用容器中存在的相同类型的bean作为属性进行自动装配。如果存在多个,将抛出异常;如果没有匹配的bean,该属性将不会被设置
  • constructor - 

自动装配的缺点:

  • 显式的依赖常常会覆盖自动装配
  • 自动装配的准确性差

方法注入