豆知识当然不是什么用不到的小冷门知识了,而是关于Bean的内容。在初步了解了通过XML配置Bean以及依赖注入之后,我很容易想到,原来的Web层,Service层和Dao层的三个对象,都可以配置到Spring中成为一个Bean,很显然Bean是一个类经过Spring组装之后得到的对象,但是Bean还有什么奥秘,来看看吧。

Bean Scopes

看到Scope感觉就是作用域了,实际上这里找到了一个解释如下:

《Spring揭秘》上对scope的解释“scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其相应的scope之前生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象”

教学视频里则讲了Bean Scopes包含的几个内容:

  1. scope实际上指的是bean的生命周期
  2. bean生存多久?
  3. bean创建多少实例?
  4. bean如何在Spring中共享?

所以这里就把Bean Scope认为是生命周期就可以了。这里首先就碰到一个问题,就是创建多少实例。

Singleton 与 Prototype

Spring默认对于每个Bean都是采取单例模式Singleton,将这个bean缓存在内存中,所有对一个bean的访问都指向同一个bean(同一个内存地址)。

通过修改配置文件中bean的scope属性,可以设置其为一些不同的值。默认为singleton,还可以设置成为prototype,此时相当于new对象,每次访问这个bean,都会得到不同的对象。

现在还有request, session 和 global session,都是应用于Web项目的,到Web阶段再来看。

为了测试一下这两种配置,我们来修改一下之前使用到的代码。

由于我们完全不需要变更具体的POJO的代码,所以只需要新创建一个XML配置文件,然后新创建一个用于测试的应用即可。Spring的XML配置文件可以叫任意名字,只要内容符合Spring的要求即可。

将原来的applicationContext.xml复制一份改个名字放在相同的目录下边,比如叫beanScope.xml,然后设置两个bean分别为scope=”singleton”和scope=”prototype”:

<bean id="myCoach" class="iocdemo1.BaseballCoach" scope="singleton">
        <constructor-arg ref="myFortuneService"/>
    </bean>
<bean id="myFortuneService" class="iocdemo1.HappyFortuneService" scope="prototype"></bean>

然后把MyApp.java也复制一份,就叫BeanMyApp.java好了,使用新的XML配置文件,但是代码修改为测试两次取同一个Bean是不是相同的一个:

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BeanMyApp {
    public static void main(String[] args) {
        //创建容器
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beanScope.xml");

        //先后获取两个被配置为单例的Bean
        Coach coach1 = context.getBean("myCoach", Coach.class);
        Coach coach2 = context.getBean("myCoach", Coach.class);

        //检测是否相同
        System.out.print("两个Bean是否相同: ");
        System.out.println(coach1==coach2);
        System.out.println("第一次获取的Bean是: " + coach1);
        System.out.println("第二次获取的Bean是: " + coach2);

        //先后获取两个被配置为prototype的Bean
        FortuneService fortuneService1 = context.getBean("myFortuneService", HappyFortuneService.class);
        FortuneService fortuneService2 = context.getBean("myFortuneService", HappyFortuneService.class);

        //检测是否相同
        System.out.print("两个Bean是否相同: ");
        System.out.println(fortuneService1==fortuneService2);
        System.out.println("第一次获取的Bean是: " + fortuneService1);
        System.out.println("第二次获取的Bean是: " + fortuneService2);

        //关闭容器
        context.close();
    }
}

运行结果如下:

两个Bean是否相同: true
第一次获取的Bean是: iocdemo1.BaseballCoach@59e5ddf
第二次获取的Bean是: iocdemo1.BaseballCoach@59e5ddf
两个Bean是否相同: false
第一次获取的Bean是: iocdemo1.HappyFortuneService@536aaa8d
第二次获取的Bean是: iocdemo1.HappyFortuneService@e320068

可见默认是单例模式,这也和我一开始自己思考的是一致的。如果将Web,Service和Dao装配,应该不会每次都全部new一个新的,太浪费资源了,而且Web应用一个就足够了,只是每次处理的request和response不同。当然,以后如果是分布式也许就有多个Web应用同时工作了。

Bean的生命周期

首先看整体的过程:

  1. Spring容器启动
  2. 初始化Bean
  3. 依赖注入
  4. Spring内部装配Bean
  5. 调用自定义方法–这里指的是init-method配置的方法,不是构造方法
  6. Bean可以使用
  7. Spring容器关闭
  8. 调用自定义的结束前方法–destroy-method

其中依赖注入和装配控制通过XML生效,在第二项也就是Bean初始化的过程和最后destroy的过程中,我们可以自行编写一些代码。

Spring给运行自行编写的代码提供了一些钩子和接口,比如可以在bean标签内的init-method和destroy-method内指定要执行的方法名称。

现在再来写个代码测试一下:

继续沿用上边的例子,这次我们修改一下TrackCoach的代码,创建两个新方法,并在两个新方法和构造方法内都加上输出语句:

public class TrackCoach implements Coach {

    private FortuneService fortuneService;

    public TrackCoach(FortuneService fortuneService) {
        this.fortuneService = fortuneService;
    }

    //需要一个无参构造器
    public TrackCoach() {
    }

    @Override
    public String getDailyWorkout() {
        return "I am TrackCoach";
    }

    @Override
    public String getDailyFortune() {
        return "I am TrackCoach " + fortuneService.getFortune();
    }

    public void doMyStartupStuff() {
        System.out.println("TrackCoach: inside init-method");
    }

    public void doMyCleanupStuff() {
        System.out.println("TrackCoach: inside destroy-method");
    }
}

然后在XML文件中配置一下TrackCoach的Bean:

<bean id="myTrackCoach" class="iocdemo1.TrackCoach" scope="singleton"></bean>

什么代码都不用动,重新运行一下BeanMyApp.java,可以发现自动运行了两个方法。

这里如果在构造方法里加上输出,可以看到构造方法还执行在init-method之前,这个估计就要更深入的了解初始化Bean的过程了。