последовательность
Это должно быть очень распространенным сценарием для выполнения некоторой логики инициализации после запуска Spring Boot.Вот несколько методов и порядок выполнения.
init-method
Настройте атрибут init-method для bean-компонента или укажите его в файле конфигурации xml, или укажите атрибут initMethod аннотированного bean-компонента.
InitializingBean
Реализуйте интерфейс InitializingBean.
Использование аннотации PostConstruct
Добавьте аннотацию PostConstruct к методу инициализации.
ApplicationRunner/CommandLineRunner в Spring Boot
Реализуйте интерфейс ApplicationRunner или CommandLineRunner.
текущий результат
Наш базовый класс:
public class Foo implements InitializingBean, CommandLineRunner, ApplicationRunner {
public void init() {
System.out.println("init method ...");
}
@PostConstruct
public void postConstruct() {
System.out.println("init by PostConstruct ...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("init afterPropertiesSet ...");
}
@Override
public void run(String... args) throws Exception {
System.out.println("init by CommandLineRunner ...");
}
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("init by ApplicationRunner ...");
}
}
Импортируйте этот компонент.
@Configuration
public class BeanConfig {
@Bean(initMethod = "init")
public Foo foo() {
return new Foo();
}
}
Рабочий вывод:
init by PostConstruct ...
init afterPropertiesSet ...
init method ...
init by ApplicationRunner ...
init by CommandLineRunner ...
Выполнение последовательного анализа исходного кода
Запуск приложения Spring Boot (SpringApplication.run
) сначала загрузит и инициализирует контекст приложения Spring (обновит), а затем вызовет бегун (runner), представленный Spring Boot: ApplicationRunner и CommandLineRunner, по сути, между ApplicationRunner и CommandLineRunner нет никакой разницы.
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<Object>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<Object>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
Таким образом, механизм инициализации в Spring будет выполняться первым. Затем посмотрите на порядок выполнения init-method, InitializingBean и PostConstruct.
После создания экземпляра bean-компонента он будет инициализирован, то есть initializeBean , изinvokeInitMethods
В методе мы видим, что сначала выполняется метод интерфейса InitializingBean, а затем настроенный метод метода init.
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
invokeAwareMethods(beanName, bean);
return null;
}
}, getAccessControlContext());
}
else {
// 如果这个bean实现了一些Aware接口,则将对应的对象设置给他
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
// 这里
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
protected void invokeInitMethods(String beanName, final Object bean, 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(new PrivilegedExceptionAction<Object>() {
@Override
public Object run() throws Exception {
((InitializingBean) bean).afterPropertiesSet();
return null;
}
}, getAccessControlContext());
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
// 如果实现了 InitializingBean 接口
((InitializingBean) bean).afterPropertiesSet();
}
}
if (mbd != null) {
// 如果配置了 init-method
String initMethodName = mbd.getInitMethodName();
if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
invokeCustomInitMethod(beanName, bean, mbd);
}
}
}
Так почему же аннотация PostConstruct выполняется первой? Происхождение аннотации PostConstruct основано на реализации BeanPostProcessor, а именно InitDestroyAnnotationBeanPostProcessor.
@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> initMethodsToIterate =
(this.checkedInitMethods != null ? this.checkedInitMethods : this.initMethods);
if (!initMethodsToIterate.isEmpty()) {
boolean debug = logger.isDebugEnabled();
for (LifecycleElement element : initMethodsToIterate) {
if (debug) {
logger.debug("Invoking init method on bean '" + beanName + "': " + element.getMethod());
}
element.invoke(target);
}
}
}
Когда BeanPostProcessor запускается в первый раз, он использует отражение, чтобы найти метаданные жизненного цикла, определенные в компоненте, то есть методы, аннотированные аннотациями PostConstruct и PreDestroy.
private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) {
if (this.lifecycleMetadataCache == null) {
// Happens after deserialization, during destruction...
return buildLifecycleMetadata(clazz);
}
// Quick check on the concurrent map first, with minimal locking.
LifecycleMetadata metadata = this.lifecycleMetadataCache.get(clazz);
if (metadata == null) {
synchronized (this.lifecycleMetadataCache) {
metadata = this.lifecycleMetadataCache.get(clazz);
if (metadata == null) {
// 双重检查
metadata = buildLifecycleMetadata(clazz);
this.lifecycleMetadataCache.put(clazz, metadata);
}
return metadata;
}
}
return metadata;
}
private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {
final boolean debug = logger.isDebugEnabled();
LinkedList<LifecycleElement> initMethods = new LinkedList<LifecycleElement>();
LinkedList<LifecycleElement> destroyMethods = new LinkedList<LifecycleElement>();
Class<?> targetClass = clazz;
do {
final LinkedList<LifecycleElement> currInitMethods = new LinkedList<LifecycleElement>();
final LinkedList<LifecycleElement> currDestroyMethods = new LinkedList<LifecycleElement>();
ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
if (initAnnotationType != null) {
if (method.getAnnotation(initAnnotationType) != null) {
LifecycleElement element = new LifecycleElement(method);
currInitMethods.add(element);
if (debug) {
logger.debug("Found init method on class [" + clazz.getName() + "]: " + method);
}
}
}
if (destroyAnnotationType != null) {
if (method.getAnnotation(destroyAnnotationType) != null) {
currDestroyMethods.add(new LifecycleElement(method));
if (debug) {
logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method);
}
}
}
}
});
initMethods.addAll(0, currInitMethods);
destroyMethods.addAll(currDestroyMethods);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
return new LifecycleMetadata(clazz, initMethods, destroyMethods);
}
Тип аннотации инициализации initAnnotationType и тип аннотации уничтожения destroyAnnotationType, появляющиеся в приведенном выше коде, в настоящее время являются PostConstruct и PreDestroy, что видно из конструктора CommonAnnotationBeanPostProcessor (который наследует InitDestroyAnnotationBeanPostProcessor), и видно, что этот код очень масштабируем.
public CommonAnnotationBeanPostProcessor() {
setOrder(Ordered.LOWEST_PRECEDENCE - 3);
setInitAnnotationType(PostConstruct.class);
setDestroyAnnotationType(PreDestroy.class);
ignoreResourceType("javax.xml.ws.WebServiceContext");
}