This is an original article. Welcome any form of reprint, but please be sure to note the source cold https://lltx.github.io.

background

The Spring framework provides a number of interfaces that you can use to customize beans beyond simple getter/setter or constructor injection. If you look at Spring Cloud Netflix, Spring Cloud Alibaba and other mature frameworks built on Spring Framework source code, you will find a large number of extension beans such as

  • Eureka health check
package org.springframework.cloud.netflix.eureka;

public class EurekaHealthCheckHandler implements InitializingBean {}
Copy the code
  • Seata Feign configuration
package com.alibaba.cloud.seata.feign;

public class SeataContextBeanPostProcessor implements BeanPostProcessor {}
Copy the code

Code sample

  • DemoBean
@Slf4j
public class DemoBean implements InitializingBean {

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

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

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

	public void initMethod(a) {
		log.info("--> custom initMehotd"); }}Copy the code
  • 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 ");
		}
		returnbean; }}Copy the code
  • DemoConfig
@Configuration
public class DemoConfig {

	@Bean(initMethod = "initMethod")
	public DemoBean demoBean(a) {
		return newDemoBean(); }}Copy the code

Run Output Logs

  • The entire bean creation process log output is consistent with the bean creation cycle above the line in the first image
DemoBean             : --> instantiate
DemoBeanPostProcessor: --> BeanPostProcessor.postProcessBeforeInitialization
DemoBean             : --> @PostConstruct
DemoBean             : --> InitializingBean.afterPropertiesSet
DemoBean             : --> custom initMehotd
DemoBeanPostProcessor: --> BeanPostProcessor.postProcessAfterInitialization
Copy the code

Executive process core source code

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

  / / execution BeanPostProcessor postProcessBeforeInitializationObject wrappedBean = wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); .// Perform user-defined initialization and JSR 250 defined methodsinvokeInitMethods(beanName, wrappedBean, mbd); ./ / executed BeanPostProcessor postProcessAfterInitialization
  wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

  return wrappedBean;
}
Copy the code
  • The following details the best use scenario for each bean’s extension cycle

BeanPostProcessor

BeanPostProcessor is a custom callback method interface that can implement its own instantiation logic, dependency resolution logic, etc. If you want to implement your own business logic after Spring has completed object instantiation, configuration, initialization, etc. One or more BeanPostProcessor processing can be implemented by extension.

  • Mostly used for adapter patterns, it is possible to wrap transformations before and after implementing the same interface bean creation
// SeATA context conversion to SeataFeignContext from other types of wrap
public class SeataContextBeanPostProcessor implements BeanPostProcessor {

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName){
		if (bean instanceofFeignContext && ! (beaninstanceof SeataFeignContext)) {
			return new SeataFeignContext(getSeataFeignObjectWrapper(),
					(FeignContext) bean);
		}
		returnbean; }}Copy the code
  • Custom annotation lookup extension
Net. Dreamlu. Mica. Redisson. Stream. RStreamListenerDetector find custom @ RStreamListener implementation based on 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);
		returnbean; }}Copy the code

PostConstruct

JavaEE5 introduces @postconstruct annotations on the Servlet lifecycle to implement custom operations prior to Bean initialization.

  • Only one non-static method can use this annotation
  • An annotated method cannot have return values and method parameters
  • Annotated methods must not throw exceptions

Note that this annotation is not defined by Spring, but is defined by the JavaEE jsr-250 specification. When you use Java11, you need to manually import the relevant jar (because Java11 removed it).

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
</dependency>
Copy the code

Usage scenario: In previous versions, we could initialize data after startup using @postConstruct annotations. However, since the API has been removed from older Versions of Java, we do not recommend using this annotation and can call back Event handling via Spring-related Events

The @postConstruct annotated method executes this method at project startup, or at Spring container startup, as a regular load of some data, such as a data dictionary.

InitializingBean

InitializingBean interface methods are executed after container initializers (getters/setters/constructors) have completed the bean’s property injection.

Application scenario: Dynamically modify container-injected Bean parameters

  • Normal user configuration parameters are injected into the bean
security:
  oauth2:
      ignore-urls:
        - '/ws/**'

@ConfigurationProperties(prefix = "security.oauth2")
public class PermitAllUrlProperties {
	@Getter
	@Setter
	private List<String> ignoreUrls = new ArrayList<>();
}
Copy the code
  • We found that the user configuration was incomplete at this point, and that there were some generalities that did not require user maintenance and could be extended by implementing the InitializingBean interface callback
@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/*"); }}Copy the code

initMethod

@postconstruct is not recommended. Instead, use Bean(initMethod = ‘initMehotd’).

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

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

conclusion

  • The reference docs. Spring. IO/spring/docs…
  • Mica: github.com/lets-mica/m…
  • pig: github.com/lltx/pig