Spring boot learning: the configuration method of Filter and the implementation principle of spring boot source code

Posted by TristanOfADown on Thu, 11 Jun 2020 04:25:00 +0200

Registration of Servlet, Filter, Listener

  • In the case of spring boot application, because it starts a Servlet engine by itself, and needs to create a ServletContext object associated with the application to bind to the Servlet engine, so that the Servlet engine can receive the request and distribute it to the application for processing.
  • ServletContext usually contains servlets, filters, listeners and other components in the Servlet specification. To register these components to ServletContext, there are three steps to complete in SpringBoot, namely:
    • Define and configure components in application code;
    • Start the application, obtain these components, and generate the corresponding BeanDefinition to register in the Spring container;
    • Take out these component beans from the Spring container (BeanFactory calls getBean method during the extraction process, and create Bean object based on BeanDefinition) and bind them to the ServletContext.

Application code configuration method

1. The @ Bean annotation method of the registration Bean implementation class combined with the @ Configuration annotation class

  • RegistrationBean is an abstract class provided by SpringBoot and an implementation class of the ServletContextInitializer interface. Therefore, when the application starts to create the embedded ServletContext corresponding to the application, it will obtain the loaded ServletContextInitializer interface implementation class object from the Spring container, and then initialize the ServletContext.

  • ServletRegistrationBean: the ServletContext ා addservlet (string, servlet) method equivalent to Servlet 3.0 +, which is mainly used to customize the dispatcherServlet, such as modifying the processed url. By default, all requests containing "/" are processed by the dispatcherServlet, which can be modified by the setUrlMappings method. The class definition is as follows:
    /**
     * A {@link ServletContextInitializer} to register {@link Servlet}s in a Servlet 3.0+
     * container. Similar to the {@link ServletContext#addServlet(String, Servlet)
     * registration} features provided by {@link ServletContext} but with a Spring Bean
     * friendly design.
     * <p>
     * The {@link #setServlet(Servlet) servlet} must be specified before calling
     * {@link #onStartup}. URL mapping can be configured used {@link #setUrlMappings} or
     * omitted when mapping to '/*' (unless
     * {@link #ServletRegistrationBean(Servlet, boolean, String...) alwaysMapUrl} is set to
     * {@code false}). The servlet name will be deduced if not specified.
     *
     * @param <T> the type of the {@link Servlet} to register
     * @author Phillip Webb
     * @since 1.4.0
     * @see ServletContextInitializer
     * @see ServletContext#addServlet(String, Servlet)
     */
    public class ServletRegistrationBean<T extends Servlet>
    		extends DynamicRegistrationBean<ServletRegistration.Dynamic> {
    
    	private static final String[] DEFAULT_MAPPINGS = { "/*" };
    
    	private T servlet;
    
    	private Set<String> urlMappings = new LinkedHashSet<>();
    
    	private boolean alwaysMapUrl = true;
    
    	private int loadOnStartup = -1;
    
    	private MultipartConfigElement multipartConfig;
    	
    	...
    	
    }
    

     

  • FilterRegistrationBean: the ServletContext ා addfilter (string, Filter) method equivalent to Servlet 3.0 +, which is mainly used to customize the Filter filter to add to the current ServletContext and Filter the request. The class definition is as follows:
    /**
     * A {@link ServletContextInitializer} to register {@link Filter}s in a Servlet 3.0+
     * container. Similar to the {@link ServletContext#addFilter(String, Filter) registration}
     * features provided by {@link ServletContext} but with a Spring Bean friendly design.
     * <p>
     * The {@link #setFilter(Filter) Filter} must be specified before calling
     * {@link #onStartup(ServletContext)}. Registrations can be associated with
     * {@link #setUrlPatterns URL patterns} and/or servlets (either by {@link #setServletNames
     * name} or via a {@link #setServletRegistrationBeans ServletRegistrationBean}s. When no
     * URL pattern or servlets are specified the filter will be associated to '/*'. The filter
     * name will be deduced if not specified.
     *
     * @param <T> the type of {@link Filter} to register
     * @author Phillip Webb
     * @since 1.4.0
     * @see ServletContextInitializer
     * @see ServletContext#addFilter(String, Filter)
     * @see DelegatingFilterProxyRegistrationBean
     */
    public class FilterRegistrationBean<T extends Filter>
    		extends AbstractFilterRegistrationBean<T> {
    
    	/**
    	 * Filters that wrap the servlet request should be ordered less than or equal to this.
    	 * @deprecated since 2.1.0 in favor of
    	 * {@code OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER}
    	 */
    	@Deprecated
    	public static final int REQUEST_WRAPPER_FILTER_MAX_ORDER = AbstractFilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER;
    
    	private T filter;
    	
    	...
    	
    }
    

     

  • ServletListenerRegistrationBean: the ServletContext ා addListener (EventListener) method equivalent to Servlet 3.0 +, which is used to register the Listener related to the Servlet specification. The interface is defined as follows:

    /**
     * A {@link ServletContextInitializer} to register {@link EventListener}s in a Servlet
     * 3.0+ container. Similar to the {@link ServletContext#addListener(EventListener)
     * registration} features provided by {@link ServletContext} but with a Spring Bean
     * friendly design.
     *
     * This bean can be used to register the following types of listener:
     * <ul>
     * <li>{@link ServletContextAttributeListener}</li>
     * <li>{@link ServletRequestListener}</li>
     * <li>{@link ServletRequestAttributeListener}</li>
     * <li>{@link HttpSessionAttributeListener}</li>
     * <li>{@link HttpSessionListener}</li>
     * <li>{@link ServletContextListener}</li>
     * </ul>
     *
     * @param <T> the type of listener
     * @author Dave Syer
     * @author Phillip Webb
     * @since 1.4.0
     */
    public class ServletListenerRegistrationBean<T extends EventListener>
    		extends RegistrationBean {
    
    	private static final Set<Class<?>> SUPPORTED_TYPES;
    
    	static {
    		Set<Class<?>> types = new HashSet<>();
    		types.add(ServletContextAttributeListener.class);
    		types.add(ServletRequestListener.class);
    		types.add(ServletRequestAttributeListener.class);
    		types.add(HttpSessionAttributeListener.class);
    		types.add(HttpSessionListener.class);
    		types.add(ServletContextListener.class);
    		SUPPORTED_TYPES = Collections.unmodifiableSet(types);
    	}
    
    	private T listener;
    	
    	...
    	
    }
    

     

  • Use example: IP white list filter

    @SpringBootApplication
    public class LogWebApplication {
    
        /**
         * IP White list come here
         * @param redisTemplate
         * @return
         */
        @Bean
        public FilterRegistrationBean ipFilter(RedisTemplate redisTemplate) {
            FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
            Filter filter = new IpWhiteListFilter(redisTemplate);
            filterRegistrationBean.setFilter(filter);
            filterRegistrationBean.addUrlPatterns(ipWhiteList);
            return filterRegistrationBean;
        }
    
        public static void main(String[] args) {
            SpringApplication.run(LogWebApplication.class, args);
        }
    }
    
    public class IpWhiteListFilter extends GenericFilterBean {
    
        private RedisTemplate<String, Object> redisTemplate;
    
        @Autowired
        public IpWhiteListFilter(RedisTemplate<String, Object> redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
                throws IOException, ServletException {
    
            HttpServletResponse response = (HttpServletResponse)servletResponse;
            HttpServletRequest request = (HttpServletRequest)servletRequest;
            
            String ip = HttpUtils.getIpAddr(request);
            if (!validIp(ip)) {
                response.setStatus(FORBIDDEN);
                LOG.warn("forbidden ip {} for {}", ip, uri);
                return;
            }
            
            filterChain.doFilter(request, servletResponse);
            } catch (Exception e) {
                LOG.error("doFilter", e);
                response.setStatus(BAD_REQUEST);
                return;
            }
        }
        
        private boolean validIp(String ip) {
            ...
        }
    }
    

     

