Revisit the interception, filtering and listening in the life cycle of SpringBoot series

Posted by psytae on Wed, 08 Dec 2021 07:01:39 +0100

Revisit the interception, filtering and listening in the life cycle of SpringBoot series

Servlet domain object and attribute change monitoring

Definition and implementation of listener

definition:

Servlet listener is a special class defined in the servlet specification. It is used to listen to the creation and destruction events of scope objects such as ServletContext, HttpSession and ServletRequest, as well as the event of property modification in these scope objects. The listener uses the observer pattern in the design pattern. It pays attention to the creation, destruction and change of specific things and makes callback actions. Therefore, the listener has the characteristics of asynchrony.

Servlet Listener listens to the creation and destruction events of three domain objects, which are:

  • ServletContext Listener: at the application level, there is only one in the whole application, and all users use one ServletContext
  • HttpSession Listener: session level. The same session is used in the browser opening and closing life cycle of the same user
  • ServletRequest Listener: request level. Each HTTP request is a request

In addition to listening to the creation and destruction of domain objects, you can also listen to the event of property modification in domain objects.

  • HttpSessionAttributeListener
  • ServletContextAttributeListener
  • ServletRequestAttributeListener

Usage scenario

The function of Servlet specification design listener is to carry out some processing before and after the event. It can generally be used to count the number of online people and online users, the number of website visits, the initialization information during system startup, etc

Implementation of listener

