那些年背过的题:ApplicationContext-bean生命周期管理

317 阅读8分钟

Spring Bean的生命周期包含以下几个核心步骤:

  1. 实例化(Instantiation)

    • Spring容器通过反射机制创建一个Bean的实例。
  2. 属性注入(Property Population)

    • 容器根据配置文件或注解将Bean所依赖的属性和引用注入到该Bean中,包括简单属性值和对其他Bean的引用。
  3. Aware接口回调(Aware Interfaces)

    • 如果Bean实现了某些Aware接口,如BeanNameAwareBeanFactoryAware等,容器会调用这些接口的方法,使Bean获得相应的资源或信息。
  4. 初始化前处理(Post-Initialization, Pre-Initialization)

    • 在Bean初始化之前,BeanPostProcessor接口中的postProcessBeforeInitialization方法会被调用。如果有多个BeanPostProcessor,它们的postProcessBeforeInitialization方法会按顺序执行。
  5. 初始化(Initialization)

    • 如果Bean定义了init-method或实现了InitializingBean接口,容器会调用这个初始化方法。
  6. 初始化后处理(Post-Initialization, Post-Initialization)

    • 初始化完成后,BeanPostProcessor接口中的postProcessAfterInitialization方法会被调用。如果有多个BeanPostProcessor,它们的postProcessAfterInitialization方法会按顺序执行。
  7. Bean就绪使用(Ready to Use)

    • 经过上述步骤,Bean已经完全初始化并可以被应用程序使用。
  8. 销毁(Destruction)

    • 当容器关闭时,或者Spring容器管理的Bean的生命周期结束时,如果Bean实现了DisposableBean接口或者定义了destroy-method,容器会调用相应的销毁方法来释放资源。

案例分析

假设我们有一个简单的 Spring MVC Web 应用程序,该应用程序包含用户管理功能,例如登录和注册。在这个示例中,我们将重点关注一个名为 UserService 的 Bean,它负责处理用户相关的业务逻辑。

1. 实例化(Instantiation)

目的: Spring 容器通过调用类的构造函数创建一个新的 UserService 实例,这是生命周期的起点。

案例:

public class UserService {
    public UserService() {
        System.out.println("构造函数:UserService 被实例化");
    }
}

源码实现细节: 在 Spring 中,Bean 的实例化是由 DefaultListableBeanFactory 类的 createBeanInstance 方法完成的:

protected Object createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
    // 省略了一些代码
    Constructor<?> constructorToUse = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
    return instantiateBean(beanName, mbd);
}

2. 属性设置(Populate Properties)

目的: Spring 容器根据配置文件或注解,为 UserService 设置需要的属性或依赖项。这一步确保所有依赖项都已经注入。

案例:

public class UserService {
    private UserRepository userRepository;

    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
        System.out.println("设置属性:userRepository 被注入");
    }
}

配置文件:

<bean id="userService" class="com.example.UserService">
    <property name="userRepository" ref="userRepository"/>
</bean>
<bean id="userRepository" class="com.example.UserRepository"/>

源码实现细节: 属性设置是在 populateBean 方法中进行的:

protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
    // Property values are applied here
    applyPropertyValues(beanName, mbd, bw, pvs);
}

3. BeanNameAware 接口的回调

目的: 如果 UserService 实现了 BeanNameAware 接口,Spring 会调用其 setBeanName 方法,将配置文件中定义的 Bean 名称传递给该方法。

案例:

public class UserService implements BeanNameAware {
    @Override
    public void setBeanName(String name) {
        System.out.println("BeanNameAware:设置 Bean 名称为 " + name);
    }
}

源码实现细节:invokeAwareMethods 方法中处理:

private void invokeAwareMethods(String beanName, Object bean) {
    if (bean instanceof BeanNameAware) {
        ((BeanNameAware) bean).setBeanName(beanName);
    }
    // Other Aware interfaces omitted for brevity
}

4. BeanFactoryAware 接口的回调

