Как написать компонент Spring

Java

Эта статья является оригинальной статьей. Любая форма перепечатки приветствуется, но обязательно с указанием источникаЛенг Ленг https://lltx.github.io.

задний план

Spring Framework предоставляет ряд интерфейсов, которые можно использовать для настройки bean-компонентов, а не для простого внедрения методов получения/установки или конструктора. Если вы внимательно посмотрите на исходный код зрелых фреймворков, таких как Spring Cloud Netflix и Spring Cloud Alibaba, построенных на Spring Framework, вы обнаружите большое количество компонентов расширения, таких как

  • Эврика Проверка здоровья
package org.springframework.cloud.netflix.eureka;

public class EurekaHealthCheckHandler implements InitializingBean {}
  • Конфигурация Seata Feign
package com.alibaba.cloud.seata.feign;

public class SeataContextBeanPostProcessor implements BeanPostProcessor {}

пример кода

  • DemoBean
@Slf4j
public class DemoBean implements InitializingBean {

	public DemoBean() {
		log.info("--> instantiate ");
	}

	@PostConstruct
	public void postConstruct() {
		log.info("--> @PostConstruct ");
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		log.info("--> InitializingBean.afterPropertiesSet ");
	}

	public void initMethod() {
		log.info("--> custom initMehotd");
	}
}
  • DemoBeanPostProcessor
@Configuration
public class DemoBeanPostProcessor implements BeanPostProcessor {
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if ("demoBean".equals(beanName)){
			log.info("--> BeanPostProcessor.postProcessBeforeInitialization ");
		}
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if ("demoBean".equals(beanName)){
			log.info("--> BeanPostProcessor.postProcessAfterInitialization ");
		}
		return bean;
	}
}
  • DemoConfig
@Configuration
public class DemoConfig {

	@Bean(initMethod = "initMethod")
	public DemoBean demoBean() {
		return new DemoBean();
	}
}

запустить выходной журнал

  • Вывод журнала всего процесса создания bean-компонента выглядит следующим образом, что соответствует циклу создания bean-компонента над горизонтальной линией на первом рисунке.
DemoBean             : --> instantiate
DemoBeanPostProcessor: --> BeanPostProcessor.postProcessBeforeInitialization
DemoBean             : --> @PostConstruct
DemoBean             : --> InitializingBean.afterPropertiesSet
DemoBean             : --> custom initMehotd
DemoBeanPostProcessor: --> BeanPostProcessor.postProcessAfterInitialization

Выполнение ядра источника

  • AbstractAutowireCapableBeanFactory.initializeBean
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {

  // 执行BeanPostProcessor.postProcessBeforeInitialization
  Object wrappedBean = wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
      ...
      // 执行用户自定义初始化and JSR 250 定义的方法
  invokeInitMethods(beanName, wrappedBean, mbd);
      ...
  // 执行执行BeanPostProcessor.postProcessAfterInitialization
  wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

  return wrappedBean;
}
  • Наилучшие варианты использования цикла расширения каждого компонента подробно описаны ниже.

BeanPostProcessor

BeanPostProcessor — это интерфейс, который можно настроить для реализации методов обратного вызова для реализации собственной логики создания экземпляров, логики разрешения зависимостей и т. д. Если вы хотите реализовать свою собственную бизнес-логику после того, как Spring завершит создание, настройку и инициализацию объекта, вы можете реализовать или Множественная обработка BeanPostProcessor.

  • Он в основном используется в режиме адаптера, который можно упаковывать и конвертировать до и после создания bean-компонентов, реализующих тот же интерфейс.
// seata 上下文转换,将其他类型 wrap 成 SeataFeignContext
public class SeataContextBeanPostProcessor implements BeanPostProcessor {

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName){
		if (bean instanceof FeignContext && !(bean instanceof SeataFeignContext)) {
			return new SeataFeignContext(getSeataFeignObjectWrapper(),
					(FeignContext) bean);
		}
		return bean;
	}
}
  • Пользовательское расширение поиска аннотаций
net.dreamlu.mica.redisson.stream.RStreamListenerDetector 查找自定义 @RStreamListener 实现 基于 Redisson 的 pub/sub

public class RStreamListenerDetector implements BeanPostProcessor {

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		Class<?> userClass = ClassUtils.getUserClass(bean);
		ReflectionUtils.doWithMethods(userClass, method -> {
			RStreamListener listener = AnnotationUtils.findAnnotation(method, RStreamListener.class);
			.... do something
		}, ReflectionUtils.USER_DECLARED_METHODS);
		return bean;
	}
}

PostConstruct

JavaEE5 представляет аннотацию @PostConstruct, которая воздействует на жизненный цикл сервлета для реализации пользовательских операций перед инициализацией bean-компонента.

  • Только один нестатический метод может использовать эту аннотацию
  • Аннотированные методы не могут иметь возвращаемых значений и параметров метода
  • Аннотированные методы не должны генерировать исключения

Здесь следует отметить, что эта аннотация не определена Spring, а принадлежит аннотации, определенной спецификацией JavaEE JSR-250.При использовании Java11 вам необходимо вручную ввести соответствующий jar (поскольку Java11 удален)

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
</dependency>

Сценарии использования: в предыдущих версиях мы могли инициализировать данные после запуска с помощью методов, аннотированных @PostConstruct. Однако, поскольку связанный API был удален в старшей версии Java, мы не рекомендуем использовать эту аннотацию, вы можете вызвать обратную обработку событий через событие, связанное с Spring.

Метод, аннотированный @PostConstruct, выполняет этот метод при запуске проекта, и его также можно понимать как выполнение при запуске контейнера Spring, что может использоваться как обычная загрузка некоторых данных, таких как словарь данных и тому подобное.

InitializingBean

Метод интерфейса InitializingBean выполняется после того, как инициализация контейнера (геттер/сеттер/конструктор) завершает внедрение свойства бина.

Сценарий приложения: динамическое изменение параметров bean-компонента, введенных контейнером.

  • Обычные параметры конфигурации пользователя вводятся в bean-компонент
security:
  oauth2:
      ignore-urls:
        - '/ws/**'

@ConfigurationProperties(prefix = "security.oauth2")
public class PermitAllUrlProperties {
	@Getter
	@Setter
	private List<String> ignoreUrls = new ArrayList<>();
}
  • Мы обнаружили, что на данный момент конфигурация пользователя не завершена, и есть некоторые распространенные, не требующие обслуживания пользователем, которые можно расширить, реализуя обратный вызов интерфейса InitializingBean.
@ConfigurationProperties(prefix = "security.oauth2.ignore")
public class PermitAllUrlProperties implements InitializingBean {

	@Getter
	@Setter
	private List<String> urls = new ArrayList<>();

	@Override
	public void afterPropertiesSet() {
		urls.add("/common/*");
	}
}

initMethod

Вышеупомянутый @PostConstruct не рекомендуется для всех, вместо этого вы можете использовать Bean(initMethod = 'initMehotd'), соответствующие ограничения указаны выше.

@Bean(initMethod = "initMethod")
public DemoBean demoBean() {
  return new DemoBean();
}

public void initMethod() {
  log.info("--> custom initMehotd");
}

Суммировать