Spring基础知识(一):Bean的作用域和生命周期

一、Spring容器

在Spring中,Bean的是一个非常广义的概念,任何的Java对象、Java组件都被当成Bean处理。 Spring容器负责创建Bean实例,所以需要知道每个Bean的实现类,Java程序面向接口编程,无须关心Bean实例的实现类;但是Spring容器必须能够精确知道每个Bean实例的实现类,因此Spring配置文件必须精确配置Bean实例的实现类。

1、BeanFactory与ApplicationContext

Spring的容器有很多,可以归为两个不同的类型

  • 一类是实现BeanFactory接口的简单容器系列,这系列容器只实现了容器的最基本功能
  • 另一类是ApplicationContext应用上下文是BeanFactory的子接口,它作为容器的高级形态而存在。

BeanFactory

BeanFactory接口包含以下几个基本方法:

  • Boolean containBean(String name):判断Spring容器是否包含id为name的Bean实例。
  • getBean(Class requiredType):获取Spring容器中属于requiredType类型的唯一的Bean实例。
  • Object getBean(String name):返回Sprin容器中id为name的Bean实例。
  • T getBean(String name,Class requiredType):返回容器中id为name,并且类型为requiredType的Bean
  • Class <?> getType(String name):返回容器中指定Bean实例的类型。

这个容器基本很少使用,基本都是使用更高级的ApplicationContext容器。

ApplicationContext

ApplicationContext作为BeanFactory的子接口,使用它作为Spring容器会更加方便,它主要有如下几个实现类:

  • FileSystemXmlApplicationContext:以基于文件系统的XML配置文件创建ApplicationContext实例。
  • ClassPathXmlApplicationContext:以类加载路径下的XML配置文件创建的ApplicationContext实例。
  • AnnotationConfigApplicationContext:以基于Java注解为配置创建的ApplicationContext实例。

创建Spring容器实例时,必须提供Spring容器管理的Bean的详细配置信息。Spring的配置信息通常采用xml配置文件来设置,因此,创建BeanFactory实例时,应该提供XML配置文件作为参数。

XML配置文件通常使用Resource对象传入。Resource接口是Spring提供的资源访问接口,通过使用该接口,Spring能够以简单、透明的方式访问磁盘、类路径以及网络上的资源。

1
2
3
ApplicationContext ctx=new ClassPathXmlApplicationContext("bean.xml");  
ApplicationContext ctx1=new FileSystemXmlApplicationContext("c:/bean.xml");
ApplicationContext ctx2=new AnnotationConfigApplicationContext(ApplicationContextConfig.class);

二、Bean的生命周期

1、接口的分类

Bean的完整生命周期经历了各种方法调用,这些方法可以划分为以下几类:

  1. Bean自身的方法:这个包括了Bean本身调用的方法和通过配置文件中<bean>

    • init-method
    • destroy-method指定的方法
  2. Bean级生命周期接口方法

    • BeanNameAware

    • BeanFactoryAware

    • InitializingBean

    • DiposableBean

      这些接口的方法

  3. 容器级生命周期接口方法

    • InstantiationAwareBeanPostProcessor

    • BeanPostProcessor

      这两个接口,一般称它们的实现类为“后处理器”。

2、启动步骤

  1. Bean的建立

    容器寻找Bean的定义信息并将其实例化。看过源码都知道,容器首先将Bean的定义信息以BeanDefinition的形式存储,等第一次getBean的时候再将其实例化并完成下面的依赖注入过程。

  2. 属性注入

    使用依赖注入,Spring将值和bean的引用注入到bean对应的属性中。

  3. BeanNameAware的setBeanName():

    如果bean实现了org.springframework.beans.BeanNameAware接口,Spring将bean的ID传递给setBeanName()方法。

  4. BeanFactoryAware的setBeanFactory():

    如果bean实现了org.springframework.beans.factory.BeanFactoryAware接口,Spring将调用setBeanName()方法,将BeanFactory容器实例传入。

  5. BeanPostProcessors的ProcessBeforeInitialization()

    如果有org.springframework.beans.factory.config.BeanPostProcessors和Bean关联,那么其postProcessBeforeInitialization()方法将被将被调用。

  6. InitializingBean的afterPropertiesSet()

    如果Bean类已实现org.springframework.beans.factory.InitializingBean接口,则执行他的afterProPertiesSet()方法。

  7. Bean定义文件中定义init-method:

    如果bean使用init-method生命了初始化方法,该方法也会被调用

  8. BeanPostProcessors的ProcessaAfterInitialization()

    如果有任何的BeanPostProcessors实例与Bean实例关联,则执行BeanPostProcessors实例的ProcessaAfterInitialization()方法

  9. 就绪

    此时,Bean已准备就绪,可以被应用程序使用了,它们将一直驻留在容器中,直到该容器被销毁。

  10. DisposableBean的destroy()

    在容器关闭时,如果Bean类有实现org.springframework.beans.factory.DisposableBean接口,Spring将调用他的destroy()方法。

  11. Bean定义文件中定义destroy-method

    如果bean使用destroy-method生命了初始化方法,该方法也会被调用