@Slf4j
@WebListener//Mark a listener component whose current class is Servlet
public class CustomListener implements ServletContextListener,
        ServletRequestListener,
        HttpSessionListener,
        ServletRequestAttributeListener{

    @Override
    public void contextInitialized(ServletContextEvent se) {
        log.info("==============context establish");
    }

    @Override
    public void contextDestroyed(ServletContextEvent se) {
        log.info("==============context Destroy");
    }


    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        log.info(" ++++++++++++++++++request Listener: Destroy");
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        log.info(" ++++++++++++++++++request Listeners: Creating");
    }


    @Override
    public void sessionCreated(HttpSessionEvent se) {
        log.info("----------------session establish");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        log.info("----------------session Destroy");
    }

    public void attributeAdded(ServletRequestAttributeEvent srae) {

        log.info("----------------attributeAdded");
    }

    public void attributeRemoved(ServletRequestAttributeEvent srae) {
        log.info("----------------attributeRemoved");
    }

    public void attributeReplaced(ServletRequestAttributeEvent srae) {
        log.info("----------------attributeReplaced");
    }
}
  • Implement the ServletRequestListener interface and override the requestDestroyed and requestInitialized methods. The execution of the requestInitialized method and requestDestroyed method of a ServletRequest represents the completion of the reception and processing of a request. Therefore, it is more suitable for the statistics of the number of visits to website resources.
  • Implement the httpsessionlister interface, and rewrite the sessionInitialized initialization and sessionDestroyed destruction methods to monitor the opening and destruction of session sessions (users' online and offline). For example, it can be used to count the number of online users
  • Implement the ServletContextListener interface and override the contextInitialized initialization and contextDestroyed destruction methods to listen to the initialization and destruction of global applications. For example, when the system starts, initialize some data into memory for subsequent use.
  • Implement the ServletRequestAttributeListener interface (or HttpSessionAttributeListener or ServletContextAttributeListener). You can listen to the actions of adding attributeAdded, deleting attributeRemoved, and replacing attributeReplaced of data attributes in the corresponding scope.

Global Servlet component scan annotation

Add @ ServletComponentScan to the startup class for automatic registration.

Source code analysis:

Because the class of @ ServletComponentScan is loaded with BeanDefinition, its Registar will be loaded. Therefore, the servletcomponentscanregister will be imported, thus adding a servletcomponentregisteringpost processor to the beanfactory. When nonOrderedPostProcessors is finally invoked, it will call the postProcessBeanFactory of ServletComponentRegisteringPostProcessor, and finally scan the path to get servlet, filter, listener registered by @WebServlet, @WebFilter and @WebListener.

The components scanned here will be injected into the IOC container. However, since the granularity of native listener, filter and servlet is larger than that of spring, these three items will already exist before spring initializes all beans, so bean injection cannot be carried out in this class

The package path is not specified. The default is the current package and its sub packages

The detailed source code analysis process is shown here

Listener test

Define the following controllers for access testing:

  • When the application starts. "======================== context Creation" is printed, indicating that the contextInitialized listening function is triggered
  • Visit“ http://localhost:8888/hello ”, the breakpoint is broken, and the "+ + + + + + + + + + + request listener: create" is printed, indicating that the requestInitialized callback function is triggered
  • Next, "------------------ session created" is printed, indicating that the sessionCreated listener function is triggered
  • Continue to execute request.setAttribute("a", "a");, "----------------- attributeAdded" is printed, indicating that the attributeAdded listening function is triggered
  • Continue to execute request.setAttribute("a", "b"); "----------------- attributeReplaced" is printed, indicating that the attributeReplaced listener function is triggered
  • Continue to complete request.removeAttribute("a"); "----------------- attributeRemoved" is printed, indicating that the attributeRemoved listener function is triggered
  • Continue to execute session.invalidate();, "----------------- session destroyed" is printed, indicating that the sessionDestroyed listening function is triggered
  • After the controller method is executed, "request listener: destroy" is printed, indicating that the requestDestroyed listener function is triggered.
  • When the application is stopped, "================== context destroyed" is printed, indicating that the contextDestroyed listening function is triggered

From the above print results, we can see that the scope range is context greater than request greater than session, but it is not. Because we manually called session.invalidate();, The session will be destroyed. Under normal circumstances, the destruction of a session is controlled by the servlet container according to factors such as session timeout.

Therefore, the normal scope life cycle ServletContext > httpsession > request

In the above breakpoint monitoring test, some redundant monitoring logs will be printed. The Spring Boot system helps us set some attribute addition and deletion settings by default to trigger monitoring. Please ignore them. Just look at the one after the breakpoint is executed and the one before the breakpoint stops

session creation timing

Note: a session is not created as soon as the controller layer is accessed. It is not created by default. It will not be created unless the following code is called:

request.getSession()
perhaps request.getSession(true);The server will generate session. 
If called request.getSession(false);Will not produce session

If we were controller Fill in the method parameters in the layer request perhaps session Object, spring Will automatically inject for us
 For example, if you write HttpSession Then it will help us create one session Object, otherwise there will be no session Object generation

If it is jsp File, the server will servlet Automatically created for you in the file
jsp The default is
HttpSession session=request.getSession(ture);

The writing method of the session will be automatically created

  @GetMapping("aaa")
    public Object test(HttpServletRequest req,HttpSession session)
    {
        String requestURI = req.getRequestURI();
        return requestURI;
    }
  @GetMapping("aaa")
    public Object test(HttpServletRequest req)
    {
        String requestURI = req.getRequestURI();
            HttpSession session = req.getSession();
        return requestURI;
    }

The writing method of the session is not automatically created

  @GetMapping("aaa")
    public Object test(HttpServletRequest req)
    {
        String requestURI = req.getRequestURI();
        return requestURI;
    }
  @GetMapping("aaa")
    public Object test(HttpServletRequest req)
    {
        String requestURI = req.getRequestURI();
            HttpSession session = req.getSession(false);
        return requestURI;
    }

Implementation of Servlet filter

filter

definition

Servlet filter is a Java class that can be used for Servlet Programming. It has the following purposes:

  • Intercept client requests before they access back-end resources.
  • The server's responses are processed before they are sent back to the client.

Usage scenario

In practical application development, we often use filters to do the following things

  • Based on certain authorization logic, HTTP requests are filtered to ensure the security of data access. For example, judge whether the source IP of the request is in the system blacklist
  • For some encrypted HTTP request data, unified decryption is performed to facilitate business processing of back-end resources
  • Or we can filter sensitive words that are often needed by social applications, or we can use filters to filter out illegal requests that trigger sensitive words

The main features of the filter are: first, it can filter all requests; second, it can change the data content of the request.

Implementation of filter

Implementation and registration method 1: using WebFilter annotation configuration

@WebFilter is a new annotation added in servlet 3.0. The original implementation of the filter needs to be configured in web.xml. Now, through this annotation, it will be automatically scanned and registered when starting.

Write Filter class:

//The name of the registrar is customFilter, and the intercepted url is all
@WebFilter(filterName="customFilter",urlPatterns={"/*"})
@Slf4j
public class CustomFilter implements Filter{

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("filter initialization");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        log.info("customFilter Before request processing----doFilter Filter requests before method");
        //Preprocess the request and response
        // For example, set the request code
        // request.setCharacterEncoding("UTF-8");
        // response.setCharacterEncoding("UTF-8");

        //The link is passed directly to the next filter
        chain.doFilter(request, response);

        log.info("customFilter After request processing----doFilter Method to process the response");
    }

    @Override
    public void destroy() {
        log.info("filter Destroy");
    }
}

