Springboot extension point series_BeanPostProcessor

Features

1. BeanPostProcessor is a Bean-level extension interface. After the Bean instantiation managed by Spring is completed, two extension points are reserved;

2. The implementation method of these two extensions is to implement the BeanPostProcessor interface and register the implementation class in the Spring container;

3. The two extension points are the postProcessBeforeInitialization method and the postProcessAfterInitialization method of the BeanPostProcessor interface;

4. The execution timing of the postProcessBeforeInitialization method is after the Spring-managed Bean instantiation and property injection are completed, and before the InitializingBean#afterPropertiesSet method and the custom initialization method;

5. The execution timing of the postProcessAfterInitialization method is after the InitializingBean#afterPropertiesSet method and the custom initialization method;

6. The postProcessBeforeInitialization method and postProcessAfterInitialization method of the implementation class of the BeanPostProcessor interface will be executed after each bean managed by Spring is initialized;

Implementation method

1. Define an entity class Dog, implement the InitializingBean interface, and implement afterPropertiesSet(). Among them, afterPropertiesSet() and init() are to demonstrate the execution timing of the postProcessBeforeInitialization method and postProcessAfterInitialization method of the implementation class of the BeanPostProcessor interface;

@Getter
@Setter
@Slf4j
public class Dog implements InitializingBean {<!-- -->
    private String name = "Wangcai";
    private String color = "black";
    public Dog() {<!-- -->
        log.info("---dog's parameterless construction method is executed");
    }
    @Override
    public void afterPropertiesSet() throws Exception {<!-- -->
        log.info("---afterPropertiesSet is executed");
    }
    public void init() {<!-- -->
        log.info("---initMethod was executed");
    }
}

? Register the Dog class into the Spring container and set the initialization method after Bean instantiation;

@Configuration
public class SpringConfig {<!-- -->
    @Bean(initMethod = "init")
    public Dog dog(){<!-- -->
        Dog dog = new Dog();
        return dog;
    }
}

2. Define MyBeanPostProcessor and implement the BeanPostProcessor interface; (The naming of the class and the logic within the method are only for demonstration purposes. In actual development, the demonstration content needs to be replaced with actual logic)