2. Annotation @ WebServlet, @ WebFilter, @ WebListener and annotation scanning @ ServletComponentScan of jsr-330

  • You can use @ WebServlet, @ WebFilter, @ WebListener annotation to apply to the corresponding component classes. Note that the implementation of component classes is based on the Servlet specification, such as the implementation of Filter interface, ServletListener interface, etc. Then you need to add the @ ServletComponentScan annotation to the @ Configuration annotation's Configuration class to scan the @ WebServlet, @ WebFilter, @ WebListener annotation's classes to register in the Spring container.

  • When the BeanDefinition is created and registered to the Spring container, the implementation classes of RegistrationBean, namely ServletRegistrationBean, FilterRegistrationBean, ServletListenerRegistrationBean, are also used to encapsulate the components. Therefore, they are also implemented based on RegistrationBean. Only Spring boot completes the encapsulation internally, and does not need to be used in the application code in the same way as the above The implementation classes of the above three registrationbeans are used for operation, defined with @ WebServlet, @ WebFilter, @ WebListener annotation, and scanned with @ ServletComponentScan.

  • Use example

    @SpringBootApplication
    @ServletComponentScan // Make @ WebListener effective
    public class LogWebApplication {
    
        /**
         * IP White list come here
         * @param redisTemplate
         * @return
         */
        @Bean
        public FilterRegistrationBean ipFilter(RedisTemplate redisTemplate) {
            FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
            Filter filter = new IpWhiteListFilter(redisTemplate);
            filterRegistrationBean.setFilter(filter);
            filterRegistrationBean.addUrlPatterns(ipWhiteList);
            return filterRegistrationBean;
        }
    
        public static void main(String[] args) {
            SpringApplication.run(LogWebApplication.class, args);
        }
    }
    
    /**
     * @author xyz
     * @date 11/11/2018 22:03
     * @description: netty Service start listener
     */
    @WebListener
    public class NettyServerListener implements ServletContextListener {
        private static final Logger LOG = LoggerFactory.getLogger(NettyServerListener.class);
    
        @Autowired
        private NettyServer nettyServer;
    
        @Override
        public void contextInitialized(ServletContextEvent servletContextEvent) {
            LOG.info("NettyServerListener: spring context inited.");
            Thread nettyServerThread = new Thread(new NettyServerThread());
            nettyServerThread.start();
        }
    
        @Override
        public void contextDestroyed(ServletContextEvent servletContextEvent) {
            LOG.info("NettyServerListener: spring context closed.");
        }
    
        /**
         * netty Service startup thread
         */
        private class NettyServerThread implements Runnable {
    
            @Override
            public void run() {
                nettyServer.start();
            }
        }
    }
    

     

