Spring Boot source code analysis -- essence of Spring Boot: initialization data at startup

Keywords: Java Spring JDK xml

When we use spring boot to build a project, we sometimes encounter the need to initialize some operations when the project starts. For this demand, spring boot provides us with the following solutions for us to choose:

  • ApplicationRunner and CommandLineRunner interfaces

  • InitializingBean interface and @ PostConstruct during Spring container initialization

  • Spring's event mechanism

ApplicationRunner and CommandLineRunner

We can implement the "ApplicationRunner" or "CommandLineRunner" interfaces. The two interfaces work in the same way and only provide a single run method. This method is available in SpringApplication.run( Before the completion of the call, I don't know if you have any impression on the end of my last article. Let's take a look at these two interfaces first.

public interface ApplicationRunner {
    void run(ApplicationArguments var1) throws Exception;
}

public interface CommandLineRunner {
    void run(String... var1) throws Exception;
}

Only a single run method is provided. Next, let's look at the specific use

ApplicationRunner

Construct a class to implement the ApplicationRunner interface

//Need to join Spring Container
@Component
public class ApplicationRunnerTest implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner");
    }
}

It's very simple. First, use @ Component to add the implementation class to the Spring container. Why do you want to do this? We'll see later. Then implement its run method to implement its own initialization data logic

CommandLineRunner

For these two interfaces, we can use the Order annotation or the Ordered interface to specify the call Order. The smaller the value in @ Order(), the higher the priority

//Need to join Spring Container
@Component
@Order(1)
public class CommandLineRunnerTest implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner...");
    }
}

It also needs to be added to the Spring container. The parameters of CommandLineRunner are the most original parameters without any processing. The parameters of ApplicationRunner are ApplicationArguments, which are further encapsulation of the original parameters

Source code analysis

Let's review my last article, that is SpringApplication.run method The last step of Step 8: execute Runners , here I copy the code directly

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<Object>();
    //Get all of the ApplicationRunner Of Bean Example
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    //Get all of the CommandLineRunner Of Bean Example
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<Object>(runners)) {
        if (runner instanceof ApplicationRunner) {
            //implement ApplicationRunner Of run Method
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            //implement CommandLineRunner Of run Method
            callRunner((CommandLineRunner) runner, args);
        }
    }
}

Obviously, it is to get the instances of ApplicationRunner and CommandLineRunner directly from the Spring container and call their run methods, which is why I use @ Component to add the implementation classes of ApplicationRunner and CommandLineRunner interfaces to the Spring container.

InitializingBean

When spring initializes a bean, if the bean implements the InitializingBean interface, the afterPropertiesSet() method will not be called until all the properties of the object are initialized

@Component
public class InitialingzingBeanTest implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean..");
    }
}

We can see that the spring initialization bean will definitely be called before the application runner and CommandLineRunner interfaces.

@PostConstruct

@Component
public class PostConstructTest {

    @PostConstruct
    public void postConstruct() {
        System.out.println("init...");
    }
}

As you can see, just add the @ PostConstruct annotation to the method and inject the class into the Spring container. Let's see when the @ PostConstruct annotation method is executed

When Spring initializes beans, when assigning values to bean instances, there is an initializeBean(beanName, exposedObject, mbd) method under the populateBean method, which is used to perform initialization operations set by users. Let's look at the following body:

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
    if (System.getSecurityManager() != null) {
        AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
            // activation Aware Method
            invokeAwareMethods(beanName, bean);
            return null;
        }, getAccessControlContext());
    }
    else {
        // Special bean Handle: Aware,BeanClassLoaderAware,BeanFactoryAware
        invokeAwareMethods(beanName, bean);
    }

    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        // Post processor
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

    try {
        // Activate user defined init Method
        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()) {
        // Post processor
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }
    return wrappedBean;
}

We can see that the invokeinitialmethods method will be executed after the processor. Let's take a look at the applybeanpostprocessors before initialization

public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)  
        throws BeansException {  

    Object result = existingBean;  
    for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {  
        result = beanProcessor.postProcessBeforeInitialization(result, beanName);  
        if (result == null) {  
            return result;  
        }  
    }  
    return result;  
}

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)  
        throws BeansException {  

    Object result = existingBean;  
    for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {  
        result = beanProcessor.postProcessAfterInitialization(result, beanName);  
        if (result == null) {  
            return result;  
        }  
    }  
    return result;  
}

Get all the postprocessors in the container, and cycle to call the postProcessBeforeInitialization method of the postprocessor. Let's take a look at a BeanPostProcessor

public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
    public CommonAnnotationBeanPostProcessor() {
        this.setOrder(2147483644);
        //Set initialization parameters to PostConstruct.class
        this.setInitAnnotationType(PostConstruct.class);
        this.setDestroyAnnotationType(PreDestroy.class);
        this.ignoreResourceType("javax.xml.ws.WebServiceContext");
    }
    //slightly...
}

In the constructor, set a property as PostConstruct.class, and observe the class CommonAnnotationBeanPostProcessor again. It inherits from InitDestroyAnnotationBeanPostProcessor. InitDestroyAnnotationBeanPostProcessor, as the name implies, is a pre / post processor made during Bean initialization and destruction. View the postProcessBeforeInitialization method under the InitDestroyAnnotationBeanPostProcessor class:

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {  
   LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());  
   try {  
       metadata.invokeInitMethods(bean, beanName);  
   }  
   catch (InvocationTargetException ex) {  
       throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());  
   }  
   catch (Throwable ex) {  
       throw new BeanCreationException(beanName, "Couldn't invoke init method", ex);  
   }  
    return bean;  
}  

