依赖
-
依赖注入(Dependency Injection)
依赖注入主要有两种形式:
基于构造器的依赖注入:
在改方式下,容器调用构造器,并向构造器传入参数,每个参数都是一个依赖。向静态工厂方法传递参数与向构造器传参一样。
下面这个类只能通过构造器注入进行依赖注入
public class SimpleMovieLister { // the SimpleMovieLister has a dependency on a MovieFinder private MovieFinder movieFinder; // a constructor so that the Spring container can inject a MovieFinder public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // business logic that actually uses the injected MovieFinder is omitted... }
构造器参数解析
构造器参数解析匹配发生在使用参数类型。如果在bean的定义中不存在潜在的歧义,那么bean定义中参数的顺序与构造bean时向构造器提供的参数顺序对应。
package x.y; public class ThingOne { public ThingOne(ThingTwo thingTwo, ThingThree thingThree) { // ... } }
假设
ThingTow
和ThingThree
没有继承关系,不存在潜在的歧义,那么下面的配置将会正常工作。我们不需要在<onstructor-arg>
中指定参数的顺序或者类型<beans> <bean id="beanOne" class="x.y.ThingOne"> <constructor-arg ref="beanTwo"/> <constructor-arg ref="beanThree"/> </bean> <bean id="beanTwo" class="x.y.ThingTwo"/> <bean id="beanThree" class="x.y.ThingThree"/> </beans>
当一个bean被引用,这个bean的类型就是已知的,参数匹配能够正常工作。但是当引用一个基本类型时,如<value>true</value>,Spring就不能确定这个参数的类型了,此时就需要在配置中指定参数类型。
package examples; public class ExampleBean { // Number of years to calculate the Ultimate Answer private int years; // The Answer to Life, the Universe, and Everything private 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
解决歧义就很必要了。也可以使用
name
属性指定构造器参数的姓名:<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateAnswer" value="42"/> </bean>
要使上面的配置有效,需要使用debug模式编译,否则应该在构造器上使用
@ConstructorProperties
进行注解:package examples; public class ExampleBean { // Fields omitted @ConstructorProperties({"years", "ultimateAnswer"}) public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
基于修改器的依赖注入
修改器依赖注入是容器在调用无参构造函数或者无参工厂方法构建bena之后调用bean的修改器方法注入依赖。
下面这个类只能通过修改器注入进行依赖注入:
package x.y; 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; } // business logic that actually uses the injected MovieFinder is omitted... }
<bean id="movieFinder" class="x.y.MovieFinder"/> <bean id="simpleMovieLister" class="x.y.SimpleMovieLister"> <property name="movieFinder" ref="movieFinder"/> </bean>
基于构造器的依赖注入适合用于注入哪些必要的依赖,基于修改器的依赖注入适合那些非必要依赖的注入。对修改器进行
@Required
注解,将会使这个依赖称为必要依赖。 -
依赖解决流程
容器完成依赖解析的流程如下:
ApplicationContext
被创建并根据配置元数据进行初始化。- 当一个bean被创建后,它的依赖便以属性、构造器参数或者是静态工厂方法参数的形式提供出来。
- 每一个属性或者是参数都有一个定义了的值或者是对容器中其他bean的引用
- 为属性和参数配置的值都将被转换为这些属性和参数对应的类型。默认的,Spring可以将字符串类型的值转换为任意的基础类型。
Bean默认是单例预实例化的,在容器被创建之后,Bean就会被创建。这可以避免在应用运行时抛出一些初始化Bean的异常,让我们在容器初始化时就发现配置上出现的问题。
循环依赖:
如果我们主要使用构造器注入,那么可能会造成循环依赖。一个解决的办法就是,将其中一个依赖的注入方式改为修改器注入,或者全部使用修改器注入。虽然不提倡使用修改器注入,但这的确可以解决循环依赖的问题。
如果A依赖B,那么容器会在实例化B,并完成B的依赖注入之后,再讲B注入A。
-
依赖和配置详情
- 直接赋值(
value
属性):
<property>
或者<constructor-arg>
标签的value
属性可以为bean的属性或者构造器参数赋值String
类型的值。Spring的转换服务会将这些值转换为与bean属性或构造器参数对应的类型。如:<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>
使用
p命名空间
是的xml配置更加的简洁:<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql://localhost:3306/mydb" p:username="root" p:password="masterkaoli"/> </beans>
我们也可以定义一个
java.util.Properties
实例:<bean id="mappings" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"> <!-- typed as a java.util.Properties --> <property name="properties"> <value> jdbc.driver.className=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mydb </value> </property> </bean>
通过使用JavaBeans的
PropertyEditor
机制,Spring容器将<value>
标签内的文字转换为java.util.Properties
实例。<idref>
元素
<bean id="theTargetBean" class="..."/> <bean id="theClientBean" class="..."> <property name="targetName"> <idref bean="theTargetBean"/> </property> </bean>
上面的配置同下面的配置作用相同:
<bean id="theTargetBean" class="..." /> <bean id="client" class="..."> <property name="targetName" value="theTargetBean"/> </bean>
但是第一种配置要优于第二种配置,因为第一种配置在应用部署时,容器会检验
refid
的引用是否存在;第二种配置则不会检查,错误的拼写只有在client
bean被初始化时,才会发现这个引用根本不存在而抛出错误。<ref>
元素
<ref>
元素是<property>
和<constructor-arg>
元素中的终极元素。通过
<ref>
中的bean
属性来设置bean的依赖,这个依赖可以是同一个容器中的bean,也可以是父容器中的bean,所以来的bean也不一定是在同一个xml文档中定义的。bean
属性的值可以是所依赖bean的id的值,或者所依赖bean的一个name值。<ref bean="someBean"/>
通过
<ref>
元素的parent
元素来设置bean的依赖,该依赖bean必须在当前容器的一个父容器中,parent
属性的值可以是所依赖bean的id的值,或者所依赖bean的一个name值。
使用parent
属性的主要用途是为了用某个与父容器中的bean同名的代理来包装父容器中的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 name is the same as the parent bean --> <bean id="accountService" 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>
- 内部bean
在
<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>
内部bean不需要定义
id
,即使定义了,容器也会忽略。同时,容器也会忽略内部bean的scope
标志,因为内部bean是匿名的且总是同它的外部bean一起被创建。内部bean无法被独立访问,也无法注入到除他的外部bean以外的其它bean中。- 集合
<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>
map的
key
和value
的值,以及set的value
值可以是:bean | ref | idref | list | set | map | props | value | null- 集合合并
Spring容器支持子集合继承父集合,并覆盖父集合中的一些值。
<beans> <!-- abstract=“true”,容器不会实例化这个bean,该bean只会作为子bean的模板 --> <bean id="parent" abstract="true" class="example.ComplexObject"> <property name="adminEmails"> <props> <prop key="administrator">administrator@example.com</prop> <prop key="support">support@example.com</prop> </props> </property> </bean> <!-- 继承父bean的类型 --> <bean id="child" parent="parent"> <property name="adminEmails"> <!-- the merge is specified on the child collection definition --> <props merge="true"> <!-- 比父bean多的值 --> <prop key="sales">sales@example.com</prop> <!-- 覆盖父bean support的值 --> <prop key="support">support@example.co.uk</prop> </props> </property> </bean> <beans>
对于<list/>,值是有序的,父bean的值在子bean之前;对于<set/>、<map>和<props>而言,值是无序的。
- 直接赋值(
-
强类型集合
如果我们在定义类的集合属性时,擦除了类型,那么在配置时,可以为这些集合配置任意类型的元素。如果我们确定了类型,得益于Spring的类型转换机制,容器可以将String类型的值转换为对应的类型。
-
NULL和空值
<bean class="ExampleBean"> <property name="email" value=""/> </bean>
是相当于:
exampleBean.setEmail("");
<bean class="ExampleBean"> <property name="email"> <null/> </property> </bean>
相当于:
exampleBean.setEmail(null);
-
p命名空间
p命名空间 使得我们使用
<bean>
的属性而非<property>
元素来配置bean的依赖。普通的XML配置结构在XML Schema Document中有定义,但是p命名空间只在Spring Core中被定义。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 两个bean的作用相同 --> <bean name="classic" class="com.example.ExampleBean"> <property name="email" value="someone@somewhere.com"/> </bean> <bean name="p-namespace" class="com.example.ExampleBean" p:email="someone@somewhere.com"/> <!-- 使用p命名空间定义一个依赖 --> <bean name="p-modern" class="com.example.OtherExampleBean" p:detail-ref="classic"/> </beans>
在第三个bean中,使用
p:detail-ref
来创建一个从p-modern
到classic
的引用。其中,detail
是变量名,-ref
表示这不是一个直接赋值,而是一个对其它bean的引用。 -
c命名空间
同p命名空间一样,使用c命名空间使得我们使用
<bean>
的属性而非<constructor-arg>
元素来通过构造器注入依赖。<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="beanTwo" class="x.y.ThingTwo"/> <bean id="beanThree" class="x.y.ThingThree"/> <!-- traditional declaration with optional argument names --> <bean id="beanOne" class="x.y.ThingOne"> <constructor-arg name="thingTwo" ref="beanTwo"/> <constructor-arg name="thingThree" ref="beanThree"/> <constructor-arg name="email" value="something@somewhere.com"/> </bean> <!-- c-namespace declaration with argument names --> <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo" c:thingThree-ref="beanThree" c:email="something@somewhere.com"/> </beans>
c命名空间的使用规则于p命名空间相同。
一种少见的情况:当我们无法知道构造器入参的名称是,我们可以使用构造参数的序号:
<!-- c-namespace index declaration --> <bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree" c:_2="something@somewhere.com"/>
由于XML的语法:XML的属性名不能以数字开始,所以上述以序列符号需要一
_
开头。这种命名结构在<constructor-arg>
元素中也可以使用,
但是并不推荐使用,因为这个元素有index
属性,同时,<constructor-arg>
元素的顺序也可以作为序号。 -
混合元素名
当我们在设置bean属性时,我们可以使用混合元素名/嵌套元素名。
<bean id="something" class="things.ThingOne"> <property name="fred.bob.sammy" value="123" /> </bean>
上面的配置中,
something
bean有一个fred
属性,fred
属性有一个sammy
属性,并且我们为sammy
赋值为123
。为了让上面的配置生效,需要在bean被构造后保证fred
和bob
属性不为空,所以这里必须保证定义things.ThingOne
时,在构造器中对fred
进行了初始化,bob
属性同理。或者在bean定义中,为fred
设置引用。