文章目录
依赖注入
依赖注入就是在Spring创建Bean的时候,去实例化该Bean构造函数所需的参数,或者通过Setter方法去设置该Bean的属性。
Spring的依赖注入有两种基于构造函数的依赖注入和基于setter的依赖注入。
基于构造函数的依赖注入
构造函数的注入是通过构造函数的参数来实现的。如下所示:
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; } }
该Bean有一个两个参数的构造函数,那么怎么注入这些参数呢?
有三种方法。
方法1:按构造函数的类型匹配:
<bean id="exampleBeanA" class="com.flydean.beans.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean>
这里通过指定参数的类型,即可以指定哪个参数是years,哪个参数是ultimateAnswer。
方法2:构造函数索引:
<bean id="exampleBeanB" class="com.flydean.beans.ExampleBean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </bean>
Spring中可以通过构造函数的索引来指定特定的参数。要注意Spring的索引是从0开始的。
方法3:构造函数名字匹配:
<bean id="exampleBeanC" class="com.flydean.beans.ExampleBeanWithConstructorProperties"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateAnswer" value="42"/> </bean>
如果通过构造函数的名字来匹配,需要注意必须在编译的时候开启调试标志,要不然Spring不能在构造函数中找到参数名。
如果不想启用调试标志,则必须使用@ConstructorProperties JDK注解显式命名构造函数参数。
public class ExampleBeanWithConstructorProperties { // Number of years to calculate the Ultimate Answer private int years; // The Answer to Life, the Universe, and Everything private String ultimateAnswer; @ConstructorProperties({"years", "ultimateAnswer"}) public ExampleBeanWithConstructorProperties(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
基于Setter的注入
Setter注入主要用来无参构造器或者获得对象实例之后才设置对象的属性。下面是Setter的例子:
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... }
对于的XML文件如下:
<!--Setter DI --> <bean id="movieFinder" class="com.flydean.beans.MovieFinder"/> <bean id="simpleMovieLister" class="com.flydean.beans.SimpleMovieLister"> <property name="movieFinder" ref="movieFinder"/> </bean>
除了XML配置,也可以使用注解:@Component、@Controller。或者使用@Configuration注解中的@Bean方法。
如何选择?
既然有这样两种注入方式,我们怎么选择呢?
通常来说,对于必须的属性,我们通过构造函数来注入。对于可选属性,我们通过Setter注入。当然你也可以在Setter方法中使用@Required注解。
当然如果第三方类不公开任何setter方法,那么构造函数注入可能是DI的唯一可用形式。
循环依赖
循环依赖主要出现在构造函数注入的情况。
类A通过构造函数注入需要类B的实例,类B通过构造函数注入需要类A的实例。如果为要相互注入的类A和类B配置bean,那么SpringIOC容器在运行时检测到这个循环引用,会抛出BeanCurrentlyInCreationException。
解决方式就是使用Setter注入。
依赖注入的配置详解
基本类型,字符串或者其他
如果< property/>元素的value属性是基本类型,Spring会将其转换为类需要的类型,配置如下:
<!--Setter DI --> <bean id="movieFinder" class="com.flydean.beans.MovieFinder"> <property name="name" value="movie"/> <property name="number" value="123456"/> </bean>
这是一个常见的Setter注入。为了简洁,也可以使用p-namespace,如下:
<?xml version="1.0" encoding="UTF-8"?> <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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="movieFinder" class="com.flydean.beans.MovieFinder" p:name="movie" p:number="123456"/> </beans>
Spring容器使用JavaBeans属性编辑器机制将< value/>元素中的文本转换为java.util.properties实例。这是一个很好的快捷方式,如下所示:
<bean id="mappings" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <!-- 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>
注意上面例子中的value里面的值。
ref
通过< ref/>标记的bean属性允许在同一容器或父容器中创建对任何bean的引用,而不管它是否在同一XML文件中。bean属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的一个值相同。以下示例显示如何使用ref元素:
<ref bean="someBean"/>
内部bean
在< property/> 或者 < constructor-arg/>元素内部的< bean/>元素可以定义一个内部bean,下面是个例子:
<bean id="simpleMovieLister" class="com.flydean.beans.SimpleMovieLister"> <property name="movieFinder"> <bean class="com.flydean.beans.MovieFinder"> <property name="name" value="movie"/> <property name="number" value="123456"/> </bean> </property> </bean>
内部bean定义不需要ID或名称。如果指定,容器也不会使用这个值作为标识符。容器在创建时也忽略作用域标志,因为内部bean总是匿名的,并且总是用外部bean创建的。不可能单独访问内部bean,也不可能将它们注入到除封闭bean之外的协作bean中。
集合
< list/>, < set/>, < map/>,和 < props/> 分别被用来设置Java Collection类型List, Set, Map,和 Properties 类型的属性和参数。 下面是个例子:
<bean id="movieFinderE" class="com.flydean.beans.MovieFinder"></bean> <bean class="com.flydean.beans.CollectionBean"> <property name="properties"> <props> <prop key="administrator">administrator@example.org</prop> <prop key="support">support@example.org</prop> <prop key="development">development@example.org</prop> </props> </property> <property name="arrayList"> <list> <value>"a list element followed by a reference"</value> </list> </property> <property name="hashMap"> <map> <entry key="an entry" value="just some string"/> <entry key ="a ref" value-ref="movieFinderE"/> </map> </property> <property name="hashSet"> <set> <value>just some string</value> <ref bean="movieFinderE" /> </set> </property> </bean>
强类型集合
通过在Java 5中引入泛型类型,可以使用强类型集合。也就是说,可以声明集合类型,使其只能包含(例如)字符串元素。如果使用Spring将强类型集合注入bean,则可以利用Spring的类型转换支持,以便在将强类型集合实例的元素添加到集合之前将其转换为适当的类型。下面的Java类和bean定义的例子:
public class SomeClass { private Map<String, Float> accounts; public void setAccounts(Map<String, Float> accounts) { this.accounts = accounts; } }
<bean id="something" class="com.flydean.beans.SomeClass"> <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>