Interception points and scenario applications in the spring bean life cycle

Overview

There are multiple customizable extension points in the process of creating a bean to perform custom operations during the bean life cycle. The following are examples of customizable points and scenarios throughout the bean life cycle.

BeanDefinitionRegistryPostProcessor

Bean definition registration post-processor allows you to modify or register bean definitions before the Spring container is initialized. You can implement this interface to customize bean registration
injection

  1. injection
 <bean class="org.example.bean.MyBeanDefinitionRegistryPostProcessor"/>
  1. customize
public class MyBeanDefinitionRegistryPostProcessor
    implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
       
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
      
    }
}

Where did it start? Above, where is spring called after we define a bean that implements BeanDefinitionRegistryPostProcessor? The following is the place where the initialization call is made

  1. AbstractApplicationContext?Method?refresh?Invoke bean factory post-processor:?invokeBeanFactoryPostProcessors?, at this time the bean factory?ConfigurableListableBeanFactory code>?has been instantiated
  2. The above method calls?PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());?
    It is a static method, which is called in this method. The key code is as follows:
while (reiterate) {
reiterate = false;
        //Get postProcessorNames based on the class type BeanDefinitionRegistryPostProcessor,
        Did you see that we now have our bean name?
        postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
        
        for (String ppName : postProcessorNames) {
           if (!processedBeans.contains(ppName)) {
              currentRegistryProcessors.add(beanFactory.getBean(ppName,
        BeanDefinitionRegistryPostProcessor.class));
              processedBeans.add(ppName);
              reiterate = true;
           }
        }
        sortPostProcessors(currentRegistryProcessors, beanFactory);
        registryProcessors.addAll(currentRegistryProcessors);
        //reflection call here
        invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
        currentRegistryProcessors.clear();
}
//Call invokeBeanFactoryPostProcessors here
invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
}
  1. Method?invokeBeanDefinitionRegistryPostProcessors?Invoke
private static void invokeBeanDefinitionRegistryPostProcessors(
      Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {<!-- -->

   for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {<!-- -->
      StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
            .tag("postProcessor", postProcessor::toString);
//Call postProcessBeanDefinitionRegistry here
      postProcessor.postProcessBeanDefinitionRegistry(registry);
      postProcessBeanDefRegistry.end();
   }
}

At this point, the analysis of the initialization call process is completed.

Method: postProcessBeanDefinitionRegistry

For some operations of ?BeanDefinitionRegistry?, beans can be added and deleted dynamically

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {<!-- -->
    GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
    genericBeanDefinition.setBeanClass(MyBean1.class);
    //Register bean
    registry.registerBeanDefinition("myBean", genericBeanDefinition);
    //Delete the bean, the premise exists
    if(registry.containsBeanDefinition("beanName")){<!-- -->
        registry.removeBeanDefinition("beanName");
    }
}

Scenario 1: Customize the scanning package and register the bean

  1. Custom annotations
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface CustomComponent {
}
  1. Scan custom packages and register beans
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    
    ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
    TypeFilter filter = new AnnotationTypeFilter(CustomComponent.class);
    scanner.addIncludeFilter(filter);

    //Scan the specified package
    for (BeanDefinition beanDefinition : scanner.findCandidateComponents("org.example.bean")) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
            ClassUtils.resolveClassName(Objects.requireNonNull(beanDefinition.getBeanClassName()), ClassUtils.getDefaultClassLoader()));
        registry.registerBeanDefinition(beanDefinition.getBeanClassName(), builder.getBeanDefinition());
    }
}

Under the custom package, classes annotated with CustomComponent will be registered in beans.

Method: postProcessBeanFactory

This is quite rich in some operations of ConfigurableListableBeanFactory
The following are examples of usage scenarios:

Scenario 1: Dynamically registering a singleton bean

 @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
      beanFactory.registerSingleton("myBean1",new MyBean1());
  }

Scenario 2: Add bean post-processing interceptor BeanPostProcessor

BeanPostProcessor is the interceptor for beans before and after initialization. As many beans are created and intercepted as many times, the interceptor can be dynamically added through postProcessBeanFactory

 @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.registerSingleton("myBean1",new MyBean1());

        beanFactory.addBeanPostProcessor(new MyBeanPostProcessor());
    }