3、总结

这个生命周期是在是太复杂了,需要很长时间才能理清他们之间的逻辑关系。

BeanNameAware、ApplicationContextAware和BeanFactoryAware

这三个接口放在一起写,是因为它们是一组的,作用相似。

“Aware”的意思是”感知到的”,那么这三个接口的意思也不难理解:

  1. 实现BeanNameAware接口的Bean,在Bean加载的过程中可以获取到该Bean的id
  2. 实现ApplicationContextAware接口的Bean,在Bean加载的过程中可以获取到Spring的ApplicationContext,这个尤其重要,ApplicationContext是Spring应用上下文,从ApplicationContext中可以获取包括任意的Bean在内的大量Spring容器内容和信息
  3. 实现BeanFactoryAware接口的Bean,在Bean加载的过程中可以获取到加载该Bean的BeanFactory

从上面的调用顺序可以看出,在bean实例化并设置属性之后,紧接着就是先调用这三个接口相关的方法。

InitialingBean和DisposableBean

InitialingBean是一个接口,提供了一个唯一的方法afterPropertiesSet()。

DisposableBean也是一个接口,提供了一个唯一的方法destory()。

这两个接口是一组的,功能类似,因此放在一起:前者顾名思义在Bean属性都设置完毕后调用afterPropertiesSet()方法做一些初始化的工作,后者在Bean生命周期结束前调用destory()方法做一些收尾工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class LifecycleBean implements InitializingBean, DisposableBean {
@SuppressWarnings("unused")
private String lifeCycleBeanName;

public void setLifeCycleBeanName(String lifeCycleBeanName) {
System.out.println("Enter LifecycleBean.setLifeCycleBeanName(), lifeCycleBeanName = " + lifeCycleBeanName);
this.lifeCycleBeanName = lifeCycleBeanName;
}

public void destroy() throws Exception {
System.out.println("Enter LifecycleBean.destroy()");
}

public void afterPropertiesSet() throws Exception {
System.out.println("Enter LifecycleBean.afterPropertiesSet()");
}

public void beanStart() {
System.out.println("Enter LifecycleBean.beanStart()");
}

public void beanEnd() {
System.out.println("Enter LifecycleBean.beanEnd()");
}
}
1
2
3
4
5
Enter LifecycleBean.setLifeCycleBeanName(), lifeCycleBeanName = lifeCycleBean
Enter LifecycleBean.afterPropertiesSet()
Enter LifecycleBean.beanStart()
Enter LifecycleBean.destroy()
Enter LifecycleBean.beanEnd()

执行结果和我们想的一样,afterPropertiesSet()方法就如同它的名字所表示的那样,是在Bean的属性都被设置完毕之后,才会调用

  1. InitializingBean接口、Disposable接口可以和init-method、destory-method配合使用,接口执行顺序优先于配置
  2. InitializingBean接口、Disposable接口底层使用类型强转.方法名()进行直接方法调用,init-method、destory-method底层使用反射
  3. 一但bean实现了InitializingBean接口,那么这个bean的代码就和Spring耦合到一起了,因此不鼓励使用接口的形式。

BeanPostProcessor

之前的InitializingBean、DisposableBean、FactoryBean包括init-method和destory-method,针对的都是某个Bean控制其初始化的操作,而似乎没有一种办法可以针对每个Bean的生成前后做一些逻辑操作,PostProcessor则帮助我们做到了这一点

BeanPostProcess接口有两个方法,都可以见名知意:

  1. postProcessBeforeInitialization:在初始化Bean之前
  2. postProcessAfterInitialization:在初始化Bean之后

