章
目
录
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通过接口和注解将不同的关注点分离,开发者应避免在初始化方法中编写过于复杂的业务逻辑。