Spring
将管理对象称为 Bean
。 Spring
可以看作是一个大型工厂,用于生产和管理 Spring
容器中的 Bean
。如果要使用 Spring
生产和管理 Bean
,那么就需要将 Bean
配置在 Spring
的配置文件中。Spring
框架支持 XML
和 Properties
两种格式的配置文件,在实际开发中常用 XML
格式的配置文件。
Bean 的配置
通常,在 XML
文件中,根元素为 <beans>
, <beans>
中包含多个 <bean>
子元素,每个 <bean>
定义一个 Bean
,并描述 Bean
如何被装配到 Spring
容器中。
<beans>
<bean/>
<bean></bean>
</beans>
属性或子元素名称 | 描述 |
---|---|
id | Bean 在 BeanFactory 中的唯一标识,获取 Bean 实例时需要以此作为索引名称 |
class | Bean 的具体实现类,使用类名 |
scope | 指定 Bean 实例的作用域 |
name | Bean 的别名,多个别名之间用逗号、空格或分号分隔 |
<constructor-arg> | <bean> 元素的子元素,使用构造方法注入,指定构造方法的参数。该元素的 index 属性指定参数的序号,ref 属性指定对 BeanFactory 中其他 Bean 的引用关系,type 属性指定参数类型,value 属性指定参数的常量值 |
<property> | <bean> 元素的子元素,用于设置一个属性。该元素的 name 属性指定 Bean 实例中相应的属性名称,value 属性指定 Bean 的属性值,ref 属性指定属性对 BeanFactory 中其他 Bean 的引用关系 |
<list> | <property> 元素的子元素,用于封装 List 或数组类型的依赖注入 |
<map> | <property> 元素的子元素,用于封装 Map 类型的依赖注入 |
<set> | <property> 元素的子元素,用于封装 Set 类型的依赖注入 |
<entry> | <map> 元素的子元素,用于设置一个键值对 |
Bean 的作用域
在 Spring
中可以为 Bean
指定作用域:
Bean 的实例化
在 Spring
框架中,如果想使用 Spring
容器中的 Bean
,需要对其进行实例化。Spring
框架实例化 Bean
有3种方式,即构造方法实例化、静态工厂实例化和实例工厂实例化。
(1)构造方法实例化
在 Spring
框架中,Spring
容器可以调用 Bean
对应类的无参构造方式来实例化 Bean
:
public class BookDaoImpl implements BookDao {
@Override
public void save() {
}
}
<bean id="bookDao" class="com.dao.impl.BookDaoImpl"/>
注意:若没有无参构造方法,则将抛出
BeanCreationException
异常
(2)静态工程实例化
在使用静态工厂实例化 Bean
时,开发者需要在工厂类中创建一个静态方法来创建 Bean
的实例。在配置 Bean
时,class
属性指定静态工厂类,同时还需要使用 factory-method
属性指定工厂类中的静态方法:
public class BookDaoImpl implements BookDao {
@Override
public void save() {
}
}
// ...
public class BookDaoFactory {
public static BookDao getBookDao() {
return new BookDaoImpl();
}
}
<bean id="bookDao" class="com.factory.BookDaoFactory" factory-method="getBookDao"/>
(3)实例工厂实例化
在使用实例工厂实例化 Bean
时需要我们在工厂类中创建一个实例方法来创建 Bean
的实例。在配置 Bean
时需要使用 factory-bean
属性指定配置的实例工厂,同时还需要使用 factory-method
属性指定实例工厂中的实例方法:
public class BookDaoImpl implements BookDao {
@Override
public void save() {
}
}
// ...
public class BookDaoFactory {
public BookDao getBookDao() {
return new BookDaoImpl();
}
}
<bean id="bookDaoFactory" class="com.factory.BookDaoFactory"/>
<bean id="bookDao" factory-method="getBookDao" factory-bean="bookDaoFactory"/>
除了上面的实现方式,还可以对实例工厂实例化方式进行简化,即使用 FactoryBean
实例化 Bean
。创建一个实现 FactoryBean
接口的工厂方法,实现统一接口,最后进行配置:
public class BookDaoFactoryBean implements FactoryBean<BookDao> {
@Override
public BookDao getObject() throws Exception {
return new BookDaoImpl();
}
@Override
public Class<?> getObjectType() {
return BookDao.class;
}
@Override
public boolean isSingleton() {
return true; // 使用单例模式
}
}
<bean id="bookDao" class="com.factory.BookDaoFactoryBean"/>
Bean 的生命周期
Bean
的生命周期是指 Bean
从创建到消亡的完整过程。而 Bean
生命周期控制是指在 Bean
创建后到销毁前做一些事情。
public class BookDaoImpl implements BookDao {
@Override
public void save() {
}
// 用于实现 bean 初始后对应操作
public void init() {
System.out.println("init...");
}
// 用于实现 bean 销毁前对应操作
public void destroy() {
System.out.println("destroy...");
}
}
<bean id="bookDao" class="com.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/>
上面是通过配置方法的方式实现 Bean
生命周期控制,也可以通过接口的方式实现 Bean
生命周期控制,实现 InitializingBean
和 DisposableBean
接口以及对应的方法:
public class BookDaoImpl implements BookDao, InitializingBean, DisposableBean {
@Override
public void save() {
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("init...");
}
@Override
public void destroy() throws Exception {
System.out.println("destroy...");
}
}
<bean id="bookDao" class="com.dao.impl.BookDaoImpl"/>
我们运行 Bean
生命周期控制的代码会发现:init
执行了,但是 destroy
没有执行。原因是:程序运行在虚拟机中,在虚拟机启动后,会经历容器初始化、使用 Bean
以及关闭/销毁容器三个阶段。然而,这里虚拟机并没有进入关闭/销毁容器阶段就退出了。这里有两种进入关闭/销毁阶段的方式:
(1)在虚拟机退出前,主动关闭容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.close();
(2)注册关闭钩子
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.registerShutdownHook(); // 指示在关闭虚拟机之前先关闭容器
Bean 的装配
Bean
的装配可以理解为将 Bean
依赖注入到 Spring
容器中。Spring
容器支持基于 XML
配置的装配、基于注解的装配以及自动装配等多种装配方式。
(1)基于 XML
配置的装配
在 Spring
中,有两种主要的基于 XML
配置的装配方式:一种是通过属性的 setter
方法进行注入,另一种是通过构造方法进行注入。根据注入的数据类型,这些方式又可以分为引用类型注入和简单类型注入。
- 使用
setter
方法注入简单类型数据
在 bean
中定义简单类型数据并提供可访问的 set
方法:
public class BookDaoImpl implements BookDao {
private int connectionNum;
private String databaseName;
public void setConnectionNum(int connectionNum) {
this.connectionNum = connectionNum;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
}
配置中使用 property
标签 value
属性注入简单类型数据:
<bean id="bookDao" class="com.dao.impl.BookDaoImpl">
<property name="connectionNum" value="10"/>
<property name="databaseName" value="mysql"/>
</bean>
- 使用
setter
方法注入引用类型数据
在 bean
中定义引用类型属性并提供可访问的 set
方法:
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
配置中使用 property
标签 ref
属性注入引用类型:
<bean id="bookService" class="com.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
<bean id="bookDao" class="com.dao.impl.BookDaoImpl"/>
- 使用构造方法注入简单类型数据
在 bean
中定义简单类型属性并提供可访问的构造方法:
public class BookDaoImpl implements BookDao {
private int connectionNum;
private String databaseName;
public BookDaoImpl(int connectionNum, String databaseName) {
this.connectionNum = connectionNum;
this.databaseName = databaseName;
}
}
配置中使用 constructor-arg
标签 value
属性注入简单类型对象:
<bean id="bookDao" class="com.dao.impl.BookDaoImpl">
<constructor-arg name="connectionNum" value="10"/>
<constructor-arg name="databaseName" value="mysql"/>
</bean>
- 使用构造方法注入引用类型数据
在 bean
中定义引用类型属性并提供可访问的构造方法:
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public BookServiceImpl(BookDao bookDao) {
this.bookDao = bookDao;
}
}
配置中使用 constructor-arg
标签 ref
属性注入引用类型对象:
<bean id="bookDao" class="com.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
(2)基于注解的装配
使用 @Component
定义 bean
:
@Component("bookDao") // 起名称为 bookDao
public class BookDaoImpl implements BookDao {
@Override
public void save() {
}
}
核心配置文件中通过组件扫描加载 bean
:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.dao"/>
</beans>
另外,Spring
提供了 @Component
注解的三个衍生注解,分别是:
@Controller
:用于表现层bean
定义@Service
:用于业务层bean
定义@Repository
:用于数据层bean
定义
(3)自动装配
IoC
容器根据 bean
所依赖的资源在容器中自动查找并注入到 bean
中的过程称为自动装配。自动装配的方式可分为:按类型、按名称、按构造方法和默认。
- 配置中使用
bean
标签autowire
属性设置自动装配的类型:
<bean id="bookDao" class="com.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.service.impl.BookServiceImpl" autowire="byType"/>
使用细节
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作
- 使用按类型装配时(
byType
)必须保障容器中相同类型的bean
唯一,推荐使用 - 使用按名称装配时(
byName
)必须保障容器中具有指定名称的bean
,因变量名与配置耦合,不推荐使用 - 自动装配优先级低于
setter
注入与构造器注入,同时出现时自动装配配置失效
(4)集合的注入
public class BookDaoImpl implements BookDao {
private int[] array;
private List<String> list;
private Set<String> set;
private Map<String, String> map;
private Properties properties;
public void setArray(int[] array) {
this.array = array;
}
public void setList(List<String> list) {
this.list = list;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
<bean id="bookDao" class="com.dao.impl.BookDaoImpl">
<property name="array">
<array>
<value>100</value>
<value>200</value>
</array>
</property>
<property name="list">
<list>
<value>java se</value>
<value>java ee</value>
</list>
</property>
<property name="set">
<set>
<value>java se</value>
<value>java ee</value>
</set>
</property>
<property name="map">
<map>
<entry key="k1" value="v1"/>
<entry key="k2" value="v2"/>
</map>
</property>
<property name="properties">
<props>
<prop key="k1">v1</prop>
<prop key="k2">v2</prop>
</props>
</property>
</bean>