Spring配置概述
要使应用程序中的Spring容器成功启动,需要具备以下三个条件:
- Spring框架的类包都已经放到工程项目的类路径下
- 应用程序为Spring提供完备的Bean配置信息
- Bean的类都已经放到应用程序的类路径下
Bean配置信息是Bean的元数据信息,它由四个方面组成:
- Bean的实现类
- Bean的属性信息,如数据源的连接数,密码,用户名等
- Bean的依赖关系,Spring根据依赖关系配置完成Bean之间的装配
- Bean的行为配置,如生命周期范围以及生命周期各过程的回调函数等
Bean配置信息中定义了Bean的实现以及依赖关系,Spring容器启动时读取Bean的配置信息,并且根据各种形式的Bean配置信息在容器内部建立起Bean定义注册表,之后根据Bean注册表实例化Bean,并建立起Bean与Bean之间的依赖关系,最后再将实例化的Bean放到Spring容器中的Bean缓存池,应用程序每次调用Bean就在此缓存池中使用。
Bean的基本配置
一般情况下,Spring IoC容器中的一个Bean即对应配置文件中的一个<bean>,这是一种镜像对应关系。其中id为这个Bean的名称,是这个Bean的唯一标识,通过容器的getBean(“id名”)即可获得对应的Bean,在容器中起到定位查找的作用,是外部程序与Spring IoC容器进行交互的桥梁。class属性指定了Bean对应的实现类。
Spring配置文件不允许出现相同id的<bean>,但却允许出现相同name的<bean>,如果有多个name相同的<bean>,通过getBean(beanName)获得Bean时,将返回最后声明的那个Bean,原因是最后的Bean覆盖了前面同名的Bean,所以避免无意间Bean覆盖的隐患,应尽量使用id而非name命名的Bean。
依赖注入
Spring支持两种依赖注入的方式,分别是属性注入和构造函数,除此之外,Spring还提供了工厂方法注入方式。
属性注入
属性注入即通过setter方法注入Bean的属性值或依赖对象,由于属性注入方式具有可选择性和灵活性高的优点,因此属性注入是实际应用中常用的注入方式。属性注入要求Bean提供一个默认的构造函数,并为需要注入的属性提供对应的setter方法。Spring先调用Bean的默认构造函数实例化Bean对象,然后通过反射的方式调用setter方法注入属性值。
注:默认构造函数是不带参的构造函数。如果Java类中没有定义任何构造函数,则JVM自动为其生成一个默认构造函数。假设Car类中定义了一个带参数的构造函数,如public Car(String brand),则需要同时提供一个默认构造函数public Car(),否则使用属性注入会抛出异常。
构造函数注入
构造函数注入是除属性注入之外的另一种常用的注入方式,它保证一些必要的属性在Bean实例化时就得到设置,它保证了Bean实例在实例化后就可以使用。
如果任何可用的对象都必须提供属性值,如果使用属性注入方式只能人为在配置时提供保证而无法在语法级提供保证,这时通过构造函数注入就可以很好地满足这一要求。使用构造函数注入的前提是Bean必须提供带参的构造函数。
package Car
public class Car{
....
public Car(String brand, double price){
this.brand = brand;
this.price = price;
}
}
<bean id="car" class="Car.Car">
<constructor-arg type="java.lang.String">
<value>baoma</value>
</constructor-arg>
<constructor-arg type="double">
<value>200.00</value>
</constructor-arg>
</bean>
在<constructor-arg>的元素中有一个type属性,它为Spring提供了判断配置项和构造函数入参对应关系的信息。当出现入参类型相同时,只能够通过入参类型和索引信息间接确定构造函数配置项和入参的对应关系。
package Car
public class Car{
....
public Car(String brand, String corp,double price){
this.brand = brand;
this.corp = corp;
this.price = price;
}
public Car(String brand, String corp,int maxSpeed){
this.brand = brand;
this.corp = corp;
this.maxSpeed= maxSpeed;
}
}
<bean id="car" class="Car.Car">
<constructor-arg index="0" type="java.lang.String">
<value>A6</value>
</constructor-arg>
<constructor-arg index="1" type="java.lang.String">
<value>奥迪</value>
</constructor-arg>
<constructor-arg index="2" type="double">
<value>200.00</value>
</constructor-arg>
</bean>
对于上述代码中两个构造函数,如果仅仅通过index进行配置,Spring将无法确定第三个入参配置项究竟是int的maxSpeed还是double的price,所以采用索引匹配配置时,真正引起异议的是第三个入参,故只要明确指定第三个入参的类型就行了。
构造注入与属性注入的对比
构造方法的好处
- 构造函数可以保证一些重要的属性在Bean实例化时就设置好,避免了因为一些重要属性没有提供,导致一个无用Bean实例的情况。
- 不需要为每个属性提供setter方法,减少类的方法个数
- 可以更好地封装类变量,不需要为每个属性指定setter方法,避免外部错误调用
属性注入的好处
- 构造函数注入有时会造成循环依赖(A依赖于B,B依赖于A)的问题,类似于线程死锁。
- 如果有多个构造函数,需要考虑配置文件和具体构造函数匹配歧义的问题,配置上相对复杂。
- 构造函数不利于类的继承与扩展,因为子类需要引用到父类复杂的构造函数。
- 构造函数的灵活性不强,在有些属性时可选的情况下,如果构造函数注入,也要为不需要的参数提供一个null值。
- 如果一个类的属性众多,构造函数的可读性就变低了。