Bean初始化的三种方式

1,109 阅读2分钟

对于Spring bean的初始化可以归纳为三种方式:

  • PostConstruct注解标注的方法
  • 实现InitializingBean接口的afterPropertiesSet()方法
  • 自定义初始化方法
    • XML配置: <bean init-method = "init" />
    • Java注解:@Bean(initMethod = "init")
    • Java API: AbstractBeanDefinition#setInitMethodName(String)

PostConstruct

PostConstructjsr规范中的一员,而不是spring框架的概念

虽然在javax中强调一个类中被PostConstruct注解标注的方法只能有一个,但在spring中可以写多个,不过为了移植性,推荐还是只写一个这样的方法。

  1. 示例代码:
@Component
public class BeanInitTestService {
    @PostConstruct
    public void init() {
        System.out.println("PostConstruct注解的bean初始化");
    }
}
  1. 源码浅析
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
	LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
	try {
		metadata.invokeInitMethods(bean, beanName);
	}
	catch (InvocationTargetException ex) {
		throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
	}
	catch (Throwable ex) {
		throw new BeanCreationException(beanName, "Failed to invoke init method", ex);
	}
	return bean;
}

public void invokeInitMethods(Object target, String beanName) throws Throwable {
	Collection<LifecycleElement> checkedInitMethods = this.checkedInitMethods;
	Collection<LifecycleElement> initMethodsToIterate =
			(checkedInitMethods != null ? checkedInitMethods : this.initMethods);
	if (!initMethodsToIterate.isEmpty()) {
		for (LifecycleElement element : initMethodsToIterate) {
			if (logger.isTraceEnabled()) {
				logger.trace("Invoking init method on bean '" + beanName + "': " + element.getMethod());
			}
			element.invoke(target);
		}
	}
}

PostConstruct注解标注的方法被调用的入口, 在类InitDestroyAnnotationBeanPostProcessorpostProcessBeforeInitialization()方法 中,postProcessBeforeInitialization() 内部调用 invokeInitMethods() 方法。 image.png

tips:PostConstruct是通过反射调用的

InitializingBean

Spring提供的一个接口,凡是实现InitializingBean接口的bean,在初始化的时候会执行afterPropertiesSet方法

示例代码:

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

源码分析: afterPropertiesSet方法的调用入口是负责加载 spring beanAbstractAutowireCapableBeanFactory,源码如下:

protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
    throws Throwable {

    boolean isInitializingBean = (bean instanceof InitializingBean);
    if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
        if (logger.isDebugEnabled()) {
            logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
        }
        if (System.getSecurityManager() != null) {
            try {
                AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
                    ((InitializingBean) bean).afterPropertiesSet();
                    return null;
                }, getAccessControlContext());
            }
            catch (PrivilegedActionException pae) {
                throw pae.getException();
            }
        }
        else {
            ((InitializingBean) bean).afterPropertiesSet();
        }
    }

InitializingBean接口是直接调用afterPropertiesSet方法,而不是通过反射调用的

自定义初始化方法

  1. xml配置

暂时不贴示例代码了

  1. bean注解
@Configuration
class TestConfig() {
    @Bean(initMethod = "initMethod")
    public Phone initPhone() {
        return new Phone();
    }
}

class Phone {
    public void initMethod() {
        System.out.println("initMethod....");
    }
}

  • AbstractBeanDefinition#setInitMethodName(String)
@Component
public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.getBeanDefinition("phone").setInitMethodName("initMethod");
    }
}

@Component
public class Phone {
    public void initMethod() {
        System.out.println("initMethod....");
    }
}

执行顺序

  1. spring bean的初始化执行顺序: 构造方法> @Autowired> PostConstruct > InitializingBean > initMethod
  2. InitializingBeanafterPropertiesSet()通过接口实现方式调用(效率上高一点),@PostConstructinitMethod都是通过反射机制调用
  3. 在初始化的过程中,执行完一个Bean的构造方法后会执行该Bean的@PostConstruct方法(如果有),然后再执行下一个Bean的构造方法和@PostConstruct。
    1. 如果@PostConstruct方法内的逻辑处理时间较长,就会增加SpringBoot应用初始化Bean的时间,进而增加应用启动的时间