@Component
@Slf4j
public class MyBeanPostProcessor implements BeanPostProcessor {<!-- -->
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {<!-- -->
        if (beanName.equals("dog")) {<!-- -->
            log.info("postProcessBeforeInitialization---" + beanName);
            //If the specific bean is instantiated and before the InitializingBean.afterPropertiesSet() method is executed, there are some other operations that can be implemented here.
        }
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {<!-- -->
       if (beanName.equals("dog")) {<!-- -->
            log.info("postProcessAfterInitialization---" + beanName);
            //If the specific bean is instantiated, after the InitializingBean.afterPropertiesSet() method is executed, there are some other operations that can be implemented here
        }
        return bean;
    }
}

3. Write unit tests to verify the results;

@SpringBootTest
@Slf4j
public class FanfuApplicationTests {<!-- -->
   @Test
    public void test3(){<!-- -->
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.fanfu");
        Dog dog = ((Dog) context.getBean("dog"));
        log.info(dog.getName());
    }
}

img

? Conclusion: Judging from the execution results of the unit test, it is verified that the execution timing of Spring’s extension point BeanPostProcessor, that is, the execution timing of the postProcessBeforeInitialization method is after the Spring-managed Bean instantiation and property injection are completed, the InitializingBean#afterPropertiesSet method and the customization before the initialization method; the execution timing of the postProcessAfterInitialization method is before and after the InitializingBean#afterPropertiesSet method and the custom initialization method;

? The above demonstrates the implementation method and execution timing of BeanPostProcessor as one of the extension points of Springboot. Let’s start with an example to understand its basic working principle. As the saying goes, to know what it is, you must also know why it is.

How it works

? The key to the working principle of BeanPostProcessor is actually two points. First, when was the implementation class of BeanPostProcessor registered? Second, how are the postProcessBeforeInitialization method and postProcessAfterInitialization method of the BeanPostProcessor implementation class executed?

Registration timing

1. Among the two extension methods in BeanPostProcessor, the postProcessBeforeInitialization method is executed first, that is, after the Bean instantiation and attribute injection are completed, the entrance of the implementation class of the BeanPostProcessor interface to the Spring container is found through debugging of the implementation sample code. , namely org.springframework.context.support.AbstractApplicationContext#refresh->registerBeanPostProcessors

img

2. Enter the AbstractApplicationContext#registerBeanPostProcessors method and you will find that this code is very clean, that is, it relies on the registerBeanPostProcessors() method of the PostProcessorRegistrationDelegate class;

protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {<!-- -->
   PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}

3. Entering the registerBeanPostProcessors() method of the PostProcessorRegistrationDelegate class is another story: the first step is to obtain the names of all implementation classes that implement the BeanPostProcessor interface. MyBeanPostProcessors in the implementation example is among them;

img

The second step is to register the BeanPostProcessorChecker in advance. The main purpose is to print log information during the Bean creation process; img

The third step is to divide all the implementation classes of the BeanPostProcessor interface into three groups according to whether they implement the PriorityOrdered interface, whether they implement the Ordered interface, and others; img

Finally, the following content is very long, but very simple, that is, according to the three categories divided in the second step, register in order. The specific order is the implementation class of the BeanPostProcessor interface that implements the PriorityOrdered interface, the implementation class of the BeanPostProcessor interface that implements the Ordered interface, and others. Implementation class of BeanPostProcessor interface; img

? In summary, the registration timing of BeanPostProcessor is during the Spring container startup process, that is, after the logic execution of the BeanFactoryPostProcessor extension point is completed, the registration of BeanPostProcessor begins. The specific registration logic is in PostProcessorRegistrationDelegate#registerBeanPostProcessors().

img

Execution timing

? It is verified from the example of the implementation method that the execution time of the implementation class of the BeanPostProcessor interface is after the instantiation and attribute injection of the Spring-managed Bean are completed. Then find the instantiation entrance of the Dog class, then leave the implementation class of the BeanPostProcessor interface. The time for execution is not far away.

1. Through Debug debugging, register the instantiation entrance of the Dog class in the Spring container, that is, org.springframework.context.support.AbstractApplicationContext#refresh->finishBeanFactoryInitialization(); img

2. Enter finishBeanFactoryInitialization() and find that the Dog class in the implementation example is instantiated in DefaultListableBeanFactory#preInstantiateSingletons->getBean(). Here is a brief introduction to the business logic of getBean(): when obtaining a certain bean, first query the cache to determine whether it exists. If it exists, return it directly. If it does not exist, start creating the Bean. If the Bean depends on another Bean, then It is a recursion of the above process. img

3. After entering from the getBean method, the main process is AbstractBeanFactory#doGetBean–>AbstractBeanFactory#createBean–>AbstractAutowireCapableBeanFactory#doCreateBean–>AbstractAutowireCapableBeanFactory#createBeanInstance. At this point, the instantiation and attribute injection of the Bean are completed. At this point, cheer up. The execution time of the implementation class of the BeanPostProcessor interface you are looking for is about to arrive. Sure enough, in the AbstractAutowireCapableBeanFactory#doCreateBean method, after the Dog class is instantiated, initializeBean() is called to initialize the bean. The execution timing of the postProcessBeforeInitialization method and postProcessAfterInitialization method of the implementation class of the BeanPostProcessor interface is before and after the execution of the Bean’s initialization method. Triggered, then this method is most likely the entrance to the execution timing of the implementation class of the BeanPostProcessor interface. img

4. Enter initializeBean() and the judgment is indeed correct. First execute the postProcessBeforeInitialization method of the BeanPostProcessor interface implementation class. Then if the bean implements InitializingBean or customizes initMethod, the InitializingBean#afterPropertiesSet and initMethod methods will be executed here. Finally, the postProcessAfterInitialization method of the BeanPostProcessor interface implementation class will be executed;

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {<!-- -->
   if (System.getSecurityManager() != null) {<!-- -->
      AccessController.doPrivileged((PrivilegedAction<Object>) () -> {<!-- -->
         invokeAwareMethods(beanName, bean);
         return null;
      }, getAccessControlContext());
   }else {<!-- -->
      invokeAwareMethods(beanName, bean);
   }
   Object wrappedBean = bean;
   if (mbd == null || !mbd.isSynthetic()) {<!-- -->
       //Execute the postProcessBeforeInitialization method of the BeanPostProcessor interface implementation class
      wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
   }
  try {<!-- -->
       //If the bean implements InitializingBean or customizes initMethod,
       //The InitializingBean#afterPropertiesSet and initMethod methods will be executed here
      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()) {<!-- -->
       //Execute the postProcessAfterInitialization method of the BeanPostProcessor interface implementation class
      wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
   }
   return wrappedBean;
}

5. Next, enter applyBeanPostProcessorsBeforeInitialization(), invokeInitMethods(), applyBeanPostProcessorsAfterInitialization() respectively to see how they are implemented. Let’s first look at applyBeanPostProcessorsBeforeInitialization(): If you have carefully studied the previous articles of BeanFactoryPostProcessor of Springboot extension point, BeanDefinitionRegistryPostProcessor of Springboot extension point, and ApplicationContextInitializer of Springboot extension point, then you will be very familiar with the routine of this method: first obtain All the implementation classes registered to the BeanPostProcessor interface in the Spring container are then traversed and executed to trigger the method. It is so simple and unpretentious.

public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
      throws BeansException {<!-- -->
   Object result = existingBean;
   for (BeanPostProcessor processor : getBeanPostProcessors()) {<!-- -->
      Object current = processor.postProcessBeforeInitialization(result, beanName);
      if (current == null) {<!-- -->
         return result;
      }
      result = current;
   }
   return result;
}

6. Let’s take a look at AbstractAutowireCapableBeanFactory#invokeInitMethods. The logic is also very clear. First determine whether the InitializingBean interface is implemented. If the InitializingBean interface is implemented, it will trigger the execution of afterPropertiesSet(), and then determine whether there is a custom initMethod method. If so, then Start execution here;

protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
      throws Throwable {<!-- -->
    //Determine whether the InitializingBean interface is implemented
   boolean isInitializingBean = (bean instanceof InitializingBean);
   if (isInitializingBean & amp; & amp; (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {<!-- -->
      if (logger.isTraceEnabled()) {<!-- -->
         logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
      }
     if (System.getSecurityManager() != null) {<!-- -->
        try {<!-- -->
            AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {<!-- -->
               ((InitializingBean) bean).afterPropertiesSet();
               return null;
            }, getAccessControlContext());
         }
         catch (PrivilegedActionException pae) {<!-- -->
            throw pae.getException();
         }
      }else {<!-- -->
         //If the InitializingBean interface is implemented, afterPropertiesSet() will be rewritten, and execution will be triggered here.
        ((InitializingBean) bean).afterPropertiesSet();
      }
  }
   if (mbd != null & amp; & amp; bean.getClass() != NullBean.class) {<!-- -->
       //Determine whether there is a custom initMethod method, if so, start execution here;
      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);
      }
   }
}