目的: 如果 UserService 实现了 BeanFactoryAware 接口,Spring 会调用其 setBeanFactory 方法,将当前的 BeanFactory 传递给该方法。这通常用于获取容器自身的一些信息或者其他的 Bean。

案例:

public class UserService implements BeanFactoryAware {
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("BeanFactoryAware:设置 BeanFactory");
    }
}

源码实现细节: 同样在 invokeAwareMethods 方法中处理:

private void invokeAwareMethods(String beanName, Object bean) {
    if (bean instanceof BeanFactoryAware) {
        ((BeanFactoryAware) bean).setBeanFactory(this);
    }
    // Other Aware interfaces omitted for brevity
}

5. ApplicationContextAware 接口的回调

目的: 如果 UserService 实现了 ApplicationContextAware 接口,Spring 会调用其 setApplicationContext 方法,将当前的 ApplicationContext 传递给该方法。这样 Bean 就可以访问到 Spring 容器的信息和资源。

案例:

public class UserService implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("ApplicationContextAware:设置 ApplicationContext");
    }
}

源码实现细节:invokeAwareMethods 方法中处理:

private void invokeAwareMethods(String beanName, Object bean) {
    if (bean instanceof ApplicationContextAware) {
        ((ApplicationContextAware) bean).setApplicationContext((ApplicationContext) this.applicationContext);
    }
    // Other Aware interfaces omitted for brevity
}

6. 预初始化(Post-Initialization / Bean Post Processors)前处理

目的: 允许开发人员对 Bean 进行自定义的前处理操作,例如修改属性或者执行特定逻辑。在初始化之前,Spring 会调用所有注册的 BeanPostProcessorpostProcessBeforeInitialization 方法。

案例:

public class CustomBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof UserService) {
            System.out.println("BeanPostProcessor:在初始化之前处理 UserService");
        }
        return bean;
    }
}

源码实现细节:applyBeanPostProcessorsBeforeInitialization 方法中处理:

public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException {
    Object result = existingBean;
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
        Object current = processor.postProcessBeforeInitialization(result, beanName);
        if (current == null) {
            return result;
        }
        result = current;
    }
    return result;
}

7. 初始化(Initialization)

目的: 在这个阶段,Spring 会先检查 UserService 是否实现了 InitializingBean 接口。如果是,则调用其 afterPropertiesSet 方法。此外,如果在配置文件中指定了初始化方法,也会调用该自定义初始化方法。

案例:

public class UserService implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean:UserService 初始化");
    }

    public void customInit() {
        System.out.println("自定义初始化方法:UserService 初始化");
    }
}

配置文件:

<bean id="userService" class="com.example.UserService" init-method="customInit">
    <property name="userRepository" ref="userRepository"/>
</bean>

源码实现细节: 初始化是在 invokeInitMethods 方法中进行的:

protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd) throws Throwable {
    boolean isInitializingBean = (bean instanceof InitializingBean);
    if (isInitializingBean) {
        ((InitializingBean) bean).afterPropertiesSet();
    }

    if (mbd != null && bean.getClass() != NullBean.class) {
        String initMethodName = mbd.getInitMethodName();
        if (StringUtils.hasLength(initMethodName)) {
            invokeCustomInitMethod(beanName, bean, initMethodName);
        }
    }
}

8. 后处理(Post-Initialization / Bean Post Processors)后处理

目的: 允许开发人员对 Bean 进行自定义的后处理操作。在初始化之后,Spring 会调用所有注册的 BeanPostProcessorpostProcessAfterInitialization 方法。这一步通常用于代理创建或其他增强操作。

案例:

public class CustomBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof UserService) {
            System.out.println("BeanPostProcessor:在初始化之后处理 UserService");
        }
        return bean;
    }
}

源码实现细节:applyBeanPostProcessorsAfterInitialization 方法中处理:

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException {
    Object result = existingBean;
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
        Object current = processor.postProcessAfterInitialization(result, beanName);
        if (current == null) {
            return result;
        }
        result = current;
    }
    return result;
}

9. 使用 Bean(Using the Bean)

目的: 在 Bean 完成初始化后,它可以被应用程序使用。此时,UserService 已经准备好提供服务,如处理用户登录、注册等操作。

