1、如何控制bean对象创建次数
1.1、为什么要控制bean对象的创建次数
我们的程序在运行中,有些bean对象使可以复用的,不用每次都去创建一个新的对象。所以我们需要针对不同的对象,来设置他是否需要每次都创建一个新的对象。
这样做的好处:无疑就是节省资源浪费
- 什么样的对象需要创建一次
比如:dao层的对象,service层的对象。都是只需要创建一个。
- 什么样的对象需要每次创建新的
比如:SqlSession和session会话。因为这些对象无法公用。
1.2、简单对象
我们可以使用<bean scope=''>中scope来进行区分,如果里面的值是singleton说明只创建一次。如果值是protopyte说明每次调用都会创建一个新的对象。
这里提前提一下:创建一次的对象会在工厂对象创建的时候将对象创建出来,创建多次的时候是在获取对象的时候才会创建。
<bean name="user" scope="singleton" class="com.wx.study.User"></bean> <bean name="user1" scope="singleton" class="com.wx.study.User"></bean>
public class User { private int id; private String name; public User() { System.out.println("user.user"); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
1.3、复杂对象
上一章我们提到过,对于复杂对象,我们在实现FactoryBean对象中的isSingleton()方法的时候,返回true为创建一次,返回false说明创建多次。
如果是静态工厂或者实例工厂,那么也是通过<bean>对象中的scope这个属性来进行控制。
2、bean对象的生命周期
2.1、什么是对象的生命周期
对象的生命周期就是对象的创建到销毁的整个过程
2.2、知道对象的生命周期的好处
在没有spring之前,我们都是通过new的方式来创建对象的,如果这个对象一直被引用,会一直保存在内存中;而使用了spring后,我们将对象的整个生命周期都交给spring的管理。
既然交给了spring来进行管理,我们只有理解了spring到底是怎样帮助我们创建对象的,才能更好的驾驭spring。
3、bean对象的创建
spring在帮助我们创建对象的时候,会判断我们的scope是否是单利(只创建一次)
- 如果singleton只创建一次,在我们new ClassPathXmlApplicationContext()的时候就会帮助我们创建(如果不配置,默认是单利)
- 如果protopyte创建多次,那么在我们调用getBean()的时候才会创建
<bean name="account" scope="singleton" class="com.wx.study.Account"></bean>
public class Account { private Integer id; private String name; public Account() { System.out.println("Account.Account"); } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
那么如果我们将配置文件scope='protopyte',那么我们只有在geBean()获取这个对象的时候才会创建,在创建ApplicationContext工厂的时候不会创建
<bean name="account" scope="prototype" class="com.wx.study.Account"></bean>
![]()
那么问题来了,如果我们是单例,但是我不想spring在创建容器的时候就将我们的bean对象初始化好,那怎么办呢?
这个时候我们可以使用lazy-init='true'来操作,这个代表懒加载。
<bean name="account" scope="singleton" lazy-init="true" class="com.wx.study.Account"></bean>
设置了这个参数后,我们就可以在单例中scope='singleton'中还可以在调用getBean()方法的时候才去创建对象。
4、bean对象的初始化
4.1、什么是初始化
初始化就是spring创建对象后,调用我们的初始化方法,将数据初始化。
- 初始化方法需要我们程序员自己编写,在创建好对象后,将对象初始化
- 初始化方法是spring调用
spring对初始化,给我们提供了两种方式:
- 实现InitializingBean接口,实现afterPropertiesSet()方法
- 在配置文件<bean>标签中添加init-method属性
4.2、实现InitializingBean接口
public class Account implements InitializingBean { //省略get/set方法 private Integer id; private String name; /** * 做一些初始化操作,spring来进行调用 * @throws Exception */ @Override public void afterPropertiesSet() throws Exception { System.out.println("我初始化啦"); } }
从图中输出的现象,我们不难看出。我们在创建Spring的工厂对象的时候,他会先调用我们的空参构造方法,所以先输出Account.Account,然后Spring在调用我们写的初始化方法afterPropertiesSet(),也就会输出图中的 我初始化啦 。
4.3、bean标签中init-method属性
有些小伙伴会想,我们都已经有InitializingBean接口,为什么还需要bean标签中的init-method呢?这是因为,如果有一天我们不用spring了,而那些代码耦合了spring这个接口,放在配置文件中,便于扩展。
<bean name="account" scope="singleton" lazy-init="true" init-method="myInit" class="com.wx.study.Account"></bean>
public class Account { //省略get/set方法 private Integer id; private String name; public void myInit(){ System.out.println("Account.myInit"); } }
由于这个和上面的借口效果一样,这里就不过多的赘述。
如果我们同时实现了接口,又在bean标签中配置了init-method属性,那么我们的执行效果是怎么样的呢?
我们可以发现,实现的方法会在之前执行,执行完后,才会执行我们的myInit()方法。
那我们在深入一点,如果在加上bean对象的注入,那我们执行的顺序又会是怎样呢?
<bean name="account" scope="singleton" lazy-init="true" init-method="myInit" class="com.wx.study.Account"> <property name="id" value="18"></property> </bean>
public class Account implements InitializingBean { //省略get/set方法 private Integer id; private String name; public void setId(Integer id) { System.out.println("Account.setId"); this.id = id; } /** * 做一些初始化操作,spring来进行调用 * @throws Exception */ @Override public void afterPropertiesSet() throws Exception { System.out.println("我初始化啦"); } public void myInit(){ System.out.println("Account.myInit"); } }
结果很明显,我们注入在我们的初始化之前,其实afterPropertiesSet()翻译过来就是配置文件set方法之后。创建对象->注入属性->初始化
我们在对资源初始化,IO初始化的时候,才会用到这个初始化操作。(后面使用的想对较少)
5、bean对象的销毁
5.1、什么时候会调用对象销毁
ctx.close();意味着工厂关闭
销毁方式也是由程序员来编写,由spring负责调用。spring提供的销毁方法和初始化很相似
5.2、实现DisposableBean接口
实现DisposableBean接口,实现它的destory()方法
public class Account implements InitializingBean, DisposableBean { private Integer id; private String name; public Account() { System.out.println("Account.Account"); } public void setId(Integer id) { System.out.println("Account.setId"); this.id = id; } /** * 做一些初始化操作,spring来进行调用 * @throws Exception */ @Override public void afterPropertiesSet() throws Exception { System.out.println("我初始化啦"); } public void myInit(){ System.out.println("Account.myInit"); } @Override public void destroy() throws Exception { System.out.println("Account.destory"); } }
5.3、定义普通方法
<bean name="account" scope="singleton" lazy-init="true" init-method="myInit" destroy-method="myDestory" class="com.wx.study.Account"> <property name="id" value="18"></property> </bean>
public class Account implements InitializingBean, DisposableBean { private Integer id; private String name; public Account() { System.out.println("Account.Account"); } public void setId(Integer id) { System.out.println("Account.setId"); this.id = id; } /** * 做一些初始化操作,spring来进行调用 * @throws Exception */ @Override public void afterPropertiesSet() throws Exception { System.out.println("我初始化啦"); } public void myInit(){ System.out.println("Account.myInit"); } @Override public void destroy() throws Exception { System.out.println("Account.destory"); } public void myDestory(){ System.out.println("Account.myDestory"); } }
5.4、分析总结
我们的销毁方法只针对于scope='singleton'才有效果,因为scop='propertypr'是每次调用都会生成一个新的对象,所以spring并不知道你是要销毁哪一个对象,所以就不帮你销毁了。
spring的生命周期可以总结为下面的这张图:
- 首先spring工厂被创建,会检查bean对象是否是单例,如果是单例,会去调用bean对象的空参构造方法来创建对象。如果不是单例,那么会在getBean()方法调用的时候创建
- 然后会进行属性注入
- 然后会检查bean对象是否继承了InitializingBean接口,如果实现了,会调用afterPerpotiesSe()方法进行初始化
- 如果bean标签上加了init-method属性,那么会调用自定义初始化方法
- 只有调用了ctx.close()方法,会检查bean对象是否实现了DisposableBean接口,如果实现了,会调用distory()方法进行销毁bean对象
- 如果bean标签上加了destory-method属性,那么会调用自定义销毁方法
6、配置文件参数化
6.1、什么是配置文件参数化
指的是spring配置文件中需要经常修改的字符串信息,抽取到一个单独的配置文件中
这么做的好处是,随着applicationContext的这个配置文件内容越来越多,我们可以将一些参数抽取出来,方便操作管理。
src\main\resources\user.properties配置
name=zhangsan id=18
<context:property-placeholder location="classpath:/user.properties"></context:property-placeholder> <bean name="account" scope="singleton" lazy-init="true" init-method="myInit" destroy-method="myDestory" class="com.wx.study.Account"> <property name="id" value="${id}"></property> </bean>
7、类型转换
现在我们有一个Date类型的属性,我们想要通过spring注入属性,我们来看看应该如何操作
public class Account implements InitializingBean, DisposableBean { //省略get/set方法 private Integer id; private String name; private Date date; @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", date=" + date + '}'; } }
<bean name="account" scope="singleton" lazy-init="true" init-method="myInit" destroy-method="myDestory" class="com.wx.study.Account"> <property name="id" value="${id}"></property> <property name="date" value="1997-12-07"></property> </bean>
但是我们属性注入的时候失败了,程序抛出了异常,这是因为spring无法将String帮助我们转换成Date类型,这个就需要我们自己来操作。这个时候就需要我们自己来写自定义类型转换器
编写自定义转换器
public class MyDateConverter implements Converter<String, Date> { @Override public Date convert(String source) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date date = null; try { date = sdf.parse(source); } catch (ParseException e) { e.printStackTrace(); } return date; } }
<bean name="myDateConverter" class="com.wx.study.MyDateConverter"></bean> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="myDateConverter"></ref> </set> </property> </bean>
这里有几个细节需要注意:这个ConversionServiceFactoryBean这个bean标签的id必须写成conversionService。其次,这个converters属性必须是set标签,因为他是set集合。
spring中内置的日期格式是1997/12/07,不支持1997-12-07
8、后置对象
如果我们想要批量对spring创建的对象进行处理,我们可以设置一个后置对象。
spring为我们在调用初始化方法之前和销毁方法之前都提供了处理bean对象的借口。我们只需要实现接口即可
代码实现
public class MyBeanPostProcessor implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof Account) { System.out.println(beanName); System.out.println("MyBeanPostProcessor.postProcessBeforeInitialization"); } return bean; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof Account) { System.out.println(beanName); System.out.println("MyBeanPostProcessor.postProcessAfterInitialization"); } return bean; } }
<bean id="myBeanPostProcessor" class="com.wx.study.MyBeanPostProcessor"></bean>
注意:
- 因为这个方法会对所有的bean对象进行增强,所以我们需要用instanceof来判断对象,对指定的对象进行处理
- 如果在接口中,一定要返回bean,不然程序就传递不下去了