Then add @ ServletComponentScan annotation to the startup class.

When multiple filters are registered with this method, the order of filter execution cannot be specified.

Originally, when configuring a filter using web.xml, the execution Order can be specified, but when using @ WebFilter, there is no configuration attribute (it needs to be combined with @ Order), so next, I will introduce the filter registration through FilterRegistrationBean.

By default, the filters configured in this annotation mode are sorted according to the first letter

Use the java class name of the filter to make A sequence convention, such as LogFilter and AuthFilter. At this time, AuthFilter will execute before LogFilter because the initial letter A precedes L

Under SpringBoot, use @ WebFilter to configure and pay attention to Filter

Registration method 2: FilterRegistrationBean

FilterRegistrationBean is provided by springboot. This class provides the setOrder method, which can set the sorting value for the filter and let spring sort before registering the web filter and then register in turn.

First, rewrite the Filter. In fact, delete the @ webFilter annotation. Nothing else has changed. The next code is the registration code of Filter

@Configuration
public class FilterRegistration {
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        //Filter can be new or dependency injection Bean
        registration.setFilter(new CustomFilter());
        //Filter name
        registration.setName("customFilter");
        //Intercept path
        registration.addUrlPatterns("/*");
        //Set order
        registration.setOrder(10);
        return registration;
    }
}

When registering multiple, you can register multiple filterregistrationbeans. After startup, the effect is the same as the first one. You can access any resource in the application to test the Filter, because the Filter is for all requests and responses. You can enter the log information in the Filter.

servlet

definition

When java programmers did web development 10 years ago, all requests were received and responded to by servlets. For each request, a servlet is written. This method is very troublesome. We wonder whether we can map to different methods for execution according to different request paths and parameters, so that we can process multiple requests in a servlet class, and each request is a method. Later, this idea gradually developed into structures, spring MVC and other frameworks

Usage scenario

At present, the scenarios of servlet use have been fully covered by the spring MVC encapsulation architecture, and there are few scenarios that need to be developed using the original servlet. However, it is not ruled out that servlet support is required for the migration and integration of old projects to spring boot projects

realization

Let's take a look at how to implement a servlet in spring boot.

@WebServlet(name = "firstServlet", urlPatterns = "/firstServlet") //Mark as a servlet for initiator scanning.
public class FirstServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().append("firstServlet");
    }

}

Then add @ ServletComponentScan annotation to the startup class.

spring interceptor and request link description

Interceptor

There is no concept of interceptor in Servlet specification. It is derived from Spring framework.

Interceptors in Spring have three methods:

  • preHandle represents the control layer method corresponding to the intercepted URL and the custom processing logic before execution
  • postHandle represents the control layer method corresponding to the intercepted URL and the customized processing logic after execution. At this time, the modelAndView has not been rendered.
  • After completion means that modelAndView has rendered the page and executed the custom processing of the interceptor

Core differences between interceptors and filters

From the life cycle of request processing, the functions of Interceptor and filter are similar. Interceptors can do almost everything filters can do.

