Spring中bean的详解
这次分享的内容是Spring中bean的相关细节,主要从三个方面入手:
- 创建bean的三种方式
- bean对象的作用范围
- bean对象的生命周期
创建Bean的三种方式
- 使用默认构造的方式
在spring的配置文件中使用bean标签,配以id和class属性之后,并且没有其他属性和标签时采用的就是这种方式
此时如果类中没有默认构造则对象没法创建
<bean id="accountService" class="org.zjb.service.impl.AccountServiceImpl"></bean>
- 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
为什么要有这种方式呢?
试想一下,有一种这样的情况,有个工厂类是我们导入的jar包,这个jar包的内容我们肯定是不能修改的,我们想要的并不是工厂类,而是通过该工厂类创建出来的方法,于是乎就有了这种方式.
我们可以模拟一下:
/**
* 模拟一个工厂类,该类可能是存在于一个jar包中的
*/
public class InstanceFactory {
public IAccountService getAccountService() {
return new AccountServiceImpl();
}
}
<!--先创建工厂类-->
<bean id="instanceFactory" class="org.zjb.factory.InstanceFactory"></bean>
<!--在指定工厂bean的id和创建对象的方法名-->
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
- 使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
这种方式也比较好理解,和上面的基本一致
/**
* 模拟一个静态工厂类,该类可能是存在于一个jar包中的
*/
public class StaticFactory {
public static IAccountService getAccountService() {
return new AccountServiceImpl();
}
}
<bean id="accountService" class="org.zjb.factory.StaticFactory" factory-method="getAccountService"></bean>
bean对象的作用范围
首先考虑一个问题,我们默认创建的对象是单例模式吗?
这个很好验证:
<!--默认方式创建-->
<bean id="accountService" class="org.zjb.service.impl.AccountServiceImpl"></bean>
/**
* 模拟业务层
*/
public class Client {
/**
* 获取spring的ioc核心容器并根据id获取对象
* @param args
*/
public static void main(String[] args) {
// 1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 2.根据id获取bean对象
IAccountService as1 = (IAccountService) ac.getBean("accountService");
System.out.println(as1);
IAccountService as2 = (IAccountService) ac.getBean("accountService");
System.out.println(as2);
System.out.println("as1 == as2 ? " + (as1 == as2));
}
执行测试代码,得到结果如下:
构造方法只执行了一遍,而且as1和as2对象是同一个对象,这些都说明默认方式创建的对象都是单例的.
能否多例模式创建对象?
可以的,也很简单,只需要将bean标签改写成如下方式即可:
<bean scope="prototype" id="accountService" class="org.zjb.service.impl.AccountServiceImpl"></bean>
可以看出这个bean标签和上面相比多了一个scope属性,这个属性指定bean对象的作用范围,有如下取值
- singleton : 单例的,也是默认值
- prototype : 多例的
- request : 作用于web应用的请求范围
- session : 作用于web应用的会话范围
- global-session : 作用于集群环境的会话(全局会话范围),当不是集群环境时,就是session
常用的就单例和多例.
bean对象的生命周期
单例对象
- 出生 : 当容器创建时出生(立即)
- 活着 : 只要容器还在,对象一直存活
- 死亡 : 容器销毁,对象死亡
- 总结 : 单例对象的生命周期和容器相同
现在我们来验证一下:
xml文件创建bean对象
<bean scope="singleton" id="accountService" class="org.zjb.service.impl.AccountServiceImpl"
init-method="init" destroy-method="destroy"
></bean>
init-method属性是指定bean对象创建时执行的方法,destroy-method属性是指定bean对象销毁时执行的方法.
我们在测试类中使用测试:
public class Client {
/**
* 获取spring的ioc核心容器并根据id获取对象
* @param args
*/
public static void main(String[] args) {
// 1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 2.根据id获取bean对象
IAccountService as1 = (IAccountService) ac.getBean("accountService");
}
程序运行结果如下:
并没有我们想要的结果,这是因为我们这里模拟的时候是使用main方法,当main方法执行完毕的时候,我们还没来的及销毁容器,就已经释放了程序的内存空间,所以这里的结果是这样的.
为了验证效果,我们需要主动释放容器对象,将测试代码改写:
public class Client {
/**
* 获取spring的ioc核心容器并根据id获取对象
* @param args
*/
public static void main(String[] args) {
// 1.获取核心容器对象
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 2.根据id获取bean对象
IAccountService as1 = (IAccountService) ac.getBean("accountService");
ac.close();
}
}
观察上面代码,我们发现在创建容器对象的时候我们使用的是引用类型是实现类的,即ClassPathXmlApplicationContext,这是因为close方法在它的父接口ApplicationContext中并不存在.
多例对象
我们将bean对象改成多例的,继续测试:
<bean scope="prototype" id="accountService" class="org.zjb.service.impl.AccountServiceImpl"
init-method="init" destroy-method="destroy"
></bean>
测试代码依旧使用上面的
得到结果为:
可见随着容器对象的销毁,多例bean并没有被销毁
多例对象的生命周期如下 :
- 出生 : 使用对象时才创建(延迟)
- 活着 : 对象只要在使用过程中就一直活着
- 死亡 : 当对象长时间不使用,且没有别的对象引用时,由java的垃圾回收机制回收