值得注意的是,这两个方法是有返回值的,不要返回null,否则getBean的时候拿不到对象

写一段测试代码,首先定义一个普通的Bean,为了后面能区分,给Bean加一个属性:

1
2
3
4
5
6
7
8
9
10
11
public class CommonBean {
private String commonName;

public void setCommonName(String commonName) {
this.commonName = commonName;
}

public void initMethod() {
System.out.println("Enter CommonBean.initMethod(), commonName = " + commonName);
}
}

定义一个PostProcess,实现BeanPostProcess接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author 五月的仓颉 http://www.cnblogs.com/xrq730/p/5721366.html
*/
public class PostProcessorBean implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Enter ProcessorBean.postProcessAfterInitialization()\n");
return bean;
}

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Enter ProcessorBean.postProcessBeforeInitialization()");
return bean;
}
}

配置一个spring.xml,给CommonBean的commonName赋予不同的值以区分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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-4.1.xsd">

<bean id="common0" class="org.xrq.bean.common.CommonBean" init-method="initMethod">
<property name="commonName" value="common0"/>
</bean>

<bean id="common1" class="org.xrq.bean.common.CommonBean" init-method="initMethod">
<property name="commonName" value="common1"/>
</bean>

<bean id="postProcessorBean" class="org.xrq.bean.processor.PostProcessorBean" />
</beans>

运行一个Spring容器, 初始化结果为:

1
2
3
4
5
6
7
8
9
10
Enter ProcessorBean.postProcessBeforeInitialization()
Enter CommonBean.initMethod(), commonName = common0
Enter ProcessorBean.postProcessAfterInitialization()

Enter ProcessorBean.postProcessBeforeInitialization()
Enter CommonBean.initMethod(), commonName = common1
Enter ProcessorBean.postProcessAfterInitialization()

Enter ProcessorBean.postProcessBeforeInitialization()
Enter ProcessorBean.postProcessAfterInitialization()

看到每个Bean初始化前后都会分别执行postProcessorBeforeInitiallization()方法与postProcessorAfterInitialization()方法,最后两行出现原因是,PostProcessorBean本身也是一个Bean。

注意:

  1. 这两个方法分别叫before和after,而中间的Initialization过程就是上面说到的各个bean实现的InitializingBean接口和inti-method方法。

  2. BeanFactory和ApplicationContext对待bean后置处理器稍有不同。ApplicationContext会自动检测在配置文件中实现了BeanPostProcessor接口的所有bean,并把它们注册为后置处理器,然后在容器创建bean的适当时候调用它,因此部署一个后置处理器同部署其他的bean并没有什么区别。而使用BeanFactory实现的时候,bean 后置处理器必须通过代码显式地去注册。

4、再次总结一下

现在bean的生命周期各个阶段应该就很清晰了,上面的步骤对应一下几个大的阶段

实例化与属性

对应步骤1和2

Aware相关的三个方法

BeanNameAware、ApplicationContextAware和BeanFactoryAware三个接口的方法,对应上面的步骤3和4,应该还有一个的里面没有写上

BeanPostProcessor后置处理器

这个接口是容器级别的,对所有的bean都起作用,也就是说每个bean初始化的时候都会检测实现这个接口的bean。

而这个接口对应两个方法,postProcessBeforeInitialization和postProcessAfterInitialization,分别对应上面的步骤5和8,因为一个是before一个是after,而中间的步骤6和7就是下面的各个bean自己实现的接口或者方法

InitializingBean接口和init-method

InitializingBean接口可以和init-method配合使用,接口执行顺序优先于配置,也就是上面的步骤6和步骤7他们的区别上面已经介绍过。

准备就绪

至此bean已经可以使用,可以看出大体分为上面的四个阶段

销毁

销毁阶段相对简单,因为没有上面的后置处理器,就是Disposable接口的方法和destory-method,两者的顺序和左右与InitializingBean接口和init-method都很类似,对应上面的步骤10和步骤11.

三、Bean的作用域

当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:

  • singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
  • prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例
  • request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效
  • session:对于每次HTTP Session,使用session定义的Bean豆浆产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效
  • globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效

测试

1、定义不同作用域的java类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component  
@Scope( "session")
public class SessionObj {

}
@Component
@Scope( "request")
public class RequestObj {

}
@Component
@Scope( "prototype")
public class PrototypeObj {

}
@Component
@Scope( "singleton")
public class SingletonObj {

}