However, there are some differences between the two usage scenarios:

  • Different specifications: Filter is a component defined in the servlet specification and takes effect in the servlet container. Interceptors are supported by the Spring framework and take effect in the Spring context.
  • Interceptors can get and use beans in the Spring IOC container, but filters can't. Because the filter is a component of the Servlet, and the bean of the IOC container is used in the Spring framework, and the interceptor is derived from the Spring framework.
  • Interceptors can access Spring context value objects, such as ModelAndView, but filters cannot. For the same reason as above.
  • The filter processes the request before entering the servlet container, and the interceptor processes the request within the servlet container. Filters are more granular than interceptors and are more suitable for processing actions of all API s at the system level. For example, for authority authentication, Spring Security uses a lot of filters.
  • Compared with the filter, the interceptor has smaller granularity and is more suitable for unified business logic processing by module and range. For example, audit logs are recorded by module and business. (in the chapter on log management later, we will introduce the use of interceptors to achieve unified access to log records)

For example, we use annotations in Filter to inject a test service, and the result is null. Because the Filter cannot use Spring IOC container bean s.

Implementation of interceptor

Write a custom interceptor class. Here we use a simple example to let you understand the interceptor life cycle. Later, in the chapter of log management, we will introduce the practice of using interceptors to uniformly access log records

@Slf4j
@Component
public class CustomHandlerInterceptor implements HandlerInterceptor {

 @Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
   throws Exception {
  log.info("preHandle:Call before request");
  //If false is returned, interrupt is requested
  return true;
 }

 @Override
 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
   ModelAndView modelAndView) throws Exception {
  log.info("postHandle:Call after request");

 }

 @Override
 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
   throws Exception {
  log.info("afterCompletion:The request calls the post completion callback method, that is, the callback after the view rendering is completed");

 }

}

Register interceptors by inheriting WebMvcConfigurerAdapter. After writing, the author found that the WebMvcConfigurerAdapter class has been abandoned. Please implement the WebMvcConfigurer interface to complete the registration of the interceptor

@Configuration
//Obsolete: public class mywebmvcconfigurer extensions webmvcconfigureradapter{
public class MyWebMvcConfigurer implements WebMvcConfigurer {
  

  @Resource
  CustomHandlerInterceptor customHandlerInterceptor;

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
     //Register interceptor interception rules
    //When multiple interceptors are added, they are executed in the order of addition
    registry.addInterceptor(customHandlerInterceptor).addPathPatterns("/*");
  }
   

}

If we inject a test service into the CustomHandlerInterceptor, the result is that we can correctly rely on the injection and use the service.

Request link description

Arbitrarily request an API in the system (because the filter interceptor we configured intercepts all requests), and analyze the execution order of each interface function in the interceptor and filter through the output results.

CustomFilter  : customFilter Before request processing----doFilter Filter requests before method
CustomHandlerInterceptor  : preHandle:Call before request
CustomHandlerInterceptor  : postHandle:Call after request
CustomHandlerInterceptor  : afterCompletion:The request calls the post completion callback method, that is, the callback after the view rendering is completed
CustomFilter  : customFilter After request processing----doFilter Method to process the response

The sequence diagram of requesting link calls is as follows:

Publishing and listening of custom events

Introduction to event monitoring

Role of event listener

First, we need to understand several roles in event monitoring

  • Event publisher (i.e. event source)
  • Event listener
  • Event itself

Usage scenario of event listening

In order to simplify the technical problems, let me give you a simple example. For example, the neighborhood committee issues a water cut-off notice. The neighborhood committee is the source of the event, the water cut-off is the event itself, and the residents under the jurisdiction of the neighborhood committee are the event monitors. Let's look at this example, which has the following characteristics:

  • Asynchronous processing: after the neighborhood committee staff issues the notice, they can be busy with other work and will not wait for the feedback of all residents.
  • Decoupling: the neighborhood committee and residents are decoupled and do not interfere with each other's working and living conditions.
  • Irregularity: the frequency of water cut-off events is irregular, and the triggering rules are relatively random.

When you meet two of the above characteristics in the business requirements of a system, you should consider using the event monitoring mechanism to realize the business requirements. Of course, there are many ways to realize the event monitoring mechanism, such as:

  • Publish subscribe mode using Message Queuing Middleware
  • java.util.EventListener provided with JDK
  • This section introduces the method of event publishing and listening in Spring environment

