Springboot extension point series_BeanFactoryPostProcessor

img

1. Functional features

  1. The execution of BeanFactoryPostProcessor is a very important part of the Spring Bean life cycle;

  2. BeanFactory level post-processor, org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory will only be executed once during the Spring life cycle;

  3. Allows the BeanDefinition data to be read after the container reads the BeanDefinition data and before the bean is instantiated, and can be modified as needed;

2. Implementation method

  1. Define a Dog class, the name attribute defaults to “Wangcai”, and the color defaults to “black”;
@Data
@Component
public class Dog {<!-- -->
    private String name="Wangcai";
    private String color="black";
}
  1. Define an implementation class (MyBeanFactoryPostProcessor), implement the org.springframework.beans.factory.config.BeanFactoryPostProcessor interface, override the postProcessBeanFactory() method, and change the attribute name of the Dog class to “dog egg”; and mark the BeanFactoryPostProcessor interface with the @Component annotation Implementation class (MyBeanFactoryPostProcessor);
@Component
@Slf4j
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {<!-- -->
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {<!-- -->
        log.info("com.fanfu.config.MyBeanFactoryPostProcessor.postProcessBeanFactory is executed");
        ScannedGenericBeanDefinition dog = ((ScannedGenericBeanDefinition) beanFactory.getBeanDefinition("dog")) ;
        MutablePropertyValues propertyValues = dog.getPropertyValues();
        propertyValues.addPropertyValue("name", "Goudaner");
        log.info("Modify the name attribute in Dog's BeanDefinition object to Goudaner");
    }
}
  1. Write unit tests to verify results;
@SpringBootTest
@Slf4j
public class FanfuApplicationTests {<!-- -->
    @Test
    public void test(){<!-- -->
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.fanfu");
        Dog dog = ((Dog) context.getBean("dog"));
        log.info(dog.getName());
        Assert.isTrue(dog.getName().equals("狗丹儿"), "Attribute modification failed");
    }
}
  1. The verification results show that the implementation class of the customized BeanFactoryPostProcessor interface (MyBeanFactoryPostProcessor) can read the BeanDefiniion data after the container reads the BeanDefinition data of the bean and before the bean is instantiated, and modify it as needed, then the customized BeanFactoryPostProcessor What is the working principle of the interface implementation class (MyBeanFactoryPostProcessor)? When is the implementation class of the BeanFactoryPostProcessor interface instantiated? How is the MyBeanFactoryPostProcessor#postProcessBeanFactory method called? Then look down.

3. Working principle

3.1 When is the implementation class of the BeanFactoryPostProcessor interface instantiated?

  1. The implementation class of the BeanFactoryPostProcessor interface (MyBeanFactoryPostProcessor) is marked with @Component and will be encapsulated into a BeanDefinition object and registered in the container when the window is started;

img

  1. When the PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors() method is executed, the names of all implementation classes of the BeanFactoryPostProcessor type will be found from the Spring container according to the type;

img

  1. After getting the names of all implementation classes of the BeanFactoryPostProcessor type in the previous step, loop again to instantiate the implementation class of BeanFactoryPostProcessor (beanFacotry.getBean() to get the instance of MyBeanFactoryPostProcessor. If it cannot be obtained, create one );

img

3.2 How is the MyBeanFactoryPostProcessor#postProcessBeanFactory method called?

  1. An AnnotationConfigApplicationContext object is constructed in the unit test. The construction method of AnnotationConfigApplicationContext is as follows:
public AnnotationConfigApplicationContext(String... basePackages) {<!-- -->
   this();
   scan(basePackages);
   refresh();
}
  1. From the construction method of AnnotationConfigApplicationContext above, you can see that refresh() is called again. What is actually called here is org.springframework.context.support.AbstractApplicationContext#refresh(). This method is also a key method for starting the Spring container. It is often encountered when analyzing Spring-related source code.

  2. In AbstractApplicationContext#refresh(), calling the org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors method officially starts the postProcessBeanFactory() method call of all implementation classes of the BeanFactoryPostProcessor interface;

  3. Follow the AbstractApplicationContext#invokeBeanFactoryPostProcessors method and you will find that this is just an entrance. The actual call execution task is the org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors() method;

  4. After following the PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors() method, you will find that there is really a whole world inside, and it is easy to get lost (carry your own questions and analyze the source code to find the answers, don’t be confused by things other than your own questions, you will definitely find a bright future) In addition, the implementation class of org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor is also called in this method, so this method is very valuable, so I will take it out separately and analyze it carefully. It is recommended to debug and read it step by step and read it several times. You can understand that it is actually very simple. The only difficulty is that this method is a bit long and requires more patience and time.

