文章目录
了解 Bean
-
Spring 启动时读取应用程序提供的 Bean 配置信息,并在 Spring 容器中生成一份相应的 Bean 配置注册表,根据注册表实例化 Bean,装配好 Bean 之间的依赖关系。
-
Bean 配置信息是 Bean 的元数据信息,主要有4个方面:
- Bean 的实现类
- Bean 的属性信息,如数据源的连接数、用户名、密码等
- Bean 的依赖关系,Spring 根据依赖关系配置完成 Bean 之间的装配
- Bean 的行为配置,如生命周期范围、生命周期各过程的回调函数等
- Bean 元数据信息在 Spring 容器中由
org.springframework.beans.factory.config.BeanDefinition
接口形成的 Bean 注册表,Spring 支持多种形式的 Bean 的配置方式。基于XML配置、基于注解配置、基于 Java Config 类配置、基于Croovy 动态语言配置
XML(Schema)形式的配置
Bean 的基本配置
装配一个 Bean
- 在 Spring 容器的配置文件中,定义一个简单的 Bean 的配置:一个 标签表示一个 Bean
<bean id="managerBean" name="managerBean"
class="com.learning.spring.ioc.bean.xml.manager.ManagerBean"
lazy-init="false" scope="singleton"
init-method="init" destroy-method="destroy"
autowire="no"
abstract="false"
parent="" depends-on=""
factory-bean="" factory-method=""
/>
- bean 标签
属性 | 描述 |
---|---|
id | Bean 的名称。id 在 Ioc 容器中必须是唯一。id 是 xml 的特殊属性,需要符合其命名规范:必须字母开头、不能使用逗号空格等 |
name | Bean 的名称,功能与 id 一致。name 没有字符上的限制,可以不唯一,但是定义在后面的会覆盖前面的。不指定,默认是全限定类名,建议使用 id |
class | Bean 对应的实现类 |
scope | Bean 的作用域,默认是 singleton,可选prototype、request等 |
lazy-init | 是否懒加载,默认 false |
init-method | 指定初始化方法,与实现 InitializingBean 接口的 afterPropertiesSet 效果一致 |
destroy-method | 指定销毁方法,与实现 DisposableBean 接口的 destroy 效果一致 |
autowire | 自动装配,默认是 no 不启用。不建议使用 |
abstract | 是否抽象,默认是 flase。true-不实例化该 Bean,继承关系的父类Bean ,会设置这个属性 |
parent | 指定父 Bean |
depends-on | 指定依赖 |
factory-bean | 指定工厂类,非静态工厂方法装配使用 |
factory-method | 指定工厂方法,工厂方法装配使用 |
Bean 的装配方式
- 构造函数装配
- Bean 实体类
public class ManagerBean {
private String name;
public ManagerBean() {
System.out.println("我被实例化了");
}
public void save() {
System.out.println("~~ save 方法 ~~");
}
}
- XML 装配配置
<!--构造函数的方式-->
<bean id="managerBean" name="managerBean" class="com.learning.spring.ioc.bean.xml.manager.ManagerBean"/>
- 静态工厂方法装配
- Bean 实体类与工厂
public class BeanManagerFactory {
/**
* 静态方法,创建 ManagerBean 对象
*
* @return
*/
public static ManagerBean createManagerBean() {
System.out.println("静态方法工厂方法");
return new ManagerBean();
}
}
- XML 装配配置
<!--静态工厂方法,其实 createPersonService 内部还是调用的构造函数-->
<bean id="beanManagerFactory" class="com.learning.spring.ioc.bean.xml.manager.BeanManagerFactory"
factory-method="createManagerBean"/>
- 非静态工厂方法装配
- Bean 实体类与工厂
public class BeanManagerFactory {
/**
* 非静态方法,需要把 PersonServiceFactory 也交给容器管理
*
* @return
*/
public ManagerBean managerBean() {
System.out.println("工厂非静态方法");
return new ManagerBean();
}
}
- XML 装配配置:先实例化工厂类,然后在实例化需要的 Bean
<!-- 实例工厂非静态方法:先实例化工厂类,然后在实例化需要的 Bean -->
<bean id="beanManagerFactory2" class="com.learning.spring.ioc.bean.xml.manager.BeanManagerFactory"/>
<bean id="personService3" factory-bean="beanManagerFactory2" factory-method="managerBean"/>
懒加载设置(ApplicationContext)
- 默认容器初始化就会加载,详见 Bean 生命周期相关博文
lazy-init
配置指定懒加载,实现延迟初始化 Bean,实现只有第一次获取 getBean 时才会初始化<beans default-lazy-init="true"></beans>
实现所有 Bean 延迟初始化scope="prototype"
,默认不会随容器初始化而初始化,并且没法修改( lazy-init=“false” 无效),因为单例会缓存,而 prototype 每次都是新的
指定初始化与销毁方法
init-method
指定初始化方法,在构造函数之后destroy-method
指定销毁方法,在容器销毁之前
依赖注入
属性注入
- 属性注入指通过 setXXX 方法注入 Bean 的属性值或依赖对象,最常用的方式。属性注入要求 Bean 提供一个默认的构造函数,并为需要注入的属性提供对应的 Setter 方法。
- Bean 实例类
public class BeanInjectionService {
private FieldInjection fieldInjection;
/**
* 属性注入需要有 setXXX 方法
*
* @param fieldInjection
*/
public void setFieldInjection(FieldInjection fieldInjection) {
this.fieldInjection = fieldInjection;
}
}
/**
* 属性注入的Bean
*/
class FieldInjection {
public void get() {
System.out.println("调用 FieldInjection.get() 方法");
}
}
- 依赖注入配置
<bean id="fieldInjection" class="com.learning.spring.ioc.bean.xml.injection.FieldInjection"/>
<bean id="beanInjectionService" class="com.learning.spring.ioc.bean.xml.injection.BeanInjectionService">
<!--1- 属性注入-->
<property name="fieldInjection" ref="fieldInjection"/>
</bean>
构造函数注入
- 构造函数注入,有几种方式,如按类型参数入参(type)、按索引匹配入参(index)、联合使用、通过自身类型反射匹配入参,其中最简单稳妥的是按索引匹配入参(index)或者类型索引联合使用。
- 构造函数注入示例
- Bean 对象类
public class BeanInjectionService {
private ConstructorInjection constructorInjection;
/**
* 构造函数注入
*
* @param constructorInjection
*/
public BeanInjectionService(ConstructorInjection constructorInjection) {
this.constructorInjection = constructorInjection;
}
}
/**
* 构造函数注入的 Bean
*/
class ConstructorInjection {
public void done() {
System.out.println("调用 ConstructorInjection.done() 方法");
}
}
- 构造函数注入配置
<bean id="constructorInjection" class="com.learning.spring.ioc.bean.xml.injection.ConstructorInjection"/>
<bean id="beanInjectionService" class="com.learning.spring.ioc.bean.xml.injection.BeanInjectionService">
<!--2- 构造函数注入-->
<constructor-arg index="0" name="constructorInjection" ref="constructorInjection" />
</bean>
构造函数实现注入,循环依赖问题
-
Spring 容器能对构造函数配置的 Bean 进行实例化有一个前提,即 Bean 构造函数入参引用的对象必须已经准备就绪。由于这个机制,如果两个Bean 都采用构造函数注入,而且都通过构造函数入参引用对方,就会发生类似于线程死锁的循环依赖问题。
-
解决:构造函数注入改成属性注入就可以了
-
代码示例:Sam 与 Eason 相互依赖,导致 ApplicationContext 启动失败
class Sam {
private Eason eason;
public Sam(Eason eason) {
this.eason = eason;
}
}
class Eason {
private Sam sam;
public Eason(Sam sam) {
this.sam = sam;
}
}
<!--构造函数实现注入,循环依赖问题-->
<bean id="sam" class="com.learning.spring.ioc.bean.xml.injection.Sam">
<constructor-arg ref="eason"/>
</bean>
<bean id="eason" class="com.learning.spring.ioc.bean.xml.injection.Eason">
<constructor-arg ref="sam"/>
</bean>
各种注入参数的详细讲解
在Spring 配置文件中,可以将String、int等字面值注入 Bean 中,还可以将集合、Map等类型的数据注入 Bean ,此外还可以注入配置文件汇总其他定义的 Bean
- 字面量
- 引用其他Bean
- 内部 Bean
- null 值
- 级联属性
- 集合类型属性
- 简化配置方式
- 自动装配
Bean 之间的关系
- 继承:abstract、parent 属性
- 依赖: depends-on、ref 属性。不是直接的 ref 关系,但是间接关联,使用depends-on
- 引用:一个Bean 要引用另一个 Bean 的id属性,可以使用 idref 标签
Bean 的作用域
基本作用域
- singleton:在Spring Ioc 容器中仅存在一个Bean 实例,Bean 以单例的方式存在
- prototype:多实例。每次从容器中调用Bean 时,都返回一个新的实例。即每次调用getBean(),相当于执行new XxxBean
Web 的作用域
-
request:每次Http请求都会创建一个新的 Bean。
-
session:同一个Http session 共享一个 Bean。不同的 http Session 使用不同的 Bean
-
global session:同一个全局的 Session 共享一个bean,一般用于 Portlet 应用环境
-
配置实现:
配置 RequestContextListener 监听器,该监听器实现 ServletRequestListener 监听
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
import 整合多个配置文件
FactoryBean 的作用
了解 FactoryBean
-
一般情况下,Spring 通过反射机制利用 bean 标签的class属性指定的实现类实例化 Bean 。某些时候实例化 Bean 的过程比较复杂,配置方式灵活性收到限制,编码的方式更合适。Spring 提供了一个 FactoryBean 工厂接口,用户通过该接口定制化 Bean 的逻辑
-
FactoryBean 支持泛型,且有三个方法
方法 | 描述 |
---|---|
boolean isSingleton(); | 确定由 FactoryBean 创建的Bean 的作用域 |
T getObject() throws Exception; | 返回由 FactoryBean 创建的 Bean 的类型 |
T getObject() throws Exception; | 返回由 FactoryBean 创建的 Bean 实例。如果 isSingleton()方法返回 true,该实例会放到 Spring 容器的单实例缓存池 |
FactoryBean 示例
- Bean 对象类
class CustomBean {
public CustomBean() {
System.out.println("CustomBean 实例化");
}
public void done() {
System.out.println("调用了 CustomBean.done 方法");
}
}
- 实现 FactoryBean 接口
public class CustomFactoryBean implements FactoryBean<CustomBean> {
/**
* 返回由 FactoryBean 创建的 Bean 实例。
* 如果 isSingleton=true,该实例会放到 Spring 容器的单实例缓存池
*
* @return
* @throws Exception
*/
@Override
public CustomBean getObject() throws Exception {
return new CustomBean();
}
/**
* 返回由 FactoryBean 创建的 Bean 的类型
*
* @return
*/
@Override
public Class<?> getObjectType() {
return CustomBean.class;
}
/**
* 确定由 FactoryBean 创建的Bean 的作用域
*
* @return
*/
@Override
public boolean isSingleton() {
return true;
}
}
- Bean 装配:不需要配置具体的Bean类,只需要配置 FactoryBean 的实现类
<bean id="customFactoryBean" class="com.learning.spring.ioc.bean.xml.factory.CustomFactoryBean"/>
- 简单运行:
- 直接获取 Bean 实例对象
- 获取 FactoryBean 实例对象 ,需要加上
&
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
// 获取 CustomBean 的 bean 对象
CustomBean customBean = context.getBean("customFactoryBean", CustomBean.class);
customBean.done();
// 获取 FactoryBean 实例 ,需要加上 &
CustomFactoryBean customFactoryBean = context.getBean("&customFactoryBean", CustomFactoryBean.class);
customFactoryBean.getObject().done();
((ClassPathXmlApplicationContext) context).close();