Spring框架中Bean的初始化是一个关键的环节,它处于Bean生命周期的重要位置,涉及多个扩展点,执行顺序也有着严格的控制。深入了解这一过程,对开发者更好地运用Spring框架至关重要。下面,我们就来详细剖析一下Spring中Bean的初始化过程。

一、初始化阶段的触发时机

Bean的初始化发生在属性注入(也就是依赖注入)完成之后。当依赖注入结束,Bean的实例虽然已经创建出来了,但此时它还不能算完全准备好。比如,可能还没有生成代理对象,一些自定义的逻辑也还未执行。只有在初始化完成后,Bean才能正式投入使用。

二、初始化方法的执行流程

Spring在Bean初始化时,会按照特定的顺序调用三类初始化方法。

(一)@PostConstruct注解方法

这是JSR – 250标准里的注解,由CommonAnnotationBeanPostProcessor进行处理,在所有初始化方法中执行顺序是最先的。看下面这个示例代码:

@Component public class MyBean { @PostConstruct public void init() { System.out.println("@PostConstruct方法执行"); } } 

在这个例子里,当MyBean进行初始化时,@PostConstruct注解标注的init方法会首先执行。

(二)InitializingBean.afterPropertiesSet()

InitializingBean是Spring原生提供的接口。实现了这个接口的Bean,其afterPropertiesSet方法会在@PostConstruct注解方法之后执行。示例代码如下:

@Component public class MyBean implements InitializingBean { @Override public void afterPropertiesSet() { System.out.println("InitializingBean方法执行"); } } 

当MyBean初始化时,在@PostConstruct方法执行完后,就会执行afterPropertiesSet方法。

(三)自定义init-method

我们可以通过XML配置<bean init-method="init">,或者在Java配置类里使用@Bean(initMethod = "init")来指定自定义的初始化方法。这个方法是在前面两种方法之后执行的。示例如下:

public class MyBean { public void customInit() { System.out.println("自定义init-method执行"); } } @Configuration public class AppConfig { @Bean(initMethod = "customInit") public MyBean myBean() { return new MyBean(); } } 

在上述代码中,customInit方法就是自定义的初始化方法,会在@PostConstructInitializingBean.afterPropertiesSet()执行之后运行。

三、初始化过程的源码分析

Spring通过initializeBean()方法来协调整个初始化流程,下面是这个方法在AbstractAutowireCapableBeanFactory.java中的源码:

// AbstractAutowireCapableBeanFactory.java protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) { // 1. 执行Aware接口回调(BeanNameAware, BeanFactoryAware等) invokeAwareMethods(beanName, bean); // 2. BeanPostProcessor前置处理 Object wrappedBean = bean; if (mbd == null ||!mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName); } try { // 3. 执行初始化方法 invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException(...); } // 4. BeanPostProcessor后置处理(如生成AOP代理) if (mbd == null ||!mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; } 

在这段代码里,invokeInitMethods方法尤为关键,它负责具体执行初始化方法,源码如下:

protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd) { // 执行InitializingBean.afterPropertiesSet() if (bean instanceof InitializingBean) { ((InitializingBean) bean).afterPropertiesSet(); } // 执行自定义init-method String initMethodName = mbd.getInitMethodName(); if (StringUtils.hasLength(initMethodName) && !(bean instanceof InitializingBean && "afterPropertiesSet".equals(initMethodName))) { Method initMethod = ClassUtils.getMethodIfAvailable(bean.getClass(), initMethodName); ReflectionUtils.makeAccessible(initMethod); initMethod.invoke(bean); } } 

从这段代码可以看出,它先检查Bean是否实现了InitializingBean接口,如果实现了就执行afterPropertiesSet方法,然后再执行自定义的init - method

四、初始化扩展点详解

(一)BeanPostProcessor的作用

BeanPostProcessor提供了两个重要的方法,用于在Bean初始化前后进行额外的处理。

  1. postProcessBeforeInitialization:这个方法在初始化方法执行之前调用,开发者可以利用它来修改Bean的属性,或者返回一个包装对象。例如:
public class CustomBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { if (bean instanceof MyBean) { System.out.println("BeforeInitialization: " + beanName); } return bean; } } 

