Spring Boot注册Servlet、Filter、Listener原理
如何使用
在Spring Boot中注册Servlet、Filter办法主要有3种,下面来看下具体例子,例子都采用Filter,Servlet同理。
第一种,使用FilterRegistrationBean、ServletRegistrationBean
@Configuration
public class AppConfig {
@Bean
public FilterRegistrationBean<FirstFilter> firstFilter() {
ServletRegistrationBean
FilterRegistrationBean<FirstFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new FirstFilter());
// 默认为/*
filterRegistrationBean.addUrlPatterns("/*");
// 设置Filter的执行顺序(默认为最低优先级)
filterRegistrationBean.setOrder(1);
return filterRegistrationBean;
}
}
此种方式最为灵活,可以设置过滤器的映射路径以及执行顺序。
第二种,使用原生注解@WebFilter、@WebServlet + @ServletComponentScan
要想使用原生注解注册Filter以及Servlet,需要加上@ServletComponentScan开启扫描
/**
* 启动类开启扫描, 默认扫描路径为当前类所在的包路径
*/
@ServletComponentScan
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@WebFilter("/*")
public class FirstFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("=============FirstFilter===============");
chain.doFilter(request, response);
}
}
该方式可以指定映射路径,但是不能指定执行顺序,即便在类上增加@Order注解或者实现Ordered接口,Spring Boot在注册时也不会使用,采用的默认顺序,即Ordered.LOWEST_PRECEDENCE,优先级最低。
第三种,使用@Component注解
对于Spring容器中管理的Filter、Servlet类型的bean,Spring Boot同时会把它们注册到Servlet 容器中
@Component
@Order(1)
public class FirstFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("=============FirstFilter===============");
chain.doFilter(request, response);
}
}
这种方式虽然简单,但无法指定映射路径,不过可以指定执行顺序,映射路径默认就是/*,可以在类上使用@Order注解或者实现Ordered接口来指定执行顺序。
使用总结
| 方式 | 映射路径 | 执行顺序 |
|---|---|---|
FilterRegistrationBean |
yes | yes |
@WebFilter+ @ServletComponentScan |
yes | no |
@Component |
no | yes |
注册原理
首先明确一点,无论是@WebFilter还是@Component方式注册,Spring Boot最终的处理形式还是会把它们处理成FilterRegistrationBean(对于Filter而言,Servlet则是ServletRegistrationBean)
Spring Boot注册Filter、Servlet、Listener分别依赖于FilterRegistrationBean、ServletRegistrationBean、ServletListenerRegistrationBean。
它们之间共同实现的顶层接口为ServletContextInitializer,下面是整个体系的一个类图

@FunctionalInterface
public interface ServletContextInitializer {
/**
* servlet3.0对于ServletContext接口增加了addFilter、addServlet、addListener
* 方法,以至于可以动态注册Filter、Servlet、Listener
*/
void onStartup(ServletContext servletContext) throws ServletException;
}
/**
* 实现了Ordered接口,这样注册Filter、Servlet时可以指定顺序
*/
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
private static final Log logger = LogFactory.getLog(RegistrationBean.class);
/**
* 默认最低优先级
*/
private int order = Ordered.LOWEST_PRECEDENCE;
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(description, servletContext);
}
protected abstract String getDescription();
protected abstract void register(String description, ServletContext servletContext);
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isEnabled() {
return this.enabled;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
}
那么Spring Boot调用ServletContextInitializer.onStartup方法的时机在哪呢?答案是TomcatStarter类,Spring Boot启动时会触发TomcatStarter类中的onStartup方法
class TomcatStarter implements ServletContainerInitializer {
private static final Log logger = LogFactory.getLog(TomcatStarter.class);
private final ServletContextInitializer[] initializers;
private volatile Exception startUpException;
TomcatStarter(ServletContextInitializer[] initializers) {
this.initializers = initializers;
}
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
/*
* debug跟踪,里面有一个ServletWebServerApplicationContext返回的
* ServletContextInitializer实现, 具体方法为
* getSelfInitializer以及selfInitialize方法
* 具体注册逻辑便委托给了selfInitialize方法
*
* 注意: initializers中的对象并没有在Spring容器中
*/
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// 从Spring容器中获取所有的ServletContextInitializer实例,调用onStartup方法完成注册
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
/**
* 重点关注ServletContextInitializerBeans类
* 这是一个Collection<ServletContextInitializer>实现
*/
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
// 那么接下来就是查看构造方法做了些啥
return new ServletContextInitializerBeans(getBeanFactory());
}
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitializer>... initializerTypes) {
this.initializers = new LinkedMultiValueMap<>();
this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
: Collections.singletonList(ServletContextInitializer.class);
/*
* 寻找容器中所有的ServletContextInitializer并添加到initializers
*/
addServletContextInitializerBeans(beanFactory);
/**
* 寻找容器中所有的Filter、Servlet、Listener bean
* 并转换成对应的FilterRegistrationBean、ServletRegistrationBean
* ServletListenerRegistrationBean。并添加到initializers
* 该方法会获取order值,用于注册顺序
*
* 从这里便解释了为啥使用@Component注解也能生效的原因
*/
addAdaptableBeans(beanFactory);
List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
.collect(Collectors.toList());
this.sortedList = Collections.unmodifiableList(sortedInitializers);
logMappings(this.initializers);
}
走到这里,便收集了所有的ServletContextInitializer实例,接下来就是调用每一个实例的onStartup方法,挑选FilterRegistrationBean类来看下时如何注册Filter的。
public class FilterRegistrationBean<T extends Filter> extends AbstractFilterRegistrationBean<T> {
private T filter;
public FilterRegistrationBean() {
}
public FilterRegistrationBean(T filter, ServletRegistrationBean<?>... servletRegistrationBeans) {
super(servletRegistrationBeans);
Assert.notNull(filter, "Filter must not be null");
this.filter = filter;
}
@Override
public T getFilter() {
return this.filter;
}
public void setFilter(T filter) {
Assert.notNull(filter, "Filter must not be null");
this.filter = filter;
}
}
该类非常简单,也没有重写onStartup方法,因此找父类AbstractFilterRegistrationBean,发现它也没有重写onStartup,于是继续往上找RegistrationBean
@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(description, servletContext);
}
DynamicRegistrationBean类
@Override
protected final void register(String description, ServletContext servletContext) {
// 注册到servletContext中
D registration = addRegistration(description, servletContext);
if (registration == null) {
logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
return;
}
// 配置映射路径等参数
configure(registration);
}
AbstractFilterRegistrationBean类
@Override
protected Dynamic addRegistration(String description, ServletContext servletContext) {
// 最终调用servletContext.addFilter完成注册
Filter filter = getFilter();
return servletContext.addFilter(getOrDeduceName(filter), filter);
}
所以到最后还是调用的servlet3.0新增的api完成最终的注册
@ServletComponentScan注册过程
从上面分析完,我们不难猜到该注解实际上就是扫描@WebFilter、@WebServlet、@WebListener注解的类,然后把这些类包装成对应的RegistrationBean注册到Spring 容器即可。下面来看下具体实现
首先肯定要分析该注解
/**
* 下面三个属性都不配的话,取该注解所在类的包路径
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ServletComponentScanRegistrar.class)
public @interface ServletComponentScan {
/**
* 扫描包路径
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* 扫描包路径
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* 扫描包路径,只是用类来替代,取类的包名作为路径
*/
Class<?>[] basePackageClasses() default {};
}
可以看到该类导入了ServletComponentScanRegistrar
class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
private static final String BEAN_NAME = "servletComponentRegisteringPostProcessor";
/**
* 唯一的作用就是向容器中注册一个servletComponentRegisteringPostProcessor bean
* 它是一个
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
// 如果存在,合并扫描的路径
if (registry.containsBeanDefinition(BEAN_NAME)) {
updatePostProcessor(registry, packagesToScan);
}
else {
// 不存在注册servletComponentRegisteringPostProcessor
addPostProcessor(registry, packagesToScan);
}
}
private void updatePostProcessor(BeanDefinitionRegistry registry, Set<String> packagesToScan) {
BeanDefinition definition = registry.getBeanDefinition(BEAN_NAME);
ValueHolder constructorArguments = definition.getConstructorArgumentValues().getGenericArgumentValue(Set.class);
@SuppressWarnings("unchecked")
Set<String> mergedPackages = (Set<String>) constructorArguments.getValue();
mergedPackages.addAll(packagesToScan);
constructorArguments.setValue(mergedPackages);
}
private void addPostProcessor(BeanDefinitionRegistry registry, Set<String> packagesToScan) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(ServletComponentRegisteringPostProcessor.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(packagesToScan);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
}
private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(ServletComponentScan.class.getName()));
String[] basePackages = attributes.getStringArray("basePackages");
Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(basePackages));
for (Class<?> basePackageClass : basePackageClasses) {
packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
}
if (packagesToScan.isEmpty()) {
packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName()));
}
return packagesToScan;
}
}
接下来看ServletComponentRegisteringPostProcessor
class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware {
private static final List<ServletComponentHandler> HANDLERS;
static {
// 分别用于处理Servlet、Filter、Listener
// 转换成对应的RegistrationBean
List<ServletComponentHandler> servletComponentHandlers = new ArrayList<>();
servletComponentHandlers.add(new WebServletHandler());
servletComponentHandlers.add(new WebFilterHandler());
servletComponentHandlers.add(new WebListenerHandler());
HANDLERS = Collections.unmodifiableList(servletComponentHandlers);
}
private final Set<String> packagesToScan;
private ApplicationContext applicationContext;
ServletComponentRegisteringPostProcessor(Set<String> packagesToScan) {
this.packagesToScan = packagesToScan;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (isRunningInEmbeddedWebServer()) {
// 扫描bean以及注册
ClassPathScanningCandidateComponentProvider componentProvider = createComponentProvider();
for (String packageToScan : this.packagesToScan) {
scanPackage(componentProvider, packageToScan);
}
}
}
private void scanPackage(ClassPathScanningCandidateComponentProvider componentProvider, String packageToScan) {
for (BeanDefinition candidate : componentProvider.findCandidateComponents(packageToScan)) {
if (candidate instanceof AnnotatedBeanDefinition) {
for (ServletComponentHandler handler : HANDLERS) {
handler.handle(((AnnotatedBeanDefinition) candidate),
(BeanDefinitionRegistry) this.applicationContext);
}
}
}
}
private boolean isRunningInEmbeddedWebServer() {
return this.applicationContext instanceof WebApplicationContext
&& ((WebApplicationContext) this.applicationContext).getServletContext() == null;
}
private ClassPathScanningCandidateComponentProvider createComponentProvider() {
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
false);
componentProvider.setEnvironment(this.applicationContext.getEnvironment());
componentProvider.setResourceLoader(this.applicationContext);
for (ServletComponentHandler handler : HANDLERS) {
componentProvider.addIncludeFilter(handler.getTypeFilter());
}
return componentProvider;
}
Set<String> getPackagesToScan() {
return Collections.unmodifiableSet(this.packagesToScan);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
class WebFilterHandler extends ServletComponentHandler {
WebFilterHandler() {
super(WebFilter.class);
}
/**
* 完成转换并注册
* 该类并未处理@Order注解或者Ordered接口,因此使用的FilterRegistrationBean默认值
* 即最低优先级
*/
@Override
public void doHandle(Map<String, Object> attributes, AnnotatedBeanDefinition beanDefinition,
BeanDefinitionRegistry registry) {
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));
registry.registerBeanDefinition(name, builder.getBeanDefinition());
}
}
Spring Boot注册Servlet、Filter、Listener原理的更多相关文章
- Spring Boot整合Servlet,Filter,Listener,访问静态资源
目录 Spring Boot整合Servlet(两种方式) 第一种方式(通过注解扫描方式完成Servlet组件的注册): 第二种方式(通过方法完成Servlet组件的注册) Springboot整合F ...
- Spring Boot 注册 Servlet 的三种方法,真是太有用了!
本文栈长教你如何在 Spring Boot 注册 Servlet.Filter.Listener. 你所需具备的基础 什么是 Spring Boot? Spring Boot 核心配置文件详解 Spr ...
- SpringBoot注册Servlet/Filter/Listener
由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,那么没有web.xml文件,如何配置我们的三大Web基础组件呢? 通过使用XXXRe ...
- 【串线篇】spring boot嵌入式Servlet容器启动原理;
什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat: 获取嵌入式的Servlet容器工厂: 1).SpringBoot应用启动运行run方法 2).r ...
- spring boot整合servlet、filter、Listener等组件方式
创建一个maven项目,然后此项目继承一个父项目:org.springframework.boot 1.创建一个maven项目: 2.点击next后配置父项目及版本号 3.点击finish后就可查看p ...
- spring boot配置Servlet容器
Spring boot 默认使用Tomcat作为嵌入式Servlet容器,只需要引入spring-boot-start-web依赖,默认采用的Tomcat作为容器 01 定制和修改Servlet容器 ...
- ServletContextInitializer添加 servlet filter listener
ServletContextInitializer添加 servlet filter listener https://www.cnblogs.com/pomer-huang/p/9639322.ht ...
- [转] Spring Boot实战之Filter实现使用JWT进行接口认证
[From] http://blog.csdn.net/sun_t89/article/details/51923017 Spring Boot实战之Filter实现使用JWT进行接口认证 jwt(j ...
- SpringBoot学习笔记(6)----SpringBoot中使用Servlet,Filter,Listener的三种方式
在一般的运用开发中Controller已经大部分都能够实现了,但是也不排除需要自己实现Servlet,Filter,Listener的方式,SpringBoot提供了三种实现方式. 1. 使用Bean ...
- SpringBoot---注册Servlet,Filter,Listener
1.概述 1.1.当使用 内嵌的Servlet容器(Tomcat.Jetty等)时,将Servlet,Filter,Listener 注册到Servlet容器的方法: 1.1.1.直接注册Bean ...
随机推荐
- 读论文SRCNN:Learning a Deep Convolutional Network for Image Super-Resolution
Learning a Deep Convolutional Network for Image Super-Resolution SRCNN是深度学习应用于SR领域的开山之作. 论文 2014 ECC ...
- Me-and-My-Girlfriend-1
Me-and-My-Girlfriend-1 目录 Me-and-My-Girlfriend-1 1 信息收集 1.1 端口扫描 1.2 后台目录扫描 1.2.1 目录分析 2 GetShell 2. ...
- SpringBoot多数据源以及事务处理
背景 在高并发的项目中,单数据库已无法承载大数据量的访问,因此需要使用多个数据库进行对数据的读写分离,此外就是在微服化的今天,我们在项目中可能采用各种不同存储,因此也需要连接不同的数据库,居于这样的背 ...
- Docker安装配置Oracle详细教程(以作持久化处理)
Docker安装Oracle 1,拉取Oracle镜像,拉取成功后查看 docker pull registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11 ...
- WPF ScrollViewer 没有效果
ScrollViewer组件外组件如果是StackPanel组件 需要给StackPanel 设置高度,ScrollViewer 才会有滚动条 如果不想设置StackPanel高度,可以把StackP ...
- pat 乙级1024 科学计数法关于stl中size()的一些思考即测试点六,无符号整数问题
来,先看题目:1024 科学计数法 分数 20 作者 HOU, Qiming 单位 浙江大学 科学计数法是科学家用来表示很大或很小的数字的一种方便的方法,其满足正则表达式 [+-][1-9].[0-9 ...
- ThreadLocal最终版本
ThreadLocal工作原理 目录 ThreadLocal工作原理 一.官方文档描述 二.为什么使用ThreadLocal 2.1.案例 三.ThreadLocal和syncronized关键字区别 ...
- mysql版本升级 5.7.21-8.0.30
当前MySQL版本为:5.7.21 升级前准备,了解5.7和8.0版本有何区别,本文主要为升级操作文档,具体建议参考官方文档,概括性的有以下几点: >默认字符集由latin1变为utf8mb4 ...
- C# 图片压缩(指定大小压缩和指定尺寸压缩)
一按大小压缩测试代码: 一测试效果: 一主要代码: /// <summary> /// 压缩图片至200 Kb以下 /// </summary> /// <param n ...
- mybatis全局配置:下划线转驼峰
处理字段名和属性名不一致的情况: mybatis-config.xml配置 <settings> <setting name="mapUnderscoreToCamelCa ...