案例:

@Service
public class UserService {
    // Business logic methods
    public void login(String username, String password) {
        System.out.println("User " + username + " is trying to log in.");
    }
}

源码实现细节: 这一阶段不涉及特殊的 Spring 源码调用,而是由应用程序逻辑决定如何使用该 Bean。

10. 销毁(Destruction)

目的: 在容器关闭时,Spring 会释放资源并进行清理工作。如果 UserService 实现了 DisposableBean 接口或者配置了销毁方法,则会调用相应的方法。

案例:

public class UserService implements DisposableBean {
    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean:UserService 被销毁");
    }

    public void customDestroy() {
        System.out.println("自定义销毁方法:UserService 被销毁");
    }
}

配置文件:

<bean id="userService" class="com.example.UserService" destroy-method="customDestroy">
    <property name="userRepository" ref="userRepository"/>
</bean>
<bean id="userRepository" class="com.example.UserRepository"/>

源码实现细节: 销毁是在 destroyBean 方法中进行的:

protected void destroyBean(String beanName, final Object bean, RootBeanDefinition mbd) {
    new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessorCache().destructionAware).destroy();
}

DisposableBeanAdapter 类负责实际调用 DisposableBean 接口的 destroy 方法和自定义的销毁方法:

public class DisposableBeanAdapter implements DisposableBean, Runnable {

    private volatile boolean invoked;

    @Override
    public void destroy() throws Exception {
        if (!this.invoked) {
            this.invoked = true;
            if (this.invokeDisposableBean) {
                ((DisposableBean) this.bean).destroy();
            }
            if (this.invokeCustomDestroyMethod) {
                invokeCustomDestroyMethod(this.destroyMethodName);
            }
        }
    }
}

综上所述,Spring Bean 的生命周期涵盖了从实例化、属性设置、各种 Aware 接口的回调、前处理、初始化、后处理,到最终的销毁。这些步骤确保了 Bean 在整个生命周期内都能被正确管理,并且提供了丰富的扩展点供开发人员进行自定义操作。通过以上案例和源码分析,您可以更好地理解 Spring 如何管理 Bean 的生命周期。

初始化阶段实际应用场景

  1. 数据库连接池初始化

    • 在一个Web应用程序中,通常需要与数据库交互。在Bean的初始化阶段,可以创建和配置数据库连接池,例如HikariCP或C3P0,以便在应用启动后立即可用。
  2. 加载配置文件

    • 有时候,应用程序需要从外部配置文件(如properties文件或YAML)加载一些配置信息。在初始化阶段,可以读取和解析这些配置文件,并将配置信息注入到相应的Bean中。
  3. 缓存预加载

    • 对于一些性能要求高的应用,可以在初始化阶段从数据库或其他持久化存储加载数据到内存缓存中,例如使用Ehcache、Redis等。这可以显著减少实时访问数据库的次数,提高响应速度。
  4. 执行校验逻辑

    • 初始化阶段可以用于执行一些校验逻辑。例如,在Bean完全初始化之前,确保所有必须的属性都已经正确设置,并且符合预期的业务规则。
  5. 注册事件监听器

    • 在一些事件驱动的应用中,可以在初始化阶段注册事件监听器,使得应用能够对特定事件作出响应。例如,在Spring中,可以通过实现ApplicationListener接口来监听容器事件。
  6. 启动后台任务

    • 某些应用可能需要在后台运行定时任务或者异步处理任务。在Spring Bean初始化阶段,可以启动这些任务。例如,可以使用Spring的@Scheduled注解或者TaskExecutor来安排这些任务。
  7. 安全配置

    • 在一些安全敏感的应用中,初始化阶段可以用于加载和配置安全相关的Bean,比如配置认证和授权机制,加载密钥和证书等。
  8. 第三方服务集成

    • 如果应用需要与第三方服务(例如消息队列、REST API、SOAP服务等)进行集成,可以在初始化阶段配置和初始化这些服务的客户端。例如,配置JMS连接工厂、REST模板等。