Servlet,Filter,Listener的注册

  • 在SpringBoot应用来说,由于是自身启动了一个Servlet引擎,并且需要创建一个与应用关联ServletContext对象绑定到Servlet引擎,从而使得Servlet引擎接收到请求可以分发到该应用来处理。
  • ServletContext内部通常会包含Servlet规范中的Servlet,Filter,Listener等组件,而将这些组件注册到ServletContext,在SpringBoot中主要通过三步来完成,分别是:
  • 在应用代码定义和配置组件;
  • 应用启动,获取这些组件,并生成对应的BeanDefinition注册到Spring容器;
  • 从Spring容器取出这些组件Bean(取出过程中完成有BeanFactory调用getBean方法,基于BeanDefinition完成Bean对象的创建)并绑定到该ServletContext中。

应用代码配置方法

1. RegistrationBean实现类结合@Configuration注解的类的@Bean注解方法

  • RegistrationBean是SpringBoot提供的一个抽象类,是ServletContextInitializer接口的实现类,故在应用启动创建应用对应的内嵌的ServletContext时,会从Spring容器获取已经加载好的ServletContextInitializer接口实现类对象,然后对ServletContext进行初始化。
  • ServletRegistrationBean:相当于Servlet 3.0+的ServletContext#addServlet(String, Servlet)方法,主要用于对dispatcherServlet进行自定义,如对处理的url进行修改,默认为所有包含“/”的请求都由dispatcherServlet处理,可以通过setUrlMappings方法来修改,类定义如下:
/**
 * 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:相当于Servlet 3.0+的ServletContext#addFilter(String, Filter)方法,主要用于自定义Filter过滤器来添加到当前的ServletContext,对请求进行过滤,类定义如下:
/**
 * 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:相当于Servlet 3.0+的ServletContext#addListener(EventListener)方法,用于注册Servlet规范相关的Listener,接口定义如下:
/**
 * 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;
	
	...
	
}
  • 使用示例:IP白名单过滤器
@SpringBootApplication
public class LogWebApplication {

    /**
     * IP白名单过来
     * @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. JSR-330的注解@WebServlet,@WebFilter,@WebListener与注解扫描@ServletComponentScan

  • 可以使用@WebServlet,@WebFilter,@WebListener注解运用在对应的组件类上面,注意组件类自身实现基于Servlet规范,如实现Filter接口,ServletListener接口等。然后需要在@Configuration注解的配置类中,加上@ServletComponentScan注解,用于扫描@WebServlet,@WebFilter,@WebListener这些注解的类来注册到Spring容器中。
  • 其中创建BeanDefinition注册到Spring容器时,也是使用RegistrationBean的实现类,即ServletRegistrationBean,FilterRegistrationBean,ServletListenerRegistrationBean,对组件进行封装的,故也是基于RegistrationBean实现的,只是SpringBoot在内部完成封装,而不需要像上面一种方式一样在应用代码显示使用以上三个RegistrationBean的实现类进行操作,使用@WebServlet,@WebFilter,@WebListener注解定义,使用@ServletComponentScan来扫描即可。
  • 使用示例
@SpringBootApplication
@ServletComponentScan // 使@WebListener生效
public class LogWebApplication {

    /**
     * IP白名单过来
     * @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服务启动监听器
 */
@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服务启动线程
     */
    private class NettyServerThread implements Runnable {

        @Override
        public void run() {
            nettyServer.start();
        }
    }
}

SpringBoot内部源码实现

ApplicationContext的启动方法refresh

 

  • Spring容器是通过ApplicationContext的refresh方法来定义启动步骤的。
@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();
		}

		...
		
	}
}
  • 依次按顺序执行:
  • obtainFreshBeanFactory:创建BeanFactory和加载BeanDefintion;
  • invokeBeanFactoryPostProcessors:调用BeanFactoryPostProcessor,即BeanFactory后置处理器。ComponentScan进行相关类扫描是在这里完成的。以上两种方法创建Servlet,Filter和Listener,对应的BeanDefinition的创建并注册到BeanFactory是在这步完成的;
  • onRefresh:完成有特殊功能的bean实例的创建。从BeanFactory获取Servlet,Filter和Listener对应的BeanDefinition并创建Bean对象实例,然后绑定到ServletContext是在这步完成的。

Servlet,Filter和Listener,对应的BeanDefinition的创建并注册到BeanFactory

  • RegistrationBean实现类结合@Configuration注解的类的@Bean方法实现:
  • 这种方式跟普通的,通过@Configuration注解配置类和@Bean注解方法来创建BeanDefintioin,然后注册到BeanFactory的实现方式一样,都是通过ConfigurationClassPostProcessor这个BeanFactoryPostProcessor来处理,具体可看我的另外一篇文章分析:Spring基于@Configuration的类配置的内部源码实现
  • 在这里的特殊之处为注册的Bean类型为:ServletRegistrationBean,FilterRegistrationBean,ServletListenerRegistrationBean,即@Bean注解的方法返回值类型为以上四种类型之一。
  • JSR-330的注解@WebServlet,@WebFilter,@WebListener与注解扫描@ServletComponentScan:
  • 这种方式主要是通过SpringBoot提供的一个BeanFactoryPostProcessor实现:ServletComponentRegisteringPostProcessor。
  • ServletComponentRegisteringPostProcessor注册到Spring容器:主要是在@ServletComponentScan中引入,具体为@ServletComponentScan注解使用@Import引入了ServletComponentScanRegistrar,而ServletComponentScanRegistrar负责注册ServletComponentRegisteringPostProcessor到Spring容器的BeanFactoryPostProcessor。