2、注入到controller

由于controller是单例的,因此必须通过实现ApplicationContextAware接口,直接从容器中取出对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@Controller  
public class IndexController implements ApplicationContextAware {

private RequestObj RequestObj;

private SessionObj SessionObj;

private PrototypeObj PrototypeObj;

private SingletonObj SingletonObj;

private ApplicationContext applicationContext;

@RequestMapping("/")
@ResponseBody
public String index() {
print();
return "Welcome";
}

public void print() {
System.out.println("first time singleton is :" + getSingletonObj());
System.out.println("second time singleton is :" + getSingletonObj());

System.out.println("first time prototype is :" + getPrototypeObj());
System.out.println("second time prototype is :" + getPrototypeObj());

System.out.println("first time request is :" + getRequestObj());
System.out.println("second time request is :" + getRequestObj());

System.out.println("first time session is :" + getSessionObj());
System.out.println("second time session is :" + getSessionObj());
}

@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}

public RequestObj getRequestObj() {
return applicationContext.getBean(RequestObj.class);
}

public void setRequestObj(RequestObj requestObj) {
RequestObj = requestObj;
}

public SessionObj getSessionObj() {
return applicationContext.getBean(SessionObj.class);
}

public void setSessionObj(SessionObj sessionObj) {
SessionObj = sessionObj;
}

public PrototypeObj getPrototypeObj() {
return applicationContext.getBean(PrototypeObj.class);
}

public void setPrototypeObj(PrototypeObj prototypeObj) {
PrototypeObj = prototypeObj;
}

public SingletonObj getSingletonObj() {
return applicationContext.getBean(SingletonObj.class);
}

public void setSingletonObj(SingletonObj singletonObj) {
SingletonObj = singletonObj;
}

}

3、运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//使用chrome第一次打印数据:  
first time singleton is :com.fb.po.SingletonObj@1e3223e
second time singleton is :com.fb.po.SingletonObj@1e3223e
first time prototype is :com.fb.po.PrototypeObj@3e683f
second time prototype is :com.fb.po.PrototypeObj@12e18d7
first time request is :com.fb.po.RequestObj@1d45706
second time request is :com.fb.po.RequestObj@1d45706
first time session is :com.fb.po.SessionObj@9a6b2e
second time session is :com.fb.po.SessionObj@9a6b2e



//使用chrome打印第二次数据
first time singleton is :com.fb.po.SingletonObj@1e3223e
second time singleton is :com.fb.po.SingletonObj@1e3223e
first time prototype is :com.fb.po.PrototypeObj@122e5be
second time prototype is :com.fb.po.PrototypeObj@192add
first time request is :com.fb.po.RequestObj@4d1b6c
second time request is :com.fb.po.RequestObj@4d1b6c
first time session is :com.fb.po.SessionObj@9a6b2e
second time session is :com.fb.po.SessionObj@9a6b2e



//使用IE打印第三次数据
first time singleton is :com.fb.po.SingletonObj@1e3223e
second time singleton is :com.fb.po.SingletonObj@1e3223e
first time prototype is :com.fb.po.PrototypeObj@10f1ecb
second time prototype is :com.fb.po.PrototypeObj@1aeb990
first time request is :com.fb.po.RequestObj@18a1e7
second time request is :com.fb.po.RequestObj@18a1e7
first time session is :com.fb.po.SessionObj@12d5c55
second time session is :com.fb.po.SessionObj@12d5c55

4、结果分析

从结果来看,单例的bean的三次的数据都是打印一样的(默认的bean的级别就是单例);

prototype的bean每次的数据都是不一样的,每次请求的时候调用两次结果都不一样。

request的bean在每次request的时候都不一致,但是同一次request返回的数据是一致的。

session的bean在前两次结果一致,最后一次数据不一致,和session的节奏是一致的。

5、总结

如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。

现在开发应用大多数都是无状态的,所以单例多线程是比较好的实现方式,多例的比较有利于保存状态,完全没必要。

四、参考地址

http://gaddma.iteye.com/blog/2037038

http://www.cnblogs.com/zrtqsk/p/3735273.html

http://developer.51cto.com/art/201104/255961.htm

http://www.cnblogs.com/xrq730/p/5721366.html