众所周知,Spring中的bean由Spring容器负责管理,包括对象的整个生命周期:创建、装配、销毁。
具体的生命周期通过下图可以描述:
说明如下:
当然,以上的这些接口只是bean可能实现的接口,一般不会同时都实现。
下面通过一个简单的例子来验证。
Bean:
public class LifeBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
private String name;
public LifeBean(){
System.out.println("执行构造函数");
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("setName()");
this.name = name;
}
public void init(){
System.out.println("执行init方法");
}
public void del() {
System.out.println("执行del方法");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("执行setBeanFactory方法");
}
@Override
public void setBeanName(String s) {
System.out.println("执行setBeanName方法");
}
@Override
public void destroy() throws Exception {
System.out.println("执行destory方法");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("执行afterProperties方法");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("执行setApplicationContext方法");
}
}
BeanPostProcessor子类:
public class MyBeanPostProcessor implements BeanPostProcessor {
public MyBeanPostProcessor() {
super();
System.out.println("执行MyBeanPostProcessor构造方法");
}
@Override
public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
System.out.println("执行postProcessBeforeInitialization方法");
return o;
}
@Override
public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
System.out.println("postProcessAfterInitialization方法");
return o;
}
}
在xml文件中配置:
测试:
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("/spring-web.xml");
LifeBean lifeBean = (LifeBean) applicationContext.getBean("life");
System.out.println(lifeBean);
((ClassPathXmlApplicationContext)applicationContext).registerShutdownHook();
}
输出如下:
执行MyBeanPostProcessor构造方法
执行构造函数(创建bean)
执行setBeanName方法(BeanNameAware接口)
执行setBeanFactory方法(BeanFactoryAware接口)
执行setApplicationContext方法(ApplicationContextAware接口)
执行postProcessBeforeInitialization方法(BeanPostProcessor接口)
执行afterProperties方法(InitializingBean接口)
执行init方法((init-method定义的初始化方法))
postProcessAfterInitialization方法(BeanPostProcessor接口)
howetong.cn.test.LifeBean@64bfbc86
执行destory方法(Disposable接口)
执行del方法(destroy-method定义的销毁方法)
通过输出结果可以看出,执行顺序与上文说明的一致。
需要注意的是,BeanPostProcessor接口的作用域是整个容器,它对所有的bean都生效。容器在创建bean的过程中,会先创建实现了BeanPostProcessor接口的bean,然后在创建其他bean的时候,会将创建的每一个bean作为参数,调用BeanPostProcessor的方法。
上文说,Spring中的Bean由Spring容器负责管理,那么,什么是Spring容器呢?从测试的例子中看到,先创建了一个ApplicationContext实例,然后从该实例中获取了名称为life的bean。也就是说,ApplicationContext可以认为是Spring的容器。继续深入可以发现,ApplicationContext继承了ListableBeanFactory接口,而ListableBeanFactory接口又继承了BeanFactory接口。
事实上,BeanFactory和ApplicationContext是Spring的两大核心接口,其中ApplicationContext是BeanFactory的子接口。它们都可以当做是Spring的容器。
Spring容器不是唯一的,而是一套的,包括根接口BeanFactory和其众多的子类。最主要的是ApplicationContext。BeanFactory是最基本的容器,只提供了基本的DI功能。继承了BeanFactory的ApplicationContext则功能更为全面,能提供更多的服务,如解析配置文本信息,资源国际化等。
Spring Ioc容器的实现,从根源上是BeanFactory。其子类比较多,如下图所示:
ApplicationContext是BeanFactory的一个重要的子类,它也是一个接口。ApplicationContext的常用实现类也比较多,主要包括如下几种:
相较于BeanFactory,ApplicationContext提供了更多额外功能:
对Bean的初始化
BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。而ApplicationContext是在容器启动时,一次性初始化所有bean。
下面通过代码进行验证。以上文中的代码为例,创建ApplicationContext容器时,即
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-web.xml");
这一行就完成了bean的初始化工作。说明ApplicationContext确实是在容器启动时完成bean的初始化的。
换成创建BeanFactory容器:
public static void main(String[] args) {
Resource resource=new ClassPathResource("spring-web.xml");
BeanFactory beanFactory = new DefaultListableBeanFactory();
BeanDefinitionReader bdr = new XmlBeanDefinitionReader((BeanDefinitionRegistry) beanFactory);
bdr.loadBeanDefinitions(resource);
// 第一次调用时,才对bean进行初始化
// LifeBean lifeBean = (LifeBean) beanFactory.getBean("life");
// System.out.println(lifeBean);
}
启动容器,没有bean的初始化信息。通过getBean方法访问life,输出如下:
执行构造函数(创建bean)
执行setBeanName方法(BeanNameAware接口)
执行setBeanFactory方法(BeanFactoryAware接口)
执行afterProperties方法(InitializingBean接口)
执行init方法(init-method定义的初始化方法)
howetong.cn.test.LifeBean@783e6358
说明访问时对bean进行了初始化。
比较这两种方式,BeanFactory的启动速度更快,但是如果有配置错误,则启动时不会暴露出来,只有在访问配置出错的bean时才会暴露。而ApplicationContext启动时就会初始化所有bean,一开始就会暴露出错误。同时,由于ApplicationContext已经预加载了所有的bean,在访问的时候不再需要额外的等待。
对事件的支持
ApplicationContext增加了对事件的支持。
ApplicationContext继承了ApplicationEventPublisher接口:
public interface ApplicationEventPublisher {
void publishEvent(ApplicationEvent var1);
void publishEvent(Object var1);
}
可以通过publishEvent方法来发布容器事件。
容器事件需要继承ApplicationEvent类:
public class EmailEvent extends ApplicationEvent {
private String address;
private String text;
public EmailEvent(Object source) {
super(source);
}
public EmailEvent(Object source, String address, String text) {
super(source);
this.address = address;
this.text = text;
}
// get,set方法
...
}
监听器需要实现ApplicationListener接口,并且要注册为一个bean,由Spring进行管理。
public class EmailListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
if (applicationEvent instanceof EmailEvent) {
EmailEvent emailEvent = (EmailEvent) applicationEvent;
System.out.println("邮件地址:" + emailEvent.getAddress());
System.out.println("邮件内容:" + emailEvent.getText());
} else {
// spring内置的其他事件
System.out.println("其他事件");
}
}
}
// 配置为bean
现在事件和监听器都有了,只要让ApplicationContext容器来发布事件消息就可以了:
public static void main(String[] args) {
ApplicationContext acx = new ClassPathXmlApplicationContext("/spring-web.xml");
EmailEvent emailEvent = new EmailEvent("test", "[email protected]", "hello world");
acx.publishEvent(emailEvent);
((AbstractApplicationContext)acx).close();
}
输出如下:
其他事件
邮件地址:[email protected]
邮件内容:hello world
其他事件
这里的”其他事件”是ApplicationContext容器内置的一些事件,如下图所示:
其中,ContextRefreshEvent是容器初始化完成或刷新事件,ContextClosedEvent是容器关闭事件,ContextStartedEvent是容器调用ConfigurableApplicationContext的start方法开始/重新开始容器时的事件,ContextStoppedEvent是调用stop方法停止容器时的事件。5.x版的spring还有个RequestHandledEvent事件,当spring处理完web请求后触发。
在上文代码的基础上做一点修改,监听一下容器刷新事件和关闭事件:
public void onApplicationEvent(ApplicationEvent applicationEvent) {
if (applicationEvent instanceof EmailEvent) {
EmailEvent emailEvent = (EmailEvent) applicationEvent;
System.out.println("邮件地址:" + emailEvent.getAddress());
System.out.println("邮件内容:" + emailEvent.getText());
} else if (applicationEvent instanceof ContextRefreshedEvent) {
System.out.println("容器初始化完成或刷新");
} else if (applicationEvent instanceof ContextClosedEvent){
System.out.println("容器关闭");
} else {
System.out.println("其他事件");
}
}
再来执行main函数,输出如下:
容器初始化完成或刷新
邮件地址:[email protected]
邮件内容:hello world
容器关闭
Spring web项目启动的时候,读取spring配置文件来生成spring容器,也就是生成BeanFactory及其子类相关的实例。
如果要在业务代码中获取spring容器,以便根据id来获取对应的bean,应该如何获取呢?
最简单的方式是直接注入。
@Autowired
ApplicationContext applicationContext;
上文介绍bean的生命周期时说过,如果一个bean实现了BeanFactoryAware接口,则在这个bean实例化的Aware接口检查阶段,会将spring容器BeanFactory通过setBeanFactory方法设置进来。如果bean实现了ApplicationContextAware接口,同样会将spring容器applicationContext设置进来。据此可以想到两种获取spring容器的方式。
获取BeanFactory容器
定义一个bean,实现BeanFactoryAware接口,则这个bean在项目启动后的实例化过程中会将BeanFactory设置进来。
@Component
public class TestFactory implements BeanFactoryAware {
private static BeanFactory beanFactory;
@SuppressWarnings("AccessStaticViaInstance")
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public static BeanFactory getBeanFactory() {
return beanFactory;
}
}
通过TestFactory.getBeanFactory()即可获取spring容器,然后调用getBean(beanId)获取对应的bean。
获取ApplicationContext容器
同样定义一个bean,在spring初始化这个bean的过程中将ApplicationContext对象设置进来。
@Service
public class SpringContext implements ApplicationContextAware {
private static ApplicationContext context;
public static Object getBean(String beanId) {
if (context.containsBean(beanId)) {
return context.getBean(beanId);
}
return null;
}
public static T getBeanByClass(Class requiredClz) {
return context.getBean(requiredClz);
}
/**
* @return the applicationContext
*/
public static ApplicationContext getApplicationContext() {
return context;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContext.context = applicationContext;
}
}
当然,除了这两种方式外,还有其他方式,如继承ApplicationObjectSupport或WebApplicationObjectSupport,这里就不详细介绍了。