@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();
			// 注册ServletComponentRegisteringPostProcessor
			beanDefinition.setBeanClass(ServletComponentRegisteringPostProcessor.class);
			beanDefinition.getConstructorArgumentValues()
					.addGenericArgumentValue(packagesToScan);
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
	}
	
}
  • ServletComponentRegisteringPostProcessor:扫描@ServletComponentScan指定的包或者没指定则扫描包含这个注解的类所在的包及其子包,获取使用了@WebServlet,@WebFilter或@WebListener注解的类,并创建BeanDefintion,并由注解的handler加工BeanDefintion后,再注册到BeanFactory;核心方法为scanPackage:扫描指定的包路径,
private void scanPackage(
		ClassPathScanningCandidateComponentProvider componentProvider,
		String packageToScan) {
		
    // componentProvider.findCandidateComponents(packageToScan)方法:
    // 查找并创建BeanDefintions集合,但是还没注册到BeanFactory中
	for (BeanDefinition candidate : componentProvider
			.findCandidateComponents(packageToScan)) {
			
		if (candidate instanceof ScannedGenericBeanDefinition) {
			for (ServletComponentHandler handler : HANDLERS) {
			
			    // 由注解的handler完成加工再注册到BeanFactory
				handler.handle(((ScannedGenericBeanDefinition) candidate),
						(BeanDefinitionRegistry) this.applicationContext);
			}
		}
	}
}

HANDLERS的定义如下:
 

static {
	List<ServletComponentHandler> servletComponentHandlers = new ArrayList<>();
	servletComponentHandlers.add(new WebServletHandler());
	servletComponentHandlers.add(new WebFilterHandler());
	servletComponentHandlers.add(new WebListenerHandler());
	HANDLERS = Collections.unmodifiableList(servletComponentHandlers);
}
  • 对于每个注解,都有相应的handler来注册到BeanFactory,具体为:WebServletHandler,WebFilterHandler,WebListenerHandler。
  • 以下以WebFilterHandler为例:可见在内部使用FilterRegistrationBean来保证Filter接口的实现类:
/**
 * 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) {
		
		// 指定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));
		
		// 创建BeanDefinition并注册到BeanFactory
		
		registry.registerBeanDefinition(name, builder.getBeanDefinition());
	}
	
	...
	
}

创建Servlet,Filter和Listener的bean对象实例并绑定到ServletContext

  • 由以上分析可知,ApplicationContext的refresh方法在调用完所有BeanFactoryPostProcessor之后,接下来会调用onRefresh方法,在这个方法中注册具有特殊含义的bean对象。
  • 创建和启动应用内嵌的Servlet引擎WebServer,创建内嵌的ServletContext对象绑定到WebServer,创建Servlet,Filter和Listener对应的bean对象绑定到ServletContext就是在ServletWebServerApplicationContext类的onRefresh方法实现的。onRefresh方法的实现如下:
@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();
		
		// 创建webServer,ServletContext,
		// 以及获取ServletContext的ServletContextInitializer
		// 并执行其onStartup方法
		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调用该方法
// 从BeanFactory获取ServletContextInitializer接口的实现类
private void selfInitialize(ServletContext servletContext) throws ServletException {	
	prepareWebApplicationContext(servletContext);
	registerApplicationScope(servletContext);
	WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),
			servletContext);
	// 在getServletContextInitializerBeans方法内部实现:
    // 从BeanDefiniton创建bean对象实例,具体为调用了BeanFactory的getBean
	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
		beans.onStartup(servletContext);
	}
}
  • 从以上源码可知,在selfInitialize方法中,调用getServletContextInitializerBeans方法来从BeanFactory获取ServletContextInitializer接口的实现类,创建bean对象实例并执行onStartup方法。
  • 其中RegistrationBean就实现了 ServletContextInitializer接口。RegistrationBean的onStartup方法实现如下:具体由子类实现register方法完成业务逻辑。对Servlet规范相关的Servlet,Filter,Listener,则是绑定到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;
		}
		// 将当前bean对象注册到servletContext
		register(description, servletContext);
	}

    // 抽象方法,由子类实现
    
	/**
	 * 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);

    ...
    
}

以下以ServletListenerRegistrationBean的register方法实现为例:调用servletContext.addListener完成绑定:
 

@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);
	}
}