深度解析Spring中Bean的初始化过程
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
方法就是自定义的初始化方法,会在@PostConstruct
和InitializingBean.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初始化前后进行额外的处理。
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
注解方法会先执行。
五、初始化过程中的典型问题及解决方案
(一)初始化方法未执行
- 常见原因:
- Bean没有被Spring管理,比如没有添加
@Component
注解。 - 对于原型作用域的Bean,获取方式不正确。
- 自定义的
init - method
名称拼写错误。
- Bean没有被Spring管理,比如没有添加
- 排查工具:我们可以启用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通过接口和注解将不同的关注点分离,开发者应避免在初始化方法中编写过于复杂的业务逻辑。