Spring boot internal source implementation

The start method refresh of ApplicationContext

 

  • The Spring container defines the startup steps through the refresh method of ApplicationContext.
    @Override
    public void refresh() throws BeansException, IllegalStateException {
    	synchronized (this.startupShutdownMonitor) {
    		// Prepare this context for refreshing.
    		prepareRefresh();
    
    		// Tell the subclass to refresh the internal bean factory.
    		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
    		// Prepare the bean factory for use in this context.
    		prepareBeanFactory(beanFactory);
    
    		try {
    			// Allows post-processing of the bean factory in context subclasses.
    			postProcessBeanFactory(beanFactory);
    
    			// Invoke factory processors registered as beans in the context.
    			invokeBeanFactoryPostProcessors(beanFactory);
    
    			// Register bean processors that intercept bean creation.
    			registerBeanPostProcessors(beanFactory);
    
    			// Initialize message source for this context.
    			initMessageSource();
    
    			// Initialize event multicaster for this context.
    			initApplicationEventMulticaster();
    
    			// Initialize other special beans in specific context subclasses.
    			onRefresh();
    
    			// Check for listener beans and register them.
    			registerListeners();
    
    			// Instantiate all remaining (non-lazy-init) singletons.
    			finishBeanFactoryInitialization(beanFactory);
    
    			// Last step: publish corresponding event.
    			finishRefresh();
    		}
    
    		...
    		
    	}
    }
    

     

  • In sequence:
    • obtainFreshBeanFactory: create BeanFactory and load beandefinition;
    • invokeBeanFactoryPostProcessors: call BeanFactoryPostProcessor, that is, BeanFactory postprocessor. This is where ComponentScan scans related classes. The above two methods are used to create Servlet, Filter and Listener. The creation and registration of corresponding BeanDefinition to BeanFactory are completed in this step;
    • onRefresh: complete the creation of bean instances with special functions. Getting the BeanDefinition corresponding to the Servlet, Filter and Listener from BeanFactory, creating the bean object instance, and then binding to the ServletContext is completed in this step.

