Revisit the interception, filtering and listening in the life cycle of SpringBoot series
- Servlet domain object and attribute change monitoring
- Implementation of Servlet filter
- servlet
- spring interceptor and request link description
- Publishing and listening of custom events
- Application initiated listening
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.