依赖
企业应用绝不会只有1个简单对象(或者说Spring bean)。哪怕是最简单的应用,也会包含许多对象协同工作。下一章节讲述,如何为真正的应用定义大量的、独立的bean,并让这些对象一起合作。
依赖注入是一个有对象定义依赖的手法,也就是,如何与其他对象合作,通过构造参数、工厂方法参数、或是在对象实例化之后设置对象属性,实例化既可以构造也可以是使用工厂方法。容器在它创建bean之后注入依赖。这个过程从根本上发生了反转,因此又名控制反转(Ioc),因为Spring bean自己控制依赖类的实例化或者定位 ,Spring bean中就有依赖类的定义,容器使用依赖类构造器创建依赖类实例。
DI机制使代码简洁,对象提供它们的依赖,解耦更高效。对象无需自己查找依赖。同样的,类更容易测试,尤其当依赖接口或者抽象类时,测试允许在单元测试中使用stub或者mock(模拟技术)实现。
DI有2种主要方式,构造注入,容器调用构造函数并传参数,每个参数都是依赖。调用静态工厂方法并传参数方式构造bean和构造注入差不多,这里是指构造注入处理参数和静态工厂方法处理参数像类似。下例中展示了一个只能使用构造注入的类。注意,此类无任何特别之处,并未依赖容器指定的接口、基类、注解,就是一个POJO。
public class SimpleMovieLister {
// the SimpleMovieLister 依赖 a MovieFinder
private MovieFinder movieFinder;
//Spring容器能注入MovieFinder的构造函数
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// 实际如何使用MovieFinder的业务逻辑省略了
}
构造参数解决方案,会匹配所使用的参数类型。如果在bean的定义中,构造参数不存在歧义,那么,在bean定义中定义的构造参数的次序,在bean实例化时,就是提供给适合的构造参数的次序。看这个类:
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
不存在歧义,假设Bar和Baz类没有集成关系,那么下面的配置是合法的,而且,不需要在<constructor-arg/>元素里指定构造参数的明确的indexes索引或者类型。
<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>
若需要引用另一个bean,类型已知,构造函数就可以匹配参数类型(像上面的示例)。使用简单类型时, 想<value>true</true>,Srping不能决定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>
当构造函数有2个相同类型的参数,指定次序可以解决此种情况。注意index是从0开始
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
setter注入
Setter注入是容器调用bean上的setter方法,bean是使用无参构造函数返回的实例,或者无参静态工厂方法返回的实例。 下面样例中展示了只能使用Setter注入的类。这个类是传统java类,就是个POJO,不依赖容器指定的接口、基类、注解。
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...
}
ApplicationContext对它所管理的bean支持构造注入和setter注入。也支持先构造注入再setter注入。定义依赖,会转换成某种形式的BeanDefinition类,BeanDefinition类与PropertyEditor实例配合,即可将属性从一种格式转换成其他格式。然而,大多数程序员不会直接使用这些类(也就是编程式),更多的是使用XML、注解(也就是@Component@Controller等等),或者@Configuration注解的类中的方法上使用 @Bean。这些配置数据,都会在容器内部转换成BeanDefinition,用于加载整个Spring Ioc 容器。
xml配置中的直接赋值(原始类型、String等等)
<property />元素的value属性为对象域属性或者构造参数设置了一个可读的字串。Spring的会将其转换为实际的与属性或者参数的数据类型。
<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>
也可以这样配java.unit.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>
引用其他bean(协作类)
ref元素是<constructor-arg/>元素和<property/>元素内决定性元素。用它设置bean的属性以引用另一个容器管理的bean。引用的bean就是要设置属性的bean的依赖,在设置属性值之前它就要被初始化。(如果协作类是单例bean,它会在容器初始化时首先完成初始化)。差不多所有的bean都会引用其他对象。指定id/name的对象的作用域和依赖校验通过bean,local ,parent属性来配置。 指定引用bean通常使用<ref/>标签,它允许引用本容器或者父容器中任意的bean,无需配置在同一个xml文件中 。<ref/>标签中bean的属性值,使用的被引用bean的id或者name。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="CustomerBean" class="com.example.common.Customer">
<property name="person" ref="PersonBean" />
</bean>
<bean id="PersonBean" class="com.example.common.Person">
<property name="name" value="MrChen" />
<property name="address" value="address1" />
<property name="age" value="28" />
</bean>
</beans>
集合
<list/>,<set/>,<map/>,<props/>元素,用来设置Java Collection属性和参数,分别对应List,Set,Map,Properties
<bean id="moreComplexObject" class="example.ComplexObject">
<!--调用setAdminEmails(java.util.Properties) -->
<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>
<!-- 调用setSomeList(java.util.List) -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- 代用setSomeMap(java.util.Map) -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- 调用 setSomeSet(java.util.Set) -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
构造注入对比setter注入
何时使用构造注入,何时使用setter注入,经验法则是:强制依赖用构造,可选依赖用Setter。注意,在settter方法上使用@Required注解即可另属性强制依赖。
Spring 团队建议,构造注入的实例是不可变的,不为null的。此外,构造注入组件要将完全初始化后的实例返回给客户端代码。还有,大量参数的构造函数是非常烂的,它意味着该类有大量的职责,得重构。
setter注入主要用于可选依赖,类内部可以指定默认依赖。否则类内所有使用依赖的地方,都得进行非空校验。setter注入的有个好处就是,类可以重配置或者再注入。因此,使用JMX MBeans
进行管理的场景中,就非常适合setter注入。
使用何种依赖注入方式,对于某些类,非常有意义。有时协同第三方类处理,没有源码,由你来决定使用何种方式。比如,第三方类未暴露任何setter方法,那么构造注入也许就是唯一的可行的注入方式了。
依赖处理过程
容器解析bean依赖如下:
ApplicationContext创建后用配置元数据中描述的所有bean进行初始化。配置元数据格式可以是XML、Java Code,或者注解。
每个bean的依赖,都会以下列形式表达:属性、构造参数,静态工厂方法的参数。当bean真正的创建时,这些依赖会被提供给bean。
每个属性或者构造函数或者以value值形式在bean处直接设置,或者引用容器中其他bean。
每一个属性或者构造参数都是一个值,该值将会从指定的格式转换为属性、构造参数的真正类型。Spring默认会将一个String类value转换成内建类型,比如int,long,String,boolean等等。
Spring容器在创建bean之前会验证bean的配置。在bean创建之前,bean的属性不会赋值。当容器创建之后,会创建被设置为预先初始化的sington-scope单例作用域bean,非单例作用域bean,只有在请求时才会创建。
微信公众号: java技术
技术交流群: 245130488
网友评论