Create and register the Servlet, Filter and Listener corresponding BeanDefinition to BeanFactory

  • The RegistrationBean implementation class combines the @ Bean method implementation of the @ Configuration annotation class:

    • This method is the same as the common way of creating a beandefinitioin through the @ Configuration annotation Configuration class and the @ Bean annotation method, and then registering to BeanFactory. They are all processed through the BeanFactory postprocessor, ConfigurationClassPostProcessor. For details, please refer to my analysis of another article: Spring's internal source code implementation of class Configuration based on @ Configuration

    • The special feature here is that the registered Bean types are: ServletRegistrationBean, FilterRegistrationBean, ServletListenerRegistrationBean, that is, the return value type of @ Bean annotation method is one of the above four types.
  • Annotation @ WebServlet, @ WebFilter, @ WebListener of JSR-330 and annotation scanning @ ServletComponentScan:

    • This method is mainly implemented through a beanfactory postprocessor provided by SpringBoot: servletcomponentregisteringpost processor.

    • ServletComponentRegisteringPostProcessor is registered to Spring container: it is mainly introduced in @ ServletComponentScan. Specifically, @ ServletComponentScan annotation uses @ Import to introduce servletcomponentscanregister, while servletcomponentscanregister is responsible for registering ServletComponentRegisteringPostProcessor to BeanFactoryPostProcessor of Spring container.

      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Import(ServletComponentScanRegistrar.class)
      public @interface ServletComponentScan {
         ...
      }
      
      class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
      
      	private static final String BEAN_NAME = "servletComponentRegisteringPostProcessor";
      
      	@Override
      	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
      			BeanDefinitionRegistry registry) {
      		Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
      		if (registry.containsBeanDefinition(BEAN_NAME)) {
      			updatePostProcessor(registry, packagesToScan);
      		}
      		else {
      			addPostProcessor(registry, packagesToScan);
      		}
      	}
      	private void addPostProcessor(BeanDefinitionRegistry registry,
      				Set<String> packagesToScan) {
      			GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
      			// Register ServletComponentRegisteringPostProcessor
      			beanDefinition.setBeanClass(ServletComponentRegisteringPostProcessor.class);
      			beanDefinition.getConstructorArgumentValues()
      					.addGenericArgumentValue(packagesToScan);
      			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      			registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
      	}
      	
      }
      

       

    • ServletComponentRegisteringPostProcessor: scan the package specified by @ ServletComponentScan or the package and its subpackages of the class containing the annotation if not specified, obtain the class annotated with @ WebServlet, @ WebFilter or @ WebListener, create beandefinition, process beandefinition by the handler of the annotation, and then register it to BeanFactory; the core method is scanpackag e: Scan the specified package path,
       

      private void scanPackage(
      		ClassPathScanningCandidateComponentProvider componentProvider,
      		String packageToScan) {
      		
          // componentProvider.findCandidateComponents(packageToScan) method:
          // Find and create beandefinitions collection, but not registered in BeanFactory
      	for (BeanDefinition candidate : componentProvider
      			.findCandidateComponents(packageToScan)) {
      			
      		if (candidate instanceof ScannedGenericBeanDefinition) {
      			for (ServletComponentHandler handler : HANDLERS) {
      			
      			    // The annotation handler completes the processing and registers it to BeanFactory
      				handler.handle(((ScannedGenericBeanDefinition) candidate),
      						(BeanDefinitionRegistry) this.applicationContext);
      			}
      		}
      	}
      }
      

      HANDLERS are defined as follows:
       

      static {
      	List<ServletComponentHandler> servletComponentHandlers = new ArrayList<>();
      	servletComponentHandlers.add(new WebServletHandler());
      	servletComponentHandlers.add(new WebFilterHandler());
      	servletComponentHandlers.add(new WebListenerHandler());
      	HANDLERS = Collections.unmodifiableList(servletComponentHandlers);
      }
      

       

    • For each annotation, there is a corresponding handler to register to BeanFactory, specifically: WebServletHandler, WebFilterHandler, and WebListenerHandler.

    • Take WebFilterHandler as an example: it can be seen that the implementation class of Filter interface is guaranteed by using FilterRegistrationBean internally:
       

      /**
       * Handler for {@link WebFilter}-annotated classes.
       *
       * @author Andy Wilkinson
       */
      class WebFilterHandler extends ServletComponentHandler {
      
      	WebFilterHandler() {
      		super(WebFilter.class);
      	}
      
      	@Override
      	public void doHandle(Map<String, Object> attributes,
      			ScannedGenericBeanDefinition beanDefinition,
      			BeanDefinitionRegistry registry) {
      		
      		// Specify FilterRegistrationBean
      		
      		BeanDefinitionBuilder builder = BeanDefinitionBuilder
      				.rootBeanDefinition(FilterRegistrationBean.class);
      		builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported"));
      		builder.addPropertyValue("dispatcherTypes", extractDispatcherTypes(attributes));
      		builder.addPropertyValue("filter", beanDefinition);
      		builder.addPropertyValue("initParameters", extractInitParameters(attributes));
      		String name = determineName(attributes, beanDefinition);
      		builder.addPropertyValue("name", name);
      		builder.addPropertyValue("servletNames", attributes.get("servletNames"));
      		builder.addPropertyValue("urlPatterns", extractUrlPatterns(attributes));
      		
      		// Create a BeanDefinition and register it with BeanFactory
      		
      		registry.registerBeanDefinition(name, builder.getBeanDefinition());
      	}
      	
      	...
      	
      }
      

       