在上述代码中,当检测到要处理的Bean是MyBean时,就会打印一条信息。
2. postProcessAfterInitialization:该方法在初始化方法执行之后调用,常被用于生成代理对象。示例代码如下:

@Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean instanceof MyService) { return Enhancer.create(bean.getClass(), (MethodInterceptor) (obj, method, args, proxy) -> { System.out.println("代理逻辑执行"); return proxy.invokeSuper(obj, args); }); } return bean; } 

在这个例子里,如果Bean是MyService类型,就会为其创建一个代理对象,并在代理逻辑中打印一条信息。

(二)@PostConstruct的实现机制

@PostConstruct注解是由CommonAnnotationBeanPostProcessor处理的。它的底层原理是通过反射机制,调用那些标记了@PostConstruct的方法。而且,这个处理器的优先级比处理InitializingBean的逻辑要高,所以@PostConstruct注解方法会先执行。

五、初始化过程中的典型问题及解决方案

(一)初始化方法未执行

  1. 常见原因
    • Bean没有被Spring管理,比如没有添加@Component注解。
    • 对于原型作用域的Bean,获取方式不正确。
    • 自定义的init - method名称拼写错误。
  2. 排查工具:我们可以启用Spring的调试日志(logging.level.org.springframework.beans=DEBUG ),通过查看日志来定位问题。

(二)初始化顺序依赖

在实际开发中,可能会遇到Bean A需要在Bean B初始化完成后才能初始化的情况。这时,可以使用@DependsOn("b")注解来解决。例如:

@Component @DependsOn("b") public class A { //... } 

这样,Spring在初始化A时,会先确保B已经完成初始化。

(三)在初始化方法中访问其他Bean

在初始化方法里访问其他Bean时,存在一定风险。如果依赖的Bean还没有初始化完成,就可能会导致空指针异常(NPE)。比较好的做法是通过ApplicationContext.getBean()方法延迟获取,但这种方式需要谨慎使用。

六、初始化阶段与其他生命周期的交互

Bean的初始化阶段和其他生命周期阶段有着紧密的联系:

  • 实例化:初始化必须在实例化之后进行,因为只有先创建出对象,才谈得上对其进行初始化。
  • 属性注入:只有当所有依赖注入都完成后,才会触发初始化,这样可以确保@Autowired注解的字段在初始化时是可用的。
  • AOP代理:通常情况下,代理对象是在postProcessAfterInitialization方法中生成的,所以原始Bean的初始化方法会先执行。
  • 销毁阶段:初始化和销毁方法在设计上是对称的,比如@PreDestroy@PostConstruct就相互对应。

七、特殊场景下的初始化行为

(一)原型(Prototype)Bean

原型Bean的特点是每次请求都会创建一个新的实例,所以它的初始化方法每次都会执行。不过需要注意的是,Spring不会管理原型Bean的销毁阶段,@PreDestroy注解在这种情况下是不生效的。

(二)延迟初始化(Lazy Init)

我们可以通过@Lazy注解或者<bean lazy - init="true">配置来实现延迟初始化。采用这种方式后,Bean的初始化和依赖注入会在首次访问时才触发。

(三)FactoryBean的初始化

FactoryBean比较特殊,它的getObject()方法返回的对象会单独走一套初始化流程。

八、总结初始化阶段的核心要点

  • 执行顺序:初始化方法的执行顺序是@PostConstruct注解方法 → InitializingBean接口的afterPropertiesSet方法 → 自定义init - method
  • 扩展点:主要的扩展点有BeanPostProcessor@PostConstruct注解、InitializingBean接口。
  • 代理生成时机:代理对象一般在postProcessAfterInitialization方法中生成,生成的代理对象可能会覆盖原始Bean。
  • 设计原则:Spring通过接口和注解将不同的关注点分离,开发者应避免在初始化方法中编写过于复杂的业务逻辑。