如何使用

在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. P6329 【模板】点分树 | 震波

    \(\text{Solution}\) 点分树就是将点分治过程中的重心连成一棵虚树 对点分树子树信息的记录,就是点分治处理每个重心时需要的信息 这样就可以留下点分治的过程,支持多次修改和查询 点分树树 ...

  2. axSpA患者新发炎症更容易发生在既往发生过炎症的区域

    axSpA患者新发炎症更容易发生在既往发生过炎症的区域 EULAR2015; PresentID: SAT0240 NEW INFLAMMATORY LESIONS IN AXIAL SPONDYLO ...

  3. global与nonlocal关键字、函数名的多种用法、函数的嵌套调用、函数的嵌套定义、闭包函数、装饰器简介

    目录 一.global与nonlocal关键字 二.函数名的多种用法 三.函数的嵌套调用 四.函数的嵌套定义 五.闭包函数 六.装饰器简介 一.global与nonlocal关键字 global方法: ...

  4. ImGui渲染3d数据的方法

    ImGui本质上是个2d渲染引擎,渲染3d数据只能另辟蹊径.目前主要有3种方法: 一是2d转换,可以自己处理3维坐标向屏幕坐标的转换,然后调用ImGui的二维绘制函数进行渲染: 二是3d贴图,首先在3 ...

  5. elasticsearch相关概念及常用操作汇总

    背景 我本来是想把我的写的es的平时总结dsl发出来的,但是我发现只搞那个意义大不.干脆多写点吧. 索引的结构化和非结构 我们经常用数据库,当然会经常用到索引. 然后从索引的维度去分析,系统分为结构化 ...

  6. ABAP PDF 打印

    SPAN { font-family: "Courier New"; font-size: 10pt; color: rgba(0, 0, 0, 1); background: r ...

  7. flexible.js源码分析

    (function flexible(window,document){ // 获取html的根元素 var docEl = document.documentElement; // dpr 物理像素 ...

  8. 面了几个说自己精通 Vue 的同学,实在一言难尽……

    请说一下响应式数据的原理 默认 Vue 在初始化数据时,会给 data 中的属性使用 Object.defineProperty 重新定义所有属性,当页面到对应属性时,会进行依赖收集 (收集当前组件中 ...

  9. 当越来越多的企业放弃使用FTP,该用什么更好的方式替代?

    FTP作为第一个完整的文件传输协议,在互联网技术发展史上具有浓墨重彩的意义,它解决了文件传输协议有无的问题,在全世界范围内被广泛使用.但如今,随着网络技术的发展,企业生产类型和生产资料的丰富化,文件传 ...

  10. 新的学习历程-python3 基本运算

    1 print(5 / 2) # 2.5 2 print(5 // 3) #1 取整除 3 print(5 % 3) #2 取余数(取模) 4 print(5 ** 2) #25 5的2次方 5 pr ...