如何使用

在Spring Boot中注册ServletFilter办法主要有3种,下面来看下具体例子,例子都采用FilterServlet同理。

第一种,使用FilterRegistrationBeanServletRegistrationBean

@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容器中管理的FilterServlet类型的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注册FilterServletListener分别依赖于FilterRegistrationBeanServletRegistrationBeanServletListenerRegistrationBean

它们之间共同实现的顶层接口为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原理的更多相关文章

  1. Spring Boot整合Servlet,Filter,Listener,访问静态资源

    目录 Spring Boot整合Servlet(两种方式) 第一种方式(通过注解扫描方式完成Servlet组件的注册): 第二种方式(通过方法完成Servlet组件的注册) Springboot整合F ...

  2. Spring Boot 注册 Servlet 的三种方法,真是太有用了!

    本文栈长教你如何在 Spring Boot 注册 Servlet.Filter.Listener. 你所需具备的基础 什么是 Spring Boot? Spring Boot 核心配置文件详解 Spr ...

  3. SpringBoot注册Servlet/Filter/Listener

    由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,那么没有web.xml文件,如何配置我们的三大Web基础组件呢? 通过使用XXXRe ...

  4. 【串线篇】spring boot嵌入式Servlet容器启动原理;

    什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat: 获取嵌入式的Servlet容器工厂: 1).SpringBoot应用启动运行run方法 2).r ...

  5. spring boot整合servlet、filter、Listener等组件方式

    创建一个maven项目,然后此项目继承一个父项目:org.springframework.boot 1.创建一个maven项目: 2.点击next后配置父项目及版本号 3.点击finish后就可查看p ...

  6. spring boot配置Servlet容器

    Spring boot 默认使用Tomcat作为嵌入式Servlet容器,只需要引入spring-boot-start-web依赖,默认采用的Tomcat作为容器 01  定制和修改Servlet容器 ...

  7. ServletContextInitializer添加 servlet filter listener

    ServletContextInitializer添加 servlet filter listener https://www.cnblogs.com/pomer-huang/p/9639322.ht ...

  8. [转] Spring Boot实战之Filter实现使用JWT进行接口认证

    [From] http://blog.csdn.net/sun_t89/article/details/51923017 Spring Boot实战之Filter实现使用JWT进行接口认证 jwt(j ...

  9. SpringBoot学习笔记(6)----SpringBoot中使用Servlet,Filter,Listener的三种方式

    在一般的运用开发中Controller已经大部分都能够实现了,但是也不排除需要自己实现Servlet,Filter,Listener的方式,SpringBoot提供了三种实现方式. 1. 使用Bean ...

  10. SpringBoot---注册Servlet,Filter,Listener

    1.概述 1.1.当使用  内嵌的Servlet容器(Tomcat.Jetty等)时,将Servlet,Filter,Listener  注册到Servlet容器的方法: 1.1.1.直接注册Bean ...

随机推荐

  1. new Date(time).getTime()在ios返回NaN

    解决: IOS识别(年月日时分秒) new Date(2010,0,1,0,0,0).getTime() 1.问题出在: 日期转成时间戳getTime(): var time = "2017 ...

  2. DBeaver连接clickhouse无法下载驱动的情况

    最近遇到dbeaver 连接clickhouse的时候提示下载驱动失败. 在网上找了些方法也不行,其中包括默认下载.配置阿里云的Maven. 最后在网上找到一个驱动包,自己手动添加即可.把下载地址分享 ...

  3. PostgreSQL事务隔离级别

    一.概念 并发控制是多个事务在并发运行时,数据库保证事务一致性(Consistency)和隔离性(Isolation)的一种机制.PostgreSQL使用了多版本并发控制技术的一种变体:快照隔离San ...

  4. 剖析flutter_download_manager学习如何做下载管理,暂停和取消

    前言 内容类应用中图片或文件下载,一般应用中应用更新和升级,这些都是经典的下载场景.下载是项目中基础且重要的模块. 从代码逻辑复用性和人力成本考虑,一直想实现一个纯Dart实现的下载库,作为技术储备. ...

  5. LeetCode-1220 统计元音字母序列的数目

    来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/count-vowels-permutation 题目描述 给你一个整数 n,请你帮忙统计一下我们 ...

  6. Anndroid GC 那些事

    内存回收机制对于app性能优化中比较重要部分,我们要做好优化工作,Android GC工作情况我们需要熟知, 因此整理了一下关于GC知识点,主要分为Dalvik与ART两部分 Dalvik堆内存结构: ...

  7. opc ua与opc da区别

    opc ua与opc da区别_OPC,OPCDA,OPCUA傻傻搞不清楚,走过路过不妨看一看 转自:https://blog.csdn.net/weixin_39624774/article/det ...

  8. Linux 磁盘扩容

    原文链接:https://blog.csdn.net/zzq100zzq/article/details/125178843 一.查看系统磁盘1.使用df -hl ,查看系统的磁盘使用情况二.linu ...

  9. 如何用HP 39GS计算器画出双曲线图像

    1.双曲线标准方程和参数方程 2.计算器上的操作 1.打开APLET->Parametric->START 2.设置X1(T)=3/COS(T),X2(T)=4*TAN(T) 3.SHIF ...

  10. c++练习267题 火柴棒等式

    *267题 原题传送门:http://oj.tfls.net/p/267 题解: #include<bits/stdc++.h>using namespace std;int c,m;in ...