7. Finally, let’s take a look at applyBeanPostProcessorsAfterInitialization(). If you understand applyBeanPostProcessorsBeforeInitialization() before, there is no need to analyze it here. It is exactly the same, familiar with the recipe and familiar taste.

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
      throws BeansException {<!-- -->
   Object result = existingBean;
   for (BeanPostProcessor processor : getBeanPostProcessors()) {<!-- -->
      Object current = processor.postProcessAfterInitialization(result, beanName);
      if (current == null) {<!-- -->
         return result;
      }
      result = current;
   }
   return result;
}

? At this point, the working principle of the Springboot extension point BeanPostProcessor has been analyzed. It boils down to two points. First, during the initialization process of the Spring container, the registration of the extension point is completed; second, after the Bean is instantiated and attribute injected in Spring, Start triggering extension actions for registered extension points. The content is very long, but the logic is simple. I hope that friends who read this article will have the patience to read it, because after I have studied the whole process clearly, I feel that I have benefited a lot, and I hope you have too.

Application scenarios

? In fact, after understanding the functional features, implementation methods and working principles of BeanPostProcessor, you can apply this extension point when encountering similar business needs. Here are two application scenarios that come to mind:

Processing custom annotations

? In the program, we can customize annotations and mark them on the corresponding classes. When a class is registered in the Spring container and instantiated, if we want to trigger some other operations corresponding to the custom annotations, we can use BeanPostProcessor. accomplish.

Parameter verification

? The specific implementation method of Springboot parameter verification, its core principle is to use the BeanPostProcessor extension point, the specific implementation class is org.springframework.validation.beanvalidation.BeanValidationPostProcessor