Spring基础-创建次数生命周期

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的生命周期可以总结为下面的这张图:

  1. 首先spring工厂被创建,会检查bean对象是否是单例,如果是单例,会去调用bean对象的空参构造方法来创建对象。如果不是单例,那么会在getBean()方法调用的时候创建
  2. 然后会进行属性注入
  3. 然后会检查bean对象是否继承了InitializingBean接口,如果实现了,会调用afterPerpotiesSe()方法进行初始化
  4. 如果bean标签上加了init-method属性,那么会调用自定义初始化方法
  5. 只有调用了ctx.close()方法,会检查bean对象是否实现了DisposableBean接口,如果实现了,会调用distory()方法进行销毁bean对象
  6. 如果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,不然程序就传递不下去了
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值