概述
Bean配置信息:
即Bean的元数据信息,包括Bean的实现类,属性信息,依赖关系和行为配置(生命周期及生命周期过程中的回调函数)
Spring容器内部协作接口
首先,容器会根据Bean的配置信息,在容器内部建立Bean定义注册表(一个个BeanDefinition对象),然后根据注册表实例化Bean,并建立Bean和Bean之间的依赖关系,最后将这些准备就绪的Bean放入缓存池中,供外部的应用程序使用
Bean基本配置
基于XML文件的配置方式如下
<bean id="foo1" class="com.spring4.chpter5.Foo"></bean>
除了使用id为Bean命名,还可以使用name为Bean命名,name属性支持多个命名,可以使用空格,分号,或逗号分开
<bean name="foo1 foo2 foo3" class="com.spring4.chpter5.Foo"></bean>
依赖注入
Spring支持3种方式的注入,分别是属性注入,构造函数注入和工厂方法注入
属性注入
通过属性的setter()方法进行注入,要求Bean必须提供一个默认的构造函数,并为需要注入的属性提供setter()方法
public class Car {
private String brand;
private String color;
private String maxSpeed;
public void setBrand(String brand) {
this.brand = brand;
}
public void setColor(String color) {
this.color = color;
}
public void setMaxSpeed(String maxSpeed) {
this.maxSpeed = maxSpeed;
}
public void introduce() {
System.out.println("brand:" + this.brand + ";color:" + this.color + ";maxSpeed:" + this.maxSpeed);
}
}
<bean id="car" class="com.spring4.chpter5.Car">
<property name="brand" value="影刺HT+"></property>
<property name="color" value="黑色"></property>
<property name="maxSpeed" value="300"></property>
</bean>
注意,Spring只会检查Bean中是否有对应的setter方法,并不关心是否有该属性,例如上面Car类中有setBrand()方法,但却不一定要有brand属性
构造函数注入
使用构造函数注入,要求Bean必须提供带参数的构造函数。例如上面的Car类,提供一个带参数的构造函数。
public Car(String brand,String color,String maxSpeed) {
this.brand = brand;
this.color = color;
this.maxSpeed = maxSpeed;
}
bean.xml配置如下:
<!-- 根据构造函数注入 -->
<constructor-arg name="color" value="红色"></constructor-arg>
<constructor-arg name="brand" value="游侠9"></constructor-arg>
<constructor-arg name="maxSpeed" value="350"></constructor-arg>
构造函数注入还提供按索引和按参数类型匹配入参的功能,可以使用constructor-arg标签下的type属性和index属性配置。
注意:循环依赖问题
考虑下面的例子:
Car类的构造函数
public Car(String brand,String color,String maxSpeed,Boss boss) {
this.brand = brand;
this.color = color;
this.maxSpeed = maxSpeed;
this.boss = boss;
}
Boss类的构造函数
public Boss(String name,Car car) {
this.name = name;
this.car = car;
}
bean.xml配置:
<bean id="car" class="com.spring4.chpter5.Car">
<constructor-arg name="color" value="红色"></constructor-arg>
<constructor-arg name="brand" value="游侠9"></constructor-arg>
<constructor-arg name="maxSpeed" value="350"></constructor-arg>
<constructor-arg name="boss" ref="boss"></constructor-arg
</bean>
<bean id="boss" class="com.spring4.chpter5.Boss">
<constructor-arg name="name" value="SSS"></constructor-arg>
<constructor-arg name="car" ref="car"></constructor-arg>
</bean>
由于在使用构造函数注入时,Bean的入参引用的对象必须已经准备就绪,而这里的Car类和Boss类都使用了构造函数注入,而且都互相引用了对方,因此会发生循环依赖问题,两者都会等待对方实例化,就会出现类似线程死锁的问题,SpringIoC容器将不能启动成功。
工厂方法注入
非静态工厂方法:
public Car getCar() {
Car car = new Car();
return car;
}
<bean id="carFactory" class="com.spring4.chpter5.CarFactory"></bean>
<bean id="car2" factory-bean="carFactory" factory-method="getCar"></bean>
静态工厂方法:
public static Car getCar2() {
Car car = new Car();
return car;
}
<bean id="car3" class="com.spring4.chpter5.CarFactory" factory-method="getCar2"></bean>
注入方式的考量
选择构造函数注入的理由:
1.构造函数注入可以保证一些重要的属性在Bean实例化好之前就设置好,避免因为一些重要属性没有提供而导致一个无用Bean实例的情况
2.不需要为每个属性都设置setter方法,减少方法的数量
3.可以更好的封装类变量,避免外部错误的调用
不选择构造函数注入的理由:
1.如果类的属性过多,bean标签下的constructor-arg标签也会增多,可读性差
2.灵活性不强,如果在有些属性是可选的情况下,通过构造函数注入需要传入null作为默认值
3.不利于类的继承和扩展,因为子类也需要引用父类的构造函数
4.会造成循环依赖问题
部分注入参数介绍
1.当注入的属性包含了XML的特殊字符时,可以使用
<property name="brand"><value><![CDATA[影刺HT+&游侠999]]></value></property>
2.引用其他Bean
通过ref标签引用其他的Bean,ref标签包含两个属性,bean和parent,bean属性表示引用当前容器中的Bean,如果没有,则引用父容器中的Bean;parent属性表示引用父容器中的Bean。
父容器bean.xml:
<bean id="car" class="com.spring4.chpter5.Car">
<property name="brand"><value><![CDATA[影刺HT+&游侠999]]></value></property>
<property name="color" value="黑色"></property>
<property name="maxSpeed" value="300"></property>
</bean>
子容器bean2.xml:
<bean id="car" class="com.spring4.chpter5.Car">
<property name="brand"><value>New棉花糖Black></value></property>
<property name="color" value="黑色"></property>
<property name="maxSpeed" value="300"></property>
</bean>
<bean id="boss" class="com.spring4.chpter5.Boss">
<constructor-arg name="name" value="SSS"></constructor-arg>
<constructor-arg name="car">
<!-- <ref bean="car"/> --> <!-- 打印结果为brand:New棉花糖Black>;color:黑色;maxSpeed:300 -->
<ref parent="car"/> <!-- 打印结果为brand:影刺HT+&游侠999;color:黑色;maxSpeed:300 -->
</constructor-arg>
</bean>
3.内部Bean
内部Bean和Java的匿名内部类相似,没有名字,也不能被其他Bean引用,只能在声明处为外部Bean提供实例注入
<bean id="boss" class="com.spring4.chpter5.Boss">
<bean class="com.spring4.chpter5.Car">
<property name="brand"><value>New棉花糖></value></property>
<property name="color" value="绿色"></property>
<property name="maxSpeed" value="280"></property>
</bean>
</constructor-arg>
</bean>
4.null值
通过标签为属性注入null值
<bean id="boss" class="com.spring4.chpter5.Boss">
<bean class="com.spring4.chpter5.Car">
<property name="brand"><value>New棉花糖></value></property>
<property name="color" value="绿色"></property>
<property name="maxSpeed"><null></null></property>
</bean>
</constructor-arg>
</bean>
5.级联属性
以圆点(.)的方式定义级联属性
<property name="car.brand" value="黑色甲虫9"></property>
6.定义集合属性
例如:为Boss类添加一个List类型的favorites属性
public class Boss {
private List<String> favorites = new ArrayList<String>();
...
}
Spring配置如下:
<property name="favorites">
<list>
<value>吃饭</value>
<value>睡觉</value>
<value>打豆豆</value>
</list>
</property>
注意:不仅仅是List类型的属性,数组类型(int[],String[])的属性也可以通过这种方式进行注入
Map类型和Set类型的注入方式与List类似
<!-- 注入Set类型的数据 -->
<property name="favorites">
<set>
<value>吃饭</value>
<value>睡觉</value>
<value>打豆豆</value>
</set>
</property>
<!-- 注入Map类型的数据 -->
<property name="jobs">
<map>
<!-- 一个entry代表一个键值对 -->
<entry key="AM" value="MA"></entry>
<entry key="WW" value="CC"></entry>
</map>
</property>
除此之外,List,Set,Map也可以使用Bean作为注入的对象,可通过注入
<list>
<ref bean="beanName" />
</list>
<set>
<ref bean="beanName"/>
</set>
<map>
<entry key-ref="beanName" value-ref="beanName"></entry>
</map>
Properties类型的属性注入
Properties与Map的区别在于,Properties只支持key和value为字符串。
<property name="props">
<props>
<prop key="key1">value1</prop>
<prop key="key2">value2</prop>
</props>
</property>
通过util命名空间配置集合类型的Bean
如果希望配置一个集合类型的Bean,而不是一个集合类型的属性,可以使用util命名空间进行配置,需要在Spring配置文件中引入util命名空间的声明。
xmlns:util="http://www.springframework.org/schema/util"
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd
配置一个List类型的Bean,可以通过list-class属性显示的指定List的实现类
<util:list id="list1" list-class="java.util.LinkedList">
<value>吃饭</value>
<value>睡觉</value>
<value>打豆豆</value>
</util:list>
配置一个Set类型的Bean,可以通过set-class属性显示的指定Set的实现类
<util:set id="set1" set-class="java.util.HashSet">
<value>吃饭</value>
<value>睡觉</value>
<value>打豆豆</value>
</util:set>
配置一个Map类型的Bean,可以通过map-class属性显示的指定Map的实现类
<util:map id="map1" map-class="java.util.HashMap">
<entry key="AM" value="MA"></entry>
<entry key="WW" value="CC"></entry>
</util:map>
方法注入
1.lookup方法注入
声明一个MagicBoss接口,并声明一个getCar()方法,现在通过lookup方法注入,使每次调用getCar()方法都返回一个新的car Bean
public interface MagicBoss {
public Car getCar();
}
<bean id="car" class="com.spring4.chpter5.Car" scope="prototype"><!-- 这里要设置car为prototype -->
<property name="brand"><value>New棉花糖Black></value></property>
<property name="color" value="黑色"></property>
<property name="maxSpeed" value="300"></property>
</bean>
<bean id="magicBoss" class="com.mjf.spring4.chpter5.MagicBoss">
<lookup-method name="getCar" bean="car"/>
</bean>
2.方法替换
Bean实现MethodReplacer接口后,可以使用该接口的方法去替换目标Bean的方法
例如:
Boss1的getCar()方法返回Car1
public class Boss1 {
public Car getCar() {
Car car = new Car();
car.setBrand("Car1");
return car;
}
}
Boss2实现了org.springframework.beans.factory.support.MethodReplacer接口,该接口返回Car2
public class Boss2 implements MethodReplacer{
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
Car car = new Car();
car.setBrand("Car2");
return car;
}
}
Spring配置如下
<bean id="boss1" class="com.mjf.spring4.chpter5.Boss1">
<!-- 使用boss2的reimplement方法替换boss1的getCar方法 -->
<replaced-method name="getCar" replacer="boss2"></replaced-method>
</bean>
<bean id="boss2" class="com.mjf.spring4.chpter5.Boss2"></bean>
Bean之间的继承和依赖
1.继承
通过继承,子bean会继承父bean的所有配置信息
<bean id="car" class="com.spring4.chpter5.Car" scope="prototype">
<property name="brand"><value>New棉花糖Black></value></property>
<property name="color" value="黑色"></property>
<property name="maxSpeed" value="300"></property>
</bean>
<!-- 继承car Bean,brand和maxSpeed属性与car一致 -->
<bean id="car2" class="com.spring4.chpter5.Car" scope="prototype" parent="car">
<property name="color" value="红色"></property>
</bean>
2.依赖
通过depends-on属性,显式的指定Bean前置依赖的Bean,前置依赖的Bean会在本Bean实例化之前创建好
<!-- boss3会在mycar实例化完成后,在实例化 -->
<bean id="boss3" class="com.spring4.chpter5.Boss" depends-on="mycar"></bean>
<bean id="mycar" class="com.spring4.chpter5.Car"></bean>
整合多个配置文件
通过import标签将多个xml配置文件整合到一起
<import resource="classpath*:com/spring4/chpter5/bean.xml"/>
Bean的作用域
类型 | 说明 |
---|---|
singelton | Bean以单例的方式存在 |
prototype | 与singelton相反,每次都会返回新的 Bean |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean,该作用域仅适用于WebApplicationContext环境 |
globalSession | 同一个全局Session共享一个Bean,一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext环境 |
注意:当非web相关作用域的Bean引用web相关作用域的Bean时,需要与Spring的动态代理技术一起使用,如下:
<!-- 根据代理,判断boss5需要取得那个HTTP请求相关的car Bean -->
<bean id="car5" class="com.spring4.chpter5.Car" scope="request">
<aop:scoped-proxy/><!-- 创建代理 -->
</bean>
<bean id="boss5" class="com.spring4.chpter5.Boss" scope="singleton">
<property name="car" ref="car5"></property><!-- 引用web相关作用域的Bean -->
</bean>
基于注解的配置
1.使用注解定义的Bean
将class定义为Bean的注解类别:
注解名 | 说明 |
---|---|
@Component | 将一个class定义为Bean |
@Repository | 用于对Dao实现类进行标注 |
@Service | 用于对Service实现类进行标注 |
@Controller | 用于对Controller实现类进行标注 |
2.扫描注解定义的Bean
Spring提供了一个Context的命名空间,它提供了通过扫描类包以应用注解定义Bean的方式,如下:
<!-- 声明Context的命名空间 -->
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 扫描类包以应用注解定义的Bean -->
<context:component-scan base-package="com.spring4.chpter5"></context:component-scan>
3.自动装配Bean
使用@Autowired进行自动注入(默认使用byType的方式)
@Autowired
private Car car;
将@Autowired的required属性设置为false,即使Spring找不到匹配的Bean,也不会抛出异常
可以使用@Qualifier注解指定注入的Bean的名称
@Autowired
@Qualifier("car")//注入名为car的Bean
private Car car;
@Autowired和@Qualifier除了能注解属性外,还可以注解方法,如下:
@Autowired
public Boss(String name,@Qualifier("car") Car car) {
System.out.println("Boss Constructor");
this.name = name;
this.car = car;
}
使用@Lazy注解指定延迟依赖注入,注意@Lazy注解必须同时标注在属性和目标Bean上,否则无效。
4.Bean作用范围和生命过程方法
使用@Scope注解指定Bean的作用范围,例如:@Scope(“prototype”)。
使用@PostConstruct和@PreDestory注解指定Bean的初始化及容器销毁前执行的方法,可以标注多个方法。
基于Java类的配置
1.使用Java类提供Bean定义信息
使用@Configuration将一个POJO标注定义为Bean的配置类(我的理解是,同xml配置文件类似,也可以作为bean标签使用)
使用@Bean注解标注方法,提供Bean的定义信息(和bean标签类似)
例如:
@Configuration
public class AppConf {
@Bean
public Boss1 getBoss1() {
Boss1 boss1 = new Boss1();
return boss1;
}
@Bean
public Car getCar() {
Car car = new Car();
return car;
}
}
上面的代码等同于
<bean id="AppConf" class="com.spring4.chpter5.AppConf"></bean>
<bean id="getBoss1" class="com.spring4.chpter5.Boss1"></bean>
<bean id="getCar" class="com.spring4.chpter5.Car"></bean>