MyBeanPostProcessor is defined as follows

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Start registration: " + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Registration completed: " + beanName);
        return bean;
    }
}

Scenario 3: Custom attribute configuration (very good)

What it means is that you can convert custom attributes and convert string values into attribute values of a specific type.
You will understand after watching the scene
The object whose bean is User has a property which is private Address address;
Address also has its own properties, as follows:

private String address1;
private String address2;
private String address3;

When I inject a bean, if what is transmitted is a string or other messy format, which is not an Address anyway, what should I convert as follows?
In the following bean configuration, the address given is Henan-Zhengzhou-High-tech Zone, which is obviously not an object, so we can use a custom converter to convert it.

<bean id="user" class="org.example.bean.User">

    <property name="id" value="1"/>
    <property name="name" value="Zhang San"/>
    <property name="address" value="Henan-Zhengzhou-High-tech Zone"/>
</bean>
Custom converter definition
  1. The converter AddressEditor corresponding to the new Address should inherit PropertyEditorSupport and override the method setAsText. In fact, what we get here is the above Henan-Zhengzhou- High-tech Zone string, we manually convert it, and finally call setValue to encapsulate it
public class AddressEditor extends PropertyEditorSupport {

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        String[] arrs = text.split("-");
        Address address = new Address();
        address.setAddress1(arrs[0]);
        address.setAddress2(arrs[1]);
        address.setAddress3(arrs[2]);
        super.setValue(address);
    }
}
  1. Then AddressEditor definitely needs to be registered. How to register? Create a new custom conversion register to implement PropertyEditorRegistrar
public class CustomEditorRegistrar implements PropertyEditorRegistrar {
    @Override
    public void registerCustomEditors(PropertyEditorRegistry registry) {
        registry.registerCustomEditor(Address.class, new AddressEditor());
    }
}
  1. The registrar needs to be handed over to spring for processing, then we can add the registrar through the postProcessBeanFactory method.
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
   beanFactory.addPropertyEditorRegistrar(new CustomEditorRegistrar());
}

After execution, the bean’s Address is our converted value

Scenario 4: Dynamic injection of attribute values

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        //Modify properties
        BeanDefinition user = beanFactory.getBeanDefinition("user");
        MutablePropertyValues propertyValues = user.getPropertyValues();
        propertyValues.addPropertyValue("id","123456");
    }
}

Scenario 5: Bean definition modification

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    //Modify properties
    BeanDefinition bd = beanFactory.getBeanDefinition("user");
    //Modify the initial method, whether it is a singleton, and other attributes
    bd.setInitMethodName("test2");
}

FactoryBeans

Used to create and configure object instances. It allows you to encapsulate object creation and configuration logic in a separate class

public class MyBeanFactory implements FactoryBean<User> {

    @Override
    public User getObject() throws Exception {
        // Here you can write the logic to create and configure MyBean instances
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }

    @Override
    public boolean isSingleton() {
        return true; //Here you can specify whether the object is a singleton
    }
}


This can also create beans

InitializingBean

bean implements InitializingBean and overrides method afterPropertiesSet

@Override
public void afterPropertiesSet() throws Exception {
    System.out.println("afterPropertiesSet");
}
  • Call time?

You should be familiar with class AbstractApplicationContext refresh called after the bean has completed initialization (not yet instantiated). This method is executed until
finishBeanFactoryInitialization(beanFactory);

Instantiate beans

Look at the bean instantiation with questions and know when to execute the above afterPropertiesSet

  1. The above method will execute the beanFactory implementation class DefaultListableBeanFactory method preInstantiateSingletons

Simplified code:

public void preInstantiateSingletons() throws BeansException {
  
  //beanDefinitionNames You should be familiar with this. This is a list that stores all bean names.
   List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

   
   for (String beanName : beanNames) {
//Just instantiate the bean based on the bean name
       getBean(beanName);
      }
   }