private LifecycleMetadata buildLifecycleMetadata(final Class clazz) {  
       final LifecycleMetadata newMetadata = new LifecycleMetadata();  
       final boolean debug = logger.isDebugEnabled();  
       ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {  
           public void doWith(Method method) {  
              if (initAnnotationType != null) {  
                   //judge clazz Medium methon Whether there is initAnnotationType Annotation, that is PostConstruct.class annotation
                  if (method.getAnnotation(initAnnotationType) != null) {  
                     //Add methods to LifecycleMetadata in
                     newMetadata.addInitMethod(method);  
                     if (debug) {  
                         logger.debug("Found init method on class [" + clazz.getName() + "]: " + method);  
                     }  
                  }  
              }  
              if (destroyAnnotationType != null) {  
                    //judge clazz Medium methon Whether there is destroyAnnotationType annotation
                  if (method.getAnnotation(destroyAnnotationType) != null) {  
                     newMetadata.addDestroyMethod(method);  
                     if (debug) {  
                         logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method);  
                     }  
                  }  
              }  
           }  
       });  
       return newMetadata;  
} 

Here we will judge whether a method has a PostConstruct.class annotation. If so, it will be added to the init/destroy queue and executed one by one later. @The method annotated by PostConstruct will execute at this time. Let's take a look at invokeInitMethods

protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
        throws Throwable {

    // Whether to achieve InitializingBean
    // If it does InitializingBean Interface, only calls are dropped bean Of afterPropertiesSet()
    boolean isInitializingBean = (bean instanceof InitializingBean);
    if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
        if (logger.isDebugEnabled()) {
            logger.debug("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 {
            // Direct call afterPropertiesSet()
            ((InitializingBean) bean).afterPropertiesSet();
        }
    }

    if (mbd != null && bean.getClass() != NullBean.class) {
        // Determine if specified init-method(),
        // If specified init-method(),Then call the init-method
        String initMethodName = mbd.getInitMethodName();
        if (StringUtils.hasLength(initMethodName) &&
                !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
                !mbd.isExternallyManagedInitMethod(initMethodName)) {
            // Implementation by reflection mechanism
            invokeCustomInitMethod(beanName, bean, mbd);
        }
    }
}

First, check whether the current bean implements the InitializingBean interface. If it does, call its afterpropertieset (), and then check whether it also specifies the init method (). If it does, call the specified init method () through the reflection mechanism.

We can also find that @ PostConstruct will execute before implementing the afterpropertieset() method of the InitializingBean interface

Spring's event mechanism

Basic concepts

Spring's event driven model consists of three parts

  • Event: ApplicationEvent, inherited from the EventObject of JDK. All events must inherit it, that is, the observed
  • Event Publisher: the ApplicationEventPublisher and ApplicationEventMulticaster interfaces. With this interface, you can publish events
  • Event listener: ApplicationListener inherits the EventListener of JDK. All listeners inherit it, which is what we call the observer. Of course, we can also use the annotation @ EventListener. The effect is the same

Event

In the Spring framework, application event events are supported by default as follows:

  • ContextStartedEvent: event triggered after ApplicationContext starts
  • ContextStoppedEvent: event triggered after ApplicationContext stops
  • ContextRefreshedEvent: event triggered after ApplicationContext initialization or refresh; (container is initialized after completion, so we can use this event to do some initialization operations).
  • ContextClosedEvent: an event triggered after the ApplicationContext is closed; (for example, when the web container is closed, it will automatically trigger the closing of the spring container. If it is a normal java application, you need to call ctx.registerShutdownHook(); register the hook when the virtual machine is closed.)

Construct a class to inherit ApplicationEvent

public class TestEvent extends ApplicationEvent {

    private String message;
    
    public TestEvent(Object source) {
        super(source);
    }

    public void getMessage() {
        System.out.println(message);
    }

    public void setMessage(String message) {
        this.message = message;
    }

}

Create event listener

There are two ways to create a listener. One is to directly implement the interface of ApplicationListener. The other is to use the annotation @ EventListener. The annotation is added to the listener method. The following example is the directly implemented interface

@Component
public class ApplicationListenerTest implements ApplicationListener<TestEvent> {
    @Override
    public void onApplicationEvent(TestEvent testEvent) {
        testEvent.getMessage();
    }
}

Event Publishing

For event publishing, the representatives are ApplicationEventPublisher and applicationeventmulticasting. The ApplicationContext interface inherits ApplicationEventPublisher and implements specific code in AbstractApplicationContext. The actual execution is entrusted to applicationeventmulticasting (it can be considered as multicast)

The following is a test example of an event Publisher:

@RunWith(SpringRunner.class)
@SpringBootTest
public class EventTest {
    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void publishTest() {
        TestEvent testEvent = new TestEvent("");
        testEvent.setMessage("hello world");
        applicationContext.publishEvent(testEvent);
    }
}

Initializing with ContextRefreshedEvent event

The event is initialized by the ContextRefreshedEvent event, which is called after the ApplicationContext initialization is completed, so we can use this event to implement a listener and initialize the operation in its onApplicationEvent() method.

@Component
public class ApplicationListenerTest implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("After the container refresh, I was called..");
    }
}

Posted by serg91 on Sun, 17 Nov 2019 19:44:30 -0800