Create the bean object instance of Servlet, Filter and Listener and bind to the ServletContext

  • From the above analysis, after the refresh method of ApplicationContext calls all BeanFactoryPostProcessor, it will call the onRefresh method to register the bean object with special meaning in this method.

  • Create and start the application embedded Servlet engine WebServer, create the embedded ServletContext object and bind it to WebServer, create the Servlet, and bind the bean object corresponding to Filter and Listener to the ServletContext in the onRefresh method of the servletwebserver ApplicationContext class. The implementation of the onRefresh method is as follows:
     

    @Override
    protected void onRefresh() {
    	super.onRefresh();
    	try {
    		createWebServer();
    	}
    	catch (Throwable ex) {
    		throw new ApplicationContextException("Unable to start web server", ex);
    	}
    }
    
    @Override
    protected void finishRefresh() {
    	super.finishRefresh();
    	WebServer webServer = startWebServer();
    	if (webServer != null) {
    		publishEvent(new ServletWebServerInitializedEvent(webServer, this));
    	}
    }
    
    private void createWebServer() {
    	WebServer webServer = this.webServer;
    	ServletContext servletContext = getServletContext();
    	if (webServer == null && servletContext == null) {
    		ServletWebServerFactory factory = getWebServerFactory();
    		
    		// Create webServer, ServletContext,
    		// And get the ServletContextInitializer of the ServletContext
    		// And execute its onStartup method
    		this.webServer = factory.getWebServer(getSelfInitializer());
    	}
    	else if (servletContext != null) {
    		try {
    			getSelfInitializer().onStartup(servletContext);
    		}
    		catch (ServletException ex) {
    			throw new ApplicationContextException("Cannot initialize servlet context",
    					ex);
    		}
    	}
    	initPropertySources();
    }
    
    // getSelfInitializer calls this method
    // Get the implementation class of ServletContextInitializer interface from BeanFactory
    private void selfInitialize(ServletContext servletContext) throws ServletException {	
    	prepareWebApplicationContext(servletContext);
    	registerApplicationScope(servletContext);
    	WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),
    			servletContext);
    	// Implemented inside the getServletContextInitializerBeans method:
        // Create a bean object instance from BeanDefiniton, specifically the getBean calling BeanFactory
    	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
    		beans.onStartup(servletContext);
    	}
    }
    

     

  • From the above source code, in selfInitialize method, call getServletContextInitializerBeans method to get the implementation class of ServletContextInitializer interface from BeanFactory, create bean object instance and execute onStartup method.
  • Registration bean implements the ServletContextInitializer interface. The onStartup method of RegistrationBean is implemented as follows: the register method is implemented by the subclass to complete the business logic. The Servlet, Filter and Listener related to the Servlet specification are bound to the ServletContext.
     

    public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
    
    	private boolean enabled = true;
    
    	@Override
    	public final void onStartup(ServletContext servletContext) throws ServletException {
    		String description = getDescription();
    		if (!isEnabled()) {
    			logger.info(StringUtils.capitalize(description)
    					+ " was not registered (disabled)");
    			return;
    		}
    		// Register the current bean object to servletContext
    		register(description, servletContext);
    	}
    
        // Abstract methods, implemented by subclasses
        
    	/**
    	 * Register this bean with the servlet context.
    	 * @param description a description of the item being registered
    	 * @param servletContext the servlet context
    	 */
    	protected abstract void register(String description, ServletContext servletContext);
    
        ...
        
    }
    

    Take the register method implementation of ServletListenerRegistrationBean as an example: call servletContext.addListener Complete binding:
     

    @Override
    protected void register(String description, ServletContext servletContext) {
    	try {
    		servletContext.addListener(this.listener);
    	}
    	catch (RuntimeException ex) {
    		throw new IllegalStateException(
    				"Failed to add listener '" + this.listener + "' to servlet context",
    				ex);
    	}
    }
    

     


 

Topics: Spring SpringBoot Netty less