The above code is to loop the bean name and then instantiate it

  1. getBean will call doGetBean. This method is to get the member variable mergedBeanDefinitions based on the bean name to get the metadata information of the bean, which is also a map, and what is stored in it is RootBeanDefinitionIt stores the bean attribute information you defined in xml
  2. With the name and the corresponding metadata, the bean can be created. The final creation code is in the class AbstractAutowireCapableBeanFactory
protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) {
   try {
      Object beanInstance;
      beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);
      BeanWrapper bw = new BeanWrapperImpl(beanInstance);
      initBeanWrapper(bw);
      return bw;
   }
  1. getInstantiationStrategy will get the instantiation strategy class, here is SimpleInstantiationStrategy, the code comes here, there is nothing to talk about here, just get the reflection class ConstructorThen create it based on .class, specifically using the tool class BeanUtils.instantiateClass(constructorToUse)
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
   if (!bd.hasMethodOverrides()) {
      Constructor<?> constructorToUse;
      synchronized (bd.constructorArgumentLock) {
         constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
         if (constructorToUse == null) {
            final Class<?> clazz = bd.getBeanClass();

            constructorToUse = clazz.getDeclaredConstructor();

            bd.resolvedConstructorOrFactoryMethod = constructorToUse;
            }
            catch (Throwable ex) {
               throw new BeanInstantiationException(clazz, "No default constructor found", ex);
            }
         }
      }
      return BeanUtils.instantiateClass(constructorToUse);
   }

Class Constructor usage:

 Class<Person> personClass = Person.class;
Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);

// Create object using constructor
Person person = constructor.newInstance("Alice", 25);
  1. What is returned after the above creation is finally a BeanWrapper object, which is a bean tool class provided by spring for convenient access and manipulation of the properties of the bean object.

The specific usage is as follows:

public static void main(String[] args) {
        //Create a simple object
        Person person = new Person();
        
        // Use BeanWrapper to wrap this object
        BeanWrapper wrapper = new BeanWrapperImpl(person);
        
        //Set attribute value
        wrapper.setPropertyValue("name", "Alice");
        wrapper.setPropertyValue("age", 25);
        // Get attribute value
        String name = (String) wrapper.getPropertyValue("name");
        int age = (int) wrapper.getPropertyValue("age");
        
        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
    //Get the object
Object bean = wrapper.getWrappedInstance();
    }

At this point, the bean instantiation is completed, and a bean wrapper class BeanWrapper is finally returned.

Call afterPropertiesSet and the defined initialization method

Going back to the beginning, how can the bean name and RootBeanDefinition be associated with BeanWrapper?

  1. The method doCreateBean is associated with the code, mbd is the RootBeanDefinition object, and getWrappedClass is the class to get the bean
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
  mbd.resolvedTargetType = beanType;
}
  1. Initialize the bean method doCreateBean to call initializeBean to call invokeInitMethods. The simplified code is as follows
protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
      throws Throwable {

   boolean isInitializingBean = (bean instanceof InitializingBean);
   if (isInitializingBean & amp; & amp; (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
      
      ((InitializingBean) bean).afterPropertiesSet();
   }

   if (mbd != null & amp; & amp; bean.getClass() != NullBean.class) {
      String initMethodName = mbd.getInitMethodName();
      if (StringUtils.hasLength(initMethodName) & amp; & amp;
            !(isInitializingBean & amp; & amp; "afterPropertiesSet".equals(initMethodName)) & amp; & amp;
            !mbd.isExternallyManagedInitMethod(initMethodName)) {
         invokeCustomInitMethod(beanName, bean, mbd);
      }
   }
}
  1. The first if above is to enter the key code of afterPropertiesSet. What does it mean? If the bean inherits InitializingBean, it will be called.
  2. Look at the second if mbd.getInitMethodName()mbd is RootBeanDefinition, getInitMethodName is the initialization method you defined in xml, xml configuration as follows:

public void test(){
    System.out.println("Initialization");
}

If the method defined by init-method has a length, and the method name is not afterPropertiesSet and is not mbd.isExternallyManagedInitMethod(initMethodName), what does this mean? This leads to another concept: to be continued
Then use reflection to execute the initialization method we defined

Summary

afterPropertiesSet is called after the bean is initialized and instantiated. After its execution, other xml-defined init-method will be called.

  • Note that init-method only accepts a parameterless initialization method