上一篇讲了Spring中的IOC,该篇要具体讲解如何通过IOC对Bean进行注入
首先看一下Bean的元数据信息:
- Bean的实现类
- Bean的属性
- Bean的依赖关系
- Bean的行为配置
Bean的元数据信息在Spring容器中对应的是一个BeanDefinition 形成的Bean注册表。Spring4.x支持的配置方式如下:
1.基于XML文件配置
2.基于注解配置
3.Java类的配置
4.Groovy动态语言配置
主要说下前两种。
- 基于XML的配置
基于XML的配置是开发过程中常用的方式,因为XML配置文件可以对配置归档,便于统一管理,结构化的XML树相对比较直观。对于XML的配置方式,会把Bean相关的注入方式,Bean之间关系,Bean的作用域等知识点一起讲解
准备工作:引入Spring4.0的命名空间到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-4.0.xsd">
</beans>
下面看一个Spring HelloWorld 程序:
首先配置要注入的Bean
<?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-4.0.xsd">
<bean id="car" name="#car1" class="com.smart.simple.Car"></bean>
</beans>
只需要在Beans节点下配置一个Bean节点,并指定Bean的id,class,然后通过启动Spring容器,getBean(name)的方式获取到Bean的信息。
public class BeanRetrieveTest {
public ApplicationContext factory = null;
private static String[] CONFIG_FILES = {"com/smart/simple/beans.xml"};
@BeforeClass
public void setUp() throws Exception {
//获取到Spring容器
factory = new ClassPathXmlApplicationContext(CONFIG_FILES);
}
@Test
public void testBeanRetrieve(){
Car car = (Car)factory.getBean("#car1");
assertNotNull(car);
}
}
@BeforeClass 是 junit单元测试的注释,可以通过反射优先于测试单元执行,用来初始化测试单元中的外部需求。
XML中Bean节点的id属性,和name属性,都可以指定多个,用;,等隔开(推荐只用id即可)。而class一定要配置全限定类名。spring容器的反射也是基于全类名去查找对应的Class,进而实例化Bean的。
流程就是两步走:
1.读取xml文件,获取Bean信息
2.实例化Spring容器,并通过getBean获取到注入的Bean
getBean还有个传入Class的获取Bean的方式:
@Test
public void testBeanRetrieve(){
Car car = (Car)factory.getBean(Car.class);
assertNotNull(car);
}
这种方式,指定在容器中同一class类型的Bean只能有一个,否则就会抛org.springframework.beans.factory.NoUniqueBeanDefinitionException异常
Spring 依赖注入方式
- 属性注入
属性注入要求Bean提供一个默认构造函数(无参),并为需要的属性提供对应的setter方法。Spring先调用默认的构造器初始化Bean,然后通过反射方式调用setter方法注入属性值。例子:
public class Car {
private int maxSpeed;
public String brand;
private double price;
public static String HONG_QI = "红旗";
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String toString(){
return "brand:"+brand+"/maxSpeed:"+maxSpeed+"/price:"+price;
}
}
<bean id="car" class="com.smart.ditype.Car">
<property name="brand" value="红旗&CA72"/>
<property name="maxSpeed" value="200"/>
<property name="price" value="20000.00"/>
</bean>
public class DiTypeTest {
public ApplicationContext factory = null;
private static String[] CONFIG_FILES = { "com/smart/ditype/beans.xml" };
@BeforeClass
public void setUp() throws Exception {
factory = new ClassPathXmlApplicationContext(CONFIG_FILES);
}
@Test
public void testCar(){
Car car = (Car)factory.getBean("car");
assertNotNull(car);
System.out.println(car);
}
}
Spring只会检查是否有对应的setter方法,至于Bean中是否要求有成员属性的变更则不会要求。这样注入在获取属性的时候也不会出问题,但是Java Bean的规范还是要求getXXX能与属性名字保持一致。
JavaBean的属性命名规范:property元素指定的属性名和Bean中setter方法中满足:xxx属性对应setXxx()方法。
特殊情况,对于特定意义的英文大写缩略词,如USA,变量的前两个字母要不全小写,要不全部大写的规范。
错误的命名规范会导致报错。
- 构造函数注入
1.按类型匹配
public Car(String brand, double price) {
this.brand = brand;
this.price = price;
}
<!--构造函数注入:type -->
<bean id="car1" class="com.smart.ditype.Car">
<constructor-arg type="java.lang.String">
<value>红旗CA72</value>
</constructor-arg>
<constructor-arg type="double">
<value>20000</value>
</constructor-arg>
</bean>
2.按索引匹配入参
<!--构造函数注入:index -->
<bean id="car2" class="com.smart.ditype.Car">
<constructor-arg index="0" value="红旗CA72" /> <constructor-arg
index="1" value="中国一汽" /> <constructor-arg index="2" value="20000" />
</bean>
3.混合匹配入参
<!--构造函数注入:type&index -->
<bean id="car3" class="com.smart.ditype.Car">
<constructor-arg index="0" type="java.lang.String">
<value>红旗CA72</value>
</constructor-arg>
<constructor-arg index="1" type="java.lang.String">
<value>中国一汽</value>
</constructor-arg>
<constructor-arg index="2" type="int">
<value>200</value>
</constructor-arg>
</bean>
<bean id="car4" class="com.smart.ditype.Car">
<constructor-arg index="0">
<value>红旗CA72</value>
</constructor-arg>
<constructor-arg index="1">
<value>中国一汽</value>
</constructor-arg>
<constructor-arg index="2" type="int">
<value>200</value>
</constructor-arg>
</bean>
在构造参数中,Java通过自身反射可以自动识别匹配要配置的参数的类型。
public class Boss {
private String name;
private Car car;
private Office office;
public Boss(String name, Car car, Office office) {
this.name = name;
this.car = car;
this.office = office;
}
public Boss(String name, Car car) {
this.name = name;
this.car = car;
}
public Boss() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
public String toString(){
return "name:"+name+"/car:"+car.getBrand()+"/office:"+office;
}
}
<bean id="boss1" class="com.smart.ditype.Boss">
<constructor-arg>
<value>John</value>
</constructor-arg>
<constructor-arg>
<ref bean="car" />
</constructor-arg>
<constructor-arg>
<ref bean="office" />
</constructor-arg>
</bean>
<bean id="office" class="com.smart.ditype.Office" />
循环依赖问题
Spring容器对构造函数配置Bean进行实例化有个前提,就是Bean构造函数入参引用的对象必须已经准备就绪。所以,如果两个Bean都采用构造注入,并且互相引用对方,就会导致类似于线程死锁问题。
public class Boss {
private String name;
private Car car;
public Boss(String name, Car car) {
this.name = name;
this.car = car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
}
public class Car {
public String brand;
private String corp;
private double price;
private Boss boss;
public Car(String brand, Boss boss) {
this.brand = brand;
this.boss= boss;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Boss getBoss() {
return boss;
}
public void setBoss(Boss boss) {
this.boss= boss;
}
}
<bean id="boss3" class="com.smart.ditype.Boss">
<constructor-arg>
<value>John</value>
</constructor-arg>
<constructor-arg>
<ref bean="carRef" />
</constructor-arg>
</bean>
<bean id="carRef" class="com.smart.ditype.Car">
<constructor-arg><value>BMW</value></constructor-arg>
<constructor-arg><ref bean="boss3"/></constructor-arg>
</bean>
这样就会出现循环依赖的问题。报了如下错误:
Error creating bean with name ‘boss3’: Requested bean is currently in creation: Is there an unresolvable circular reference?
- 工厂方法注入
1.非静态工厂
public class CarFactory {
//指定非静态工厂方法
public Car createHongQiCar(){
Car car = new Car();
car.setBrand("红旗CA72");
return car;
}
public static Car createCar(){
Car car = new Car();
return car;
}
}
<!-- 非静态工厂方法 指定factoryBean 和factoryMethod-->
<bean id="carFactory" class="com.smart.ditype.CarFactory" />
<bean id="car5" factory-bean="carFactory" factory-method="createHongQiCar">
</bean>
2.静态工厂
<!-- 静态工厂方法 只需要指定静态的 factoryMethod-->
<bean id="car6" class="com.smart.ditype.CarFactory"
factory-method="createCar"></bean>
- 注入参数详解
1.字面值:一般指可以使用字符串表示的值,使用value元素标签注入
<bean id="car" class="com.smart.attr.Car">
<property name="maxSpeed">
<value>200</value>
</property>
<property name="brand">
<value>
<![CDATA[红旗&CA72]]>
</value>
</property>
<property name="price" value="2000.00" />
</bean>
通过属性注入,可以把字面值直接注入到对应的属性中,<![CDATA[红旗&CA72]]>
用来让xml标签对注入的内容当做普通字符处理。XML中有&,<,>,”,’ 一共5个特殊字符需要特别处理。也可以通过转义序列来进行处理。
2.引用其他Bean
<bean id="boss" class="com.smart.attr.Boss">
<property name="car" ref="car" />
<property name="name" value="Tom" />
<property name="age" value="45" />
</bean>
引用ref可以通过配置属性local和parent进行不同Bean的引用,此处不常用。
3. 内部Bean
如果car Bean只被Boss引用,那么可以在Boss内部声明一个Bean
<!-- 内部Bean -->
<bean id="boss2" class="com.smart.attr.Boss">
<property name="car">
<bean class="com.smart.attr.Car">
<property name="maxSpeed" value="200" />
<property name="price" value="2000.00" />
</bean>
</property>
</bean>
- null值引用
<bean id="car" class="com.smart.attr.Car" lazy-init="default">
<property name="brand">
<value>
<null/>
</value>
</property>
<property name="maxSpeed">
<value>200</value>
</property>
<property name="price" value="2000.00" />
</bean>
5.级联属性
当一个Bean中需要对另外一个Bean中的属性赋值时,就用到了级联属性
<bean id="boss" class="com.smart.attr.Boss">
<property name="car.brand" value="BMW"/>
</bean>
6.集合类型属性
<!-- 引用Bean -->
<bean id="boss1" class="com.smart.attr.Boss">
<property name="car" ref="car" />
<property name="favorites">
<!-- list>
<value>看报</value>
<value>赛车</value>
<value>高尔夫</value>
</list -->
<set>
<value>看报</value>
<value>赛车</value>
<value>高尔夫</value>
</set>
</property>
<property name="jobs">
<map>
<entry >
<key>
<value>AM</value>
</key>
<value>会见客户</value>
</entry>
<entry>
<key>
<value>PM</value>
</key>
<value>公司内部会议</value>
</entry>
</map>
</property>
<property name="mails">
<props>
<prop key="jobMail">john-office@smart.com</prop>
<prop key="lifeMail">john-life@smart.com</prop>
</props>
</property>
<property name="jobTime">
<map>
<entry>
<key>
<value>会见客户</value>
</key>
<value>124</value>
</entry>
</map>
</property>
</bean>
集合除了上述方式配置,还可以通过util标签简化集合配置。
7.简化配置方式
通过p标签对配置进行简化
<bean id="car" class="com.smart.ditype.Car"
p:brand="红旗&CA72"
p:maxSpeed="200"
p:price="20000.00"/>
<bean id="boss" class="com.smart.ditype.Boss"
p:car-ref="car"/>
8.自动装配
1、创建CumputerBean类
package www.csdn.spring.autowire.bean;
public class CumputerBean {
// 电脑名称
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "CumputerBean [name=" + name + "]";
}
}
2、创建DeptBean 类
package www.csdn.spring.autowire.bean;
public class DeptBean {
//部门名称
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "DeptBean [name=" + name + "]";
}
}
3、创建EmployeeBean
package www.csdn.spring.autowire.bean;
public class EmployeeBean {
private DeptBean deptBean;
private CumputerBean cumputerBean;
public void setDeptBean(DeptBean deptBean) {
this.deptBean = deptBean;
}
public void setCumputerBean(CumputerBean cumputerBean) {
this.cumputerBean = cumputerBean;
}
@Override
public String toString() {
return "EmployeeBean [deptBean=" + deptBean + ", cumputerBean="
+ cumputerBean + "]";
}
}
首先分析no、byName、byType的配置都是采用setter方法依赖注入实现的案例
1、no配置(通过ref=””引用需要的bean)
<?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.xsd">
<!-- 电脑bean -->
<bean id="cumputerBean" class="www.csdn.spring.autowire.bean.CumputerBean">
<property name="name" value="HP6325笔记本" />
</bean>
<!-- 部门bean -->
<bean id="deptBean" class="www.csdn.spring.autowire.bean.DeptBean">
<property name="name" value="CSDN教育事业部" />
</bean>
<!-- 员工bean 根据EmployeeBean中的 属性名称 去匹配-->
<bean id="employeeBean" class="www.csdn.spring.autowire.bean.EmployeeBean">
<property name="cumputerBean" ref="cumputerBean" />
<property name="deptBean" ref="deptBean" />
</bean>
</beans>
2、byName配置(分析:会根据EmployeeBean中属性的名称 自动装配)
<?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.xsd">
<!-- 电脑bean -->
<bean id="cumputerBean" class="www.csdn.spring.autowire.bean.CumputerBean">
<property name="name" value="HP6325笔记本" />
</bean>
<!-- 部门bean -->
<bean id="deptBean" class="www.csdn.spring.autowire.bean.DeptBean">
<property name="name" value="CSDN教育事业部" />
</bean>
<!-- 员工bean-->
<bean id="employeeBean" class="www.csdn.spring.autowire.bean.EmployeeBean" autowire="byName"/>
</beans>
3、byType配置(分析:会根据EmployeeBean中属性的类型 自动装配)
<?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.xsd">
<!-- 电脑bean -->
<bean id="cumputerBean" class="www.csdn.spring.autowire.bean.CumputerBean">
<property name="name" value="HP6325笔记本" />
</bean>
<!-- 部门bean -->
<bean id="deptBean" class="www.csdn.spring.autowire.bean.DeptBean">
<property name="name" value="CSDN教育事业部" />
</bean>
<!-- 员工bean 根据EmployeeBean中的 属性名称 去匹配-->
<bean id="employeeBean" class="www.csdn.spring.autowire.bean.EmployeeBean" autowire="byType"/>
</beans>
注意:当根据byType类型装配时,当在容器内找到多个匹配的类型时会出现如下error
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'employeeBean' defined in class path resource [spring-byType.xml]: Unsatisfied dependency expressed through bean property 'deptBean': : No qualifying bean of type [www.csdn.spring.autowire.bean.DeptBean] is defined: expected single matching bean but found 2: deptBean,deptBean1; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [www.csdn.spring.autowire.bean.DeptBean] is defined: expected single matching bean but found 2: deptBean,deptBean1
4、Constructor(构造器参数根据byType类型匹配,自动装配)
首先修改EmployeeBean类 修改后代码如下:
package www.csdn.spring.autowire.bean;
public class EmployeeBean {
private DeptBean deptBean;
private CumputerBean cumputerBean;
public EmployeeBean(DeptBean deptBean, CumputerBean cumputerBean) {
super();
this.deptBean = deptBean;
this.cumputerBean = cumputerBean;
}
@Override
public String toString() {
return "EmployeeBean [deptBean=" + deptBean + ", cumputerBean="
+ cumputerBean + "]";
}
}
配置文件操作:
<?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.xsd">
<!-- 电脑bean -->
<bean id="cumputerBean" class="www.csdn.spring.autowire.bean.CumputerBean">
<property name="name" value="HP6325笔记本" />
</bean>
<!-- 部门bean -->
<bean id="deptBean" class="www.csdn.spring.autowire.bean.DeptBean">
<property name="name" value="CSDN教育事业部" />
</bean>
<!-- 员工bean 根据EmployeeBean中的 属性名称 bytype 去匹配 -->
<bean id="employeeBean" class="www.csdn.spring.autowire.bean.EmployeeBean"
autowire="constructor">
</bean>
</beans>
接下来的内容,会涉及到Bean之间关系等。会独立为另外的文章,接下篇。