Как Spring Boot выполняет логику инициализации

Spring Boot

последовательность

Это должно быть очень распространенным сценарием для выполнения некоторой логики инициализации после запуска 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");
}

Суммировать