如何使用

在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. JZOJ 5372. 【NOIP2017提高A组模拟9.17】猫

    题目大意 对于 \(m = [1,\lfloor \frac n 2 \rfloor]\) 要求在一个序列中恰好选出 \(m\) 个不相邻的数使得权值和最大 其中 \(1\) 的左边是 \(n\),\ ...

  2. Hexo系列(四):Hexo写文章

    作者:独笔孤行 官网:​​ ​http://anyamaze.com​​ 公众号:云实战 可以执行下列命令来创建一篇新文章或者新的页面. $ hexo new [layout] <title&g ...

  3. 深入理解计算机系统(CSAPP)bomblab实验进阶之nuclearlab——详细题解

    前言 本实验是难度高于bomblab的一个补充实验,该实验部分题目难度已经达到CTF入门水平,且这个实验据说是上一届的某个学长原创,因此互联网上几乎找不到类似的题目.在间断地思考了几周后我最终完成了所 ...

  4. js手动触发页面元素点击事件,自定义点击事件模拟点击

    // initEvent事件已经弃用1. 创建MouseEvents事件const clickEvent = document.createEvent('MouseEvents')2. 初始化点击事件 ...

  5. Python3 时间戳格式化和减法运算

    import datetime import time # 获取当前时间(2023-02-16 16:41:36) now_date = datetime.datetime.now().strftim ...

  6. 零基础小白速成python?有了这本书你还在担心什么?

    <Python编程快速上手>书籍PDF高清版免费下载地址 提取码:bc9h 内容简介  · · · · · · 如今,人们面临的大多数任务都可以通过编写计算机软件来完成.Python是一种 ...

  7. vue框架3

    js的几种循环方式 1.v-for可以循环的变量 <!DOCTYPE html> <html lang="en"> <head> <met ...

  8. docker swarm集群安装使用

    1.安装master docker swarm init --advertise-addr 10.98.10.186 Swarm initialized: current node (qemrm3oq ...

  9. 用例需注意的点-UI自动化

    记几条--用例注意事项:用例从功能里面转化而来,并且不能脱离业务(针对某一个页面功能\某一个流程业务,写一条用例:即将界面操作间接转化为代码去操作!)1用例要尽量独立,相互不影响!(单独一条都可运行) ...

  10. 使用svg让页面自适应浏览器大小,整体等比缩放

    网页代码: <!DOCTYPE html> <html> <head> <style> body { margin:0; padding:0; widt ...