Spring Bean的生命周期和Spring容器

Spring Bean的生命周期

众所周知,Spring中的bean由Spring容器负责管理,包括对象的整个生命周期:创建、装配、销毁。
具体的生命周期通过下图可以描述:
在这里插入图片描述
说明如下:

  1. Spring实例化一个bean,默认是单例的。
  2. Spring对bean进行依赖注入。
  3. 如果bean实现了BeanNameAware接口,则将bean的id传给setBeanNameAware方法。
  4. 如果bean实现了BeanFactoryAware接口,则将beanFactory实例传给setBeanFactory方法。
  5. 如果bean实现了ApplicationContextAware接口,则将应用上下文引用传给setApplicationContext方法。
  6. 如果bean实现了BeanPostProcessor接口,则调用postProcessBeforeInitialization方法。
  7. 如果bean实现了InitializationBean接口,则调用afterPropertiesSet方法。类似的,如果bean使用了init-method属性声明了初始化方法,则该方法也会被调用。
  8. 如果bean实现了BeanPostProcessor接口,则调用postProcessAfterInitialization方法。
  9. 按照以上顺序执行后,此时bean已经准备就绪,一直驻留在应用上下文中,直到被销毁。销毁时,如果bean实现了DisposableBean接口,将调用destory方法。同样的,如果bean使用destory-method属性声明了销毁方法,则该方法将会被调用。

当然,以上的这些接口只是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文件中配置:

<bean id = "life" class="howetong.cn.test.LifeBean" init-method="init" destroy-method="del"/>

<bean id = "beanPostProcessor" class="howetong.cn.test.MyBeanPostProcessor"/>

测试:

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容器

上文说,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则功能更为全面,能提供更多的服务,如解析配置文本信息,资源国际化等

BeanFactory和ApplicationContext介绍

Spring Ioc容器的实现,从根源上是BeanFactory。其子类比较多,如下图所示:
在这里插入图片描述
ApplicationContext是BeanFactory的一个重要的子类,它也是一个接口。ApplicationContext的常用实现类也比较多,主要包括如下几种:

  • AnnotationConfigApplicationContext
    从一个或对多个基于Java的配置类中加载上下文定义,适用于Java注解的方式。
  • ClassPathXMLApplicationContext
    从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式
  • FileSystemXMLApplicationContext
    从文件系统下的一个或多个xml配置文件中加载上下文定义(也就是从系统盘符中加载xml配置文件)。
  • AnnotationConfigWebApplicationContext
    专为web应用准备,适用于注解方式。
  • XmlWebApplicationContext
    从web应用下的一个或多个xml配置文件中加载上下文定义,适用于xml配置方式。

相较于BeanFactory,ApplicationContext提供了更多额外功能:

  • 默认初始化所有的Singleton,也可以通过配置取消预初始化。
  • 继承MessageSource,因此支持国际化。
  • 资源访问,比如访问URL和文件。
  • 事件机制。
  • 同时加载多个配置文件。
  • 以声明式方式启动并创建Spring容器。

对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
<bean id = "emailListener" class="howetong.cn.test.EmailListener" />

现在事件和监听器都有了,只要让ApplicationContext容器来发布事件消息就可以了:

public static void main(String[] args) {
    ApplicationContext acx = new ClassPathXmlApplicationContext("/spring-web.xml");
    EmailEvent emailEvent = new EmailEvent("test", "howe@test.com", "hello world");
    acx.publishEvent(emailEvent);
    ((AbstractApplicationContext)acx).close();
}

输出如下:

其他事件
邮件地址:howe@test.com
邮件内容: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函数,输出如下:

容器初始化完成或刷新
邮件地址:howe@test.com
邮件内容:hello world
容器关闭

Spring容器获取

Spring web项目启动的时候,读取spring配置文件来生成spring容器,也就是生成BeanFactory及其子类相关的实例。
如果要在业务代码中获取spring容器,以便根据id来获取对应的bean,应该如何获取呢?

直接注入

最简单的方式是直接注入。

@Autowired
ApplicationContext applicationContext;

Spring初始化过程注入

上文介绍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> T getBeanByClass(Class<T> 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,这里就不详细介绍了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值