public static void invokeBeanFactoryPostProcessors(
      ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {<!-- -->
   //The reason why this method has high gold content is
   //This is because in this method, the postProcessBeanDefinitionRegistry method of the BeanDefinitionRegistryPostProcessor implementation class is executed first;
   //Then execute the postProcessBeanFactory method of the implementation class of the BeanFactoryPostProcessor interface
   //These two interfaces are very similar on the surface, but there are obvious differences in execution timing and functions.
   Set<String> processedBeans = new HashSet<>();
   //AnnotationConfigApplicationContext inherits from GenericApplicationContext,
   //The GenericApplicationContext implements the BeanDefinitionRegistry interface
   //So this will enter the if statement
   if (beanFactory instanceof BeanDefinitionRegistry) {<!-- -->
      BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
      List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
      List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
        //The BeanFactoryPostProcessor executed in advance here releases the ApplicationPreparedEvent event when preparing the context environment;
        //Trigger the listener, registered through AbstractApplicationContext#addBeanFactoryPostProcessor;
        //This is not the focus of analysis this time, you can skip this first;
      for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {<!-- -->
         if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {<!-- -->
            BeanDefinitionRegistryPostProcessor registryProcessor =
                  (BeanDefinitionRegistryPostProcessor) postProcessor;
            registryProcessor.postProcessBeanDefinitionRegistry(registry);
            registryProcessors.add(registryProcessor);
         }
         else {<!-- -->
            regularPostProcessors.add(postProcessor);
         }
      }
      List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
      // From line 32 to line 72, the postProcessBeanDefinitionRegistry method of the BeanDefinitionRegistryPostProcessor implementation class is executed;
      //The execution process is also a bit complex, divided into three steps. First, execute the implementation class that implements the PriorityOrdered interface.
      String[] postProcessorNames =
            beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
      for (String ppName : postProcessorNames) {<!-- -->
         if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {<!-- -->
            currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
            processedBeans.add(ppName);
         }
      }
      sortPostProcessors(currentRegistryProcessors, beanFactory);
      registryProcessors.addAll(currentRegistryProcessors);
      invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
      currentRegistryProcessors.clear();
      // Second, execute the implementation class that implements the Ordered interface;
      postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
      for (String ppName : postProcessorNames) {<!-- -->
         if (!processedBeans.contains(ppName) & amp; & amp; beanFactory.isTypeMatch(ppName, Ordered.class)) {<!-- -->
            currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
            processedBeans.add(ppName);
         }
      }
      sortPostProcessors(currentRegistryProcessors, beanFactory);
      registryProcessors.addAll(currentRegistryProcessors);
      invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
      currentRegistryProcessors.clear();
      //Third, execute the remaining BeanDefinitionRegistryPostProcessor implementation classes;
      boolean reiterate = true;
      while (reiterate) {<!-- -->
         reiterate = false;
         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);
         invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
         currentRegistryProcessors.clear();
      }
      // BeanDefinitionRegistryPostProcessor inherits BeanFactoryPostProcessor,
      //So postProcessBeanFactory() of this part of the implementation class will be executed in advance
      invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
      //Line 26, the BeanFactoryPostProcessor implementation class of non-BeanDefinitionRegistryPostProcessor type will be executed here
      invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
   } else {<!-- -->
      invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
   }
   //The implementation class of the BeanDefinitionRegistryPostProcessor interface has been executed.
   //The following begins to prepare the implementation class of the BeanFactoryPostProcessor interface
   String[] postProcessorNames =
         beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);
   // Before formal execution, the implementation classes of the BeanFactoryPostProcessor interface are divided into three categories.
   //Respectively, they implement the PriorityOrdered interface, implement the Ordered interface, and others;
   List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
   List<String> orderedPostProcessorNames = new ArrayList<>();
   List<String> nonOrderedPostProcessorNames = new ArrayList<>();
   for (String ppName : postProcessorNames) {<!-- -->
      if (processedBeans.contains(ppName)) {<!-- -->
         // skip - already processed in first phase above
      }
      else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {<!-- -->
         priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
      }
      else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {<!-- -->
         orderedPostProcessorNames.add(ppName);
      }
      else {<!-- -->
         nonOrderedPostProcessorNames.add(ppName);
      }
   }
   // Classify the classes. First, execute the implementation class that implements the PriorityOrdered interface;
   sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
   invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
   // Second, execute the implementation class that implements the Ordered interface;
   List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
   for (String postProcessorName : orderedPostProcessorNames) {<!-- -->
      orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
   }
   sortPostProcessors(orderedPostProcessors, beanFactory);
   invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);
   //Third, execute the implementation class that does not implement the above two interfaces. The customized MyBeanFactoryPostProcessor is executed here.
   //In fact, it is also very simple, similar to the execution process of the implementation class of the BeanDefinitionRegistryPostProcessor interface;
   List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
   for (String postProcessorName : nonOrderedPostProcessorNames) {<!-- -->
      nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
   }
   invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);
   beanFactory.clearMetadataCache();
}

3.3 Call sequence diagram

Here I have drawn a timing diagram so that you can see the entire calling process more intuitively. You can also follow this diagram and debug step by step to understand the entire process;

img

3.4postProcessBeanFactory()postProcessBeanDefinitionRegistry()

By analyzing the PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors() method, the difference between postProcessBeanFactory() and postProcessBeanDefinitionRegistry() is already obvious. Here is a summary (if there are any inaccuracies in the summary, please tell me in the comment area so we can make progress together):

  1. The postProcessBeanDefinitionRegistry method of the implementation class of the BeanDefinitionRegistryPostProcessor interface shall be executed prior to the postProcessBeanFactory method of the implementation class of the BeanFactoryPostProcessor interface;

  2. The formal parameter of the postProcessBeanDefinitionRegistry method is BeanDefinitionRegistry, and the formal parameter of the postProcessBeanFactory method is ConfigurableListableBeanFactory. There will be some functional differences; it should be noted that DefaultListableBeanFactory implements the BeanDefinitionRegistry and ConfigurableListableBeanFactory interfaces;

  3. BeanDefinitionRegistryPostProcessor inherits BeanFactoryPostProcessor

4. Application scenarios

Decryption of sensitive information, such as encryption and decryption of database connection information: In actual business development, the password of mysq and redis is actually unsafe to be configured in clear text in the configuration file, and encrypted password information needs to be configured; but When the encrypted password information is injected into the data source, connecting to the mysql database will definitely result in an abnormal connection, because mysql does not know your encryption method and encryption method. This will create a requirement: the database information configured in the configuration file needs to be encrypted, but the password information must be decrypted in the program before injecting the password information into the data source.

BeanFactoryPostProcessor can just solve this problem. Before actually using the data source to connect to the database, it reads the encrypted information, performs decryption processing, and then replaces the encrypted information in the Spring container with the decrypted information.