Code implementation

Custom event

Inherit from the ApplicationEvent abstract class, and then define your own constructor.

@SuppressWarnings("serial")
public class MyEvent extends ApplicationEvent
{
 public MyEvent(Object source)
 {
  super(source);
 }
}

Custom event listener

There are four ways for springboot to listen to events

  • 1. Write code to add listeners to ApplicationContext
  • 2. Use the Component annotation to load the listener into the spring container
  • 3. Configure the listener in application.properties
  • 4. Realize event listening through @ EventListener annotation

Mode 1

First create the MyListener1 class

@Slf4j
public class MyListener1 implements ApplicationListener<MyEvent> {
    public void onApplicationEvent(MyEvent event) {
        log.info(String.format("%s Listen to event source:%s.", MyListener1.class.getName(), event.getSource()));
    }
}

Then obtain the ConfigurableApplicationContext context in the springboot application startup class and load the listener

@SpringBootApplication
public class BootLaunchApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(BootLaunchApplication.class, args);
        //Load listening
        context.addApplicationListener(new MyListener1());
    }
}

Mode 2 (recommended)

Create the MyListener2 class and load it into the spring container using the @ Component annotation

@Component
@Slf4j
public class MyListener2 implements ApplicationListener<MyEvent> {

    public void onApplicationEvent(MyEvent event) {
        log.info(String.format("%s Listen to event source:%s.", MyListener2.class.getName(), event.getSource()));
    }

}

Mode 3

First create the MyListener3 class

@Slf4j
public class MyListener3 implements ApplicationListener<MyEvent> {
    public void onApplicationEvent(MyEvent event) {
        log.info(String.format("%s Listen to event source:%s.", MyListener3.class.getName(), event.getSource()));
    }
}

Then configure listening in application.properties

context:
  listener:
    classes: com.zimug.bootlaunch.customlistener.MyListener3

Mode 4 (recommended)

Create the MyListener4 class, which does not need to implement the ApplicationListener interface and uses @ EventListener to decorate the specific method

@Slf4j
@Component
public class MyListener4 {
    @EventListener
    public void listener(MyEvent event) {
        log.info(String.format("%s Listen to event source:%s.", MyListener4.class.getName(), event.getSource()));
    }
}

Release of test listening events

With applicationContext, you can publish events wherever you want

@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomListenerTest {

    @Resource private
    ApplicationContext applicationContext;

    @Test
    public void testEvent(){
        applicationContext.publishEvent(new MyEvent("Test event."));
    }
}

After startup, the log is printed as follows. (the following screenshot is the screenshot after the startup class publishes the event. In the unit test, listener 1 can't listen, and there is a problem with the execution sequence):

From the log printing, we can see that the implementation of the four events of SpringBoot is in order. No matter how many times it is executed, it is in this order.

From the log printing, we can see that the implementation of the four events of SpringBoot is in order. No matter how many times it is executed, it is in this order.

In depth understanding of Spring event publishing and listening

Application initiated listening

brief introduction

Spring Boot provides two interfaces: CommandLineRunner and ApplicationRunner, which are used for special processing when starting the application. These codes will be executed before the run() method of spring application is completed.

Compared with the application listener interface of Spring and the ServletContextListener listener of Servlet introduced in the previous chapter.

The advantage of using both is that you can easily use the application startup parameters and do different initialization operations according to different parameters.

Introduction to common scenarios

Implements CommandLineRunner and ApplicationRunner interfaces. It is usually used for special code execution before application startup, such as:

  • Load the data commonly used by the system into memory
  • Apply garbage data cleanup from the last run
  • Sending of notification after successful system startup

As shown in the figure below, I implemented the CommandLineRunner interface to load the commonly used configuration data in the system when the application starts. When using the data from the database to memory in the future, I only need to call the getSysConfigList method instead of loading the data in the database every time. It saves system resources and reduces data loading time.

Code experiment

Implemented by @ Component definition

CommandLineRunner: the parameter is an array of strings

@Slf4j
@Component
public class CommandLineStartupRunner implements CommandLineRunner {
    @Override
    public void run(String... args){
        log.info("CommandLineRunner Incoming parameters:{}", Arrays.toString(args));
    }
}

ApplicationRunner: the parameters are put into ApplicationArguments, and the parameters are obtained through getOptionNames(), getOptionValues(), and getSourceArgs()

@Slf4j
@Component
public class AppStartupRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args)  {
        log.info("ApplicationRunner Parameter name: {}", args.getOptionNames());
        log.info("ApplicationRunner Parameter value: {}", args.getOptionValues("age"));
        log.info("ApplicationRunner parameter: {}", Arrays.toString(args.getSourceArgs()));
    }
}

