任何一个功能模块都是由很多组件(对象)在一起协调完成的,订单管理组件、产品管理组件、支付组件需要彼此了解并相互协作,它们还需要与数据库访问组件一起协作,从而完成从数据库读取数据、写入数据的行为。
在Spring中,所有对象的创建、装配、维护、销毁,都交给Spring容器,对象之间的关联(即装配)也是由Spring容器来负责完成。
(一)spring bean 的常用装配方式
1)java中显示配置(使用注解)
interface Wheel {
}
public class BigWheel implements Wheel {
}
public class Car {
Wheel wheel;
public Car(Wheel wheel) {
this.wheel = wheel;
}
void setWheel(Wheel wheel) {
this.wheel = wheel;
}
}
生成:
@Configuration
public class WheelConfig {
@Bean(name="myWheel")
public Wheel getWheel(){
return new BigWheel();
}
}
注入方法1:
@Bean
public Car getCar() {
return new Car(getWheel());
}
注入方法2:
@Bean
public Car getCar(Wheel wheel) {
return new Car(Wheel wheel);
}
注入方法3:
@Bean
public Car getCar(Wheel wheel) {
Car car = new Car();
car.setWheel(wheel);
return car;
}
JavaConfig是配置代码,其与业务逻辑代码应该是隔离的,最好放在单独的包中。配置代码不应该包含任何业务逻辑,也不应该侵入业务逻辑代码。
接口Wheel和类BigWheel可以是我们项目文件中定义的,也可能是引用第三方库中的组件,我们可以在JavaConfig中,使用它们,显示地创建我们所需要的bean。
注解@Configuration 说明WheelConfig类是一个配置类,该类包含了在spring应用上下文中创建bean的细节。
带有@Bean注解的方法getWheel()定义了一个生成Wheel类型的bean的方法,默认情况下该bean的ID与方法名相同,但是我们可以通过设置name属性对其指定命名。
将Wheel类型的bean注入Car类型的bean中,可以将getWheel()方法作为Car的构造函数的参数,此时并不会在每一次注入时都调用getWheel()方法,而是返回同一个由getWheel()方法产生的bean。也可以采用如方法2更加简单的方法从spring容器中寻找符合条件的Wheel类型的bean,该bean可能是有javaConfig显示配置生成、可能是由XML配置生成,也可能是由组件扫描生成的。方法2和方法3的区别在于一个使用了构造器方法,另一个使用了Setter方法,其他方法也是可以的。
2)XML中显示配置
<?xml version="1.0" encoding="UTF-8"?>
<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="myWheel" class="com.oschina.BigWheel">
</beans>
<beans>元素是所有Spring配置文件中的根元素
<bean>元素相当于JavaConfig中的@Bean注解,id可以用来指定bean的名字。这里有两个问题,1)Spring发现这个 <bean>元素时,会调用Wheel的默认构造函数来创建一个bean,这就显得不如JavaConfig功能强大了,因为使用JavaConfig,我们可以指定任意一种方式来创建bean;2)<bean>元素的 class如果填错,在编译器是无法发现的。
我们需要创建的bean通常可能包含很多成员属性,或是bean的引用,或是字面量常量等等。XML配置提供了构造器注入和属性注入两种配置方式,让我们可以在创建bean的时候,注入bean引用或是注入字面量。
a)构造器注入
构造器注入bean引用(<constructor-arg>元素或是c命名空间)
public class Car {
private Wheel wheel;
}
<bean id="myCar" class="com.oschina.Car">
<constructor-arg ref="myWheel"/>
</bean>
<bean id="myCar" class="com.oschina.Car">
c:wheel-ref="myWheel"
/>
<bean id="myCar" class="com.oschina.Car">
c:_0-ref="myWheel"
/>
<bean id="myCar" class="com.oschina.Car">
c:_-ref="myWheel"
/>
构造器注入字面量(<constructor-arg>元素或是c命名空间)
public class Car {
private String name;
}
<bean id="myCar" class="com.oschina.Car">
<constructor-arg value="myWheel"/>
</bean>
<bean id="myCar" class="com.oschina.Car">
c:_name="myWheel"
/>
<bean id="myCar" class="com.oschina.Car">
c:_0="myWheel"
/>
<bean id="myCar" class="com.oschina.Car">
c:_="myWheel"
/>
装配集合(只能使用<constructor-arg>元素)
public class Car {
List<Wheel> wheelList;
List<String> nameList;
}
<bean id="myCar" class="com.oschina.Car">
<constructor-arg>
<list>
<ref bean="myWheel">
<ref bean="hisWheel">
<ref bean="herWheel">
</list>
</constructor-arg>
<constructor-arg>
<list>
<value> mimo </value>
<value> timo </value>
<value> nimo </value>
</list>
</constructor-arg>
</bean>
b)属性注入
属性注入bean引用(propertyname或是p命名空间)
public class Car {
private Wheel wheel;
}
<bean id="myCar" class="com.oschina.Car">
<property name="wheel" ref="myWheel"/>
</bean>
<bean id="myCar" class="com.oschina.Car">
p:wheel-ref="myWheel"
/>
<bean id="myCar" class="com.oschina.Car">
p:_0-ref="myWheel"
/>
<bean id="myCar" class="com.oschina.Car">
p:_-ref="myWheel"
/>
属性注入字面量(propertyname或是p命名空间)
public class Car {
private String nick;
private String label;
}
<bean id="myCar" class="com.oschina.Car">
<propertyname name="nick" value="mimo"/>
<propertyname name="label" value="good"/>
</bean>
<bean id="myCar" class="com.oschina.Car">
p:nick="myWheel"
p:label="good"
/>
装配集合(只能使用<constructor-arg>元素)
public class Car {
List<Wheel> wheelList;
List<String> nameList;
}
<bean id="myCar" class="com.oschina.Car">
<property wheelList = "myWheel">
<List>
<value> mimo </value>
</List>
</property>
</bean>
3)组件扫描(compont scanning),自动装配(autowiring)
如果我们需要创建的bean的类型都是在自己的源文件中定义的,那么除了JavaConfig和XML的显示配置之外,我们还可以通过组件扫描、自动装配的方式来创建bean。
@Congiuation
@ComponentScan
public class WheelConfig{
}
@ComponentScan("com.oschina")
public class WheelConfig{
}
@ComponentScan(basePackages={"com.oschina","com.osindea"})
public class WheelConfig{
}
@ComponentScan(basePackages={CodeChina.class, CodeIndea.class})
public class WheelConfig{
}
@Component("myWheel")
public class BigWheel implements Wheel {
}
通过在配置类上加注解@ComponentScan,可以在该配置类所在的包目录下扫描所有文件,对于拥有注解@Component的类,会自动创建对应类型的bean。还可以通过设置ComponentScan注解的basePackages属性,来指定组件扫描的基础包。
还可以在XML配置中指定组件扫描的基础包
<context:component-scan base-package="com.oschina" />
@Component
public class Car{
@Autowired
private Wheel wheel;
}
@Component
public class Car{
private Wheel wheel;
@Autowired
void Car(Wheel wheel) {
this.wheel = wheel;
}
}
@Component
public class Car{
private Wheel wheel;
@Autowired
void setWheel(Wheel wheel) {
this.wheel = wheel;
}
}
通过注解@Autowired,我可以将类型Car所需要的依赖的bean自动注入,该注解可以加在成员属性上、构造方法上,或者setter方法上,或者其他任何方法。
拥有@Autowired注解的属性或是方法,如果在spring容器中找不到合适类型的bean,或者找到了多个,都会报错。@Autowired(required=false),可以设置required属性为false,避免找不到bean的报错。对于多个bean的错误,可以通过@Resource设置name或@Qualifier或@Primary来选择指定的bean。
4)三种方式的比较:
组件扫描、自动装配是最方便的方式,但该方法生成的bean散落在项目中的各个地方,查找不便,且只能对项目内部定义的类进行组件扫描。
XML的方式将所有bean的生成配置在若干张XML文件中,统一配置,统一管理,但是当项目中所需的bean非常多时,也会导致XML文件过于庞大。且该方式只能调用类的默认构造函数进行bean的生成,且class一旦配错,在编译器无法发现。
JavaConfig的方式在所需生成bean的个数非常多时,也会有配置文件过大的问题,但是其可以使用任意一种方式产生bean,比XML更加灵活。
我个人比较喜欢组件扫描配合XML的方式来进行配置。