Implemented by @ Bean definition

In this way, you can specify the execution order. Note that the first two beans are CommandLineRunner and the last Bean is ApplicationRunner.

@Configuration
public class BeanRunner {
    @Bean
    @Order(1)
    public CommandLineRunner runner1(){
        return new CommandLineRunner() {
            @Override
            public void run(String... args){
                System.out.println("BeanCommandLineRunner run1()" + Arrays.toString(args));
            }
        };
    }

    @Bean
    @Order(2)
    public CommandLineRunner runner2(){
        return new CommandLineRunner() {
            @Override
            public void run(String... args){
                System.out.println("BeanCommandLineRunner run2()" + Arrays.toString(args));
            }
        };
    }

    @Bean
    @Order(3)
    public ApplicationRunner runner3(){
        return new ApplicationRunner() {
            @Override
            public void run(ApplicationArguments args){
                System.out.println("BeanApplicationRunner run3()" + Arrays.toString(args.getSourceArgs()));
            }
        };
    }
}

You can set the execution Order through @ Order

Perform test

Add the following parameters to the startup configuration and start the application after saving

Test output results:

c.z.boot.launch.config.AppStartupRunner  : ApplicationRunner Parameter name: [name, age]
c.z.boot.launch.config.AppStartupRunner  : ApplicationRunner Parameter value: [18]
c.z.boot.launch.config.AppStartupRunner  : ApplicationRunner parameter: [--name=zimug, --age=18]

BeanApplicationRunner run3()[--name=zimug, --age=18]

c.z.b.l.config.CommandLineStartupRunner  : CommandLineRunner Incoming parameters:[--name=zimug, --age=18]
BeanCommandLineRunner run1()[--name=zimug, --age=18]
BeanCommandLineRunner run2()[--name=zimug, --age=18]

From the test results (the author is not sure whether this priority order is normal at present, but from my multiple test results, the order has always been like this):

  • ApplicationRunner takes precedence over CommandLineRunner
  • The priority of the Runner running in the form of Bean is lower than that of the Component annotation plus the implements Runner interface
  • The Order annotation can only guarantee the execution Order of similar CommandLineRunner or ApplicationRunner, and cannot guarantee the Order across classes

summary

The core usage of CommandLineRunner and ApplicationRunner is the same, which is used for special code execution before application startup. The execution order of ApplicationRunner precedes that of CommandLineRunner; ApplicationRunner encapsulates parameters into objects and provides methods to obtain parameter names and values, which will be more convenient in operation.

Problem summary

This is the real problem encountered by the author in practice, that is, I have defined the implementation of multiple CommandLineRunner. The strange problem is that when you define multiple implementations of CommandLineRunner, one or more of them will not be executed.

Analysis: the following code is the code that will be executed after the SpringBootApplication starts the project. Let's see that the code starts CommandLineRunner or ApplicationRunner through a process. In other words, the next CommandLineRunner will be executed only after the execution of the previous CommandLineRunner is completed. It is executed synchronously.

private void callRunners(ApplicationContext context, ApplicationArguments args) {
		List<Object> runners = new ArrayList<>();
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		AnnotationAwareOrderComparator.sort(runners);
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}

Therefore, if a synchronous blocking API or a while(true) loop is invoked in a CommandLineRunner implementation run method body, other implementations after the CommandLineRunner in traversal will not be executed.