SpringBoot中给Tomcat添加过滤器
SpringBoot中给Tomcat添加过滤器
一、引入
JavaWeb组件Servlet提供了filter过滤功能,其功能是对目标资源的请求和响应进行拦截,对拦截到的请求和响应做出特殊的功能处理,比如我们请求中有一些敏感信息过滤就是利用过滤器过滤
二、Filter功能概述
Java Servlet API中提供了Filter接口,编写Filter的实现类,从而实现自定义过滤器。
Filter的请求流程为:
-
1、客户端发起请求服务容器判断当前请求资源是否有过滤器,
-
2、有则执行过滤器过滤器过滤通过后请求到Servlet服务器返回结果通过过滤器返回给请求方
Filter接口源码:
package javax.servlet;
import java.io.IOException;
public interface Filter {
// 初始化方法
default void init(FilterConfig filterConfig) throws ServletException {
}
// 过滤方法
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
// 初始化方法
default void destroy() {
}
}
init() | 此方法在只在过滤器创建的时候执行一次,用于初始化过滤器的属性 | |
---|---|---|
doFilter() | 该方法会对请求进行拦截,用户需要在该方法中自定义对请求内容以及响应内容进行过滤的,调用该方法的入参 FilterChain对象的 doFilter 方法对请求放行执行后面的逻辑,若未调用 doFilter 方法则本次请求结束,并向客户端返回响应失败 | |
destroy() | 此方法用于销毁过滤器,过滤器被创建以后只要项目一直运行,过滤器就会一直存在,在项目停止时,会调用该方法销毁过滤器 |
三、添加过滤器进行实操
在SpringBoot中有两种方式实现自定义Filter:
- 1、使用 @WebFilter 和 @ServletComponentScan 组合注解;
- 2、通过配置类注入 FilterRegistrationBean对象
3.1、注解版
注解版是最常用的了。首先看下 @WebFilter注解中的信息:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebFilter {
String description() default "";
String displayName() default "";
WebInitParam[] initParams() default {};
String filterName() default "";
String smallIcon() default "";
String largeIcon() default "";
String[] servletNames() default {};
String[] value() default {};
String[] urlPatterns() default {};
DispatcherType[] dispatcherTypes() default {DispatcherType.REQUEST};
boolean asyncSupported() default false;
}
几个重要参数详解:
属性 | 功能描述 | |
---|---|---|
urlPatterns | 自定义需要拦截的URL,可以使用正则匹配,若没指定该参数值,则默认拦截所有请求 | |
filterName | 自定义过滤器的名称 | |
initParams | 自定义过滤器初始化参数的数组,此参数可以通过自定义过滤器 init() 的入参FilterConfig对象的 getInitParameter() 方法获取;(由于过滤器没有直接排除自定义URL不拦截的设定,如果我们需要在自定义拦截的URL中排除部分不需要拦截的URL,可以通过将需要排除的URL放到initParams参数中再在doFilter方法中排除) |
如下自定义个一个拦截所有URL除了带有 “/test” 的片段名称为testFilter的过滤器:
@WebFilter(filterName = "testFilter", urlPatterns = "/*",
initParams = @WebInitParam(name = "noFilterUrl", value = "/test"))
public class TestFilter implements Filter {
private List<String> noFilterUrls;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 从过滤器配置中获取initParams参数
String noFilterUrl = filterConfig.getInitParameter("noFilterUrl");
// 将排除的URL放入成员变量noFilterUrls中
if (StringUtils.isNotBlank(noFilterUrl)) {
noFilterUrls = new ArrayList<>(Arrays.asList(noFilterUrl.split(",")));
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
// 若请求中包含noFilterUrls中的片段则直接跳过过滤器进入下一步请求中
String url = ((HttpServletRequest)servletRequest).getRequestURI();
Boolean flag = false;
if (!CollectionUtils.isEmpty(noFilterUrls)) {
for (String noFilterUrl : noFilterUrls) {
if (url.contains(noFilterUrl)) {
flag = true;
break;
}
}
}
if (!flag) {
...... //过滤请求响应逻辑
}
// 一定要写上,不然后续的过滤器链条无法进行调用
// 具体源码参考:org.apache.catalina.core.ApplicationFilterChain#internalDoFilter
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
在启动类上需要添加@ServletComponentScan注解才能使过滤器生效
@SpringBootApplication
@ServletComponentScan(basePackages = "com.guang.springbootfilter.filter")
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class);
}
}
这里需要注意的是,如果实现多个FIlter功能的过滤器。使用@WebFilter注解的方式只能根据过滤器名的类名顺序执行,添加@Order注解是无效的,因为@WebFilter在容器加载时,不会使用@Order注解定义的顺序,而是默认直接使用类名排序。所以使用这种方式实现多个过滤器,且有顺序要求,则需要注意类名的定义。
但是一个项目中也不会存在着多个过滤器,要是存在着多个过滤器的话,可以考虑下是否可以使用拦截器来进行调用?
如果真的需要考虑到使用多个过滤器,那么参考如下所示:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* @Description
* @Author liguang
* @Date 2023/10/22/09:23
*/
@WebFilter(urlPatterns = "/*",filterName = "a")
public class AFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(AFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
logger.info("进入过滤器,但是不做拦截---------------------a");
chain.doFilter(request,response);
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* @Description
* @Author liguang
* @Date 2023/10/22/09:23
*/
@WebFilter(urlPatterns = "/*",filterName = "b")
public class BFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(BFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
logger.info("进入过滤器,但是不做拦截-----------------b");
chain.doFilter(request,response);
}
}
那么看一下打印结果:
进入过滤器,但是不做拦截-----------------a
进入过滤器,但是不做拦截-----------------b
3.2、配置版本
配置版本这里就不再演示了。直接放上参考链接:
四、原理探究
下面以注解版本为例,来进行探究。因为@WebFilter是Tomcat提供的,而@ServletComponentScan注解是Springboot提供的。
4.1、解析过程
所以我最开始的猜测就是因为是@ServletComponentScan注解发挥了作用,因为会去扫描指定包下添加了@WebFilter注解的类,检查其是否实现了javax.servlet.Filter接口。如果符合条件,那么会调用context添加到过滤器链条中去的。
那么带着这个思路来进行验证一下:
@Import(ServletComponentScanRegistrar.class)
public @interface ServletComponentScan {}
看到这里已经很明显了,添加了一个Bean来进行处理
package org.springframework.boot.web.servlet;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
/**
* {@link ImportBeanDefinitionRegistrar} used by
* {@link ServletComponentScan @ServletComponentScan}.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
*/
class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
private static final String BEAN_NAME = "servletComponentRegisteringPostProcessor";
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取得到扫描的包路径
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
// 判断容器中是否存在这个名称的BD,一般来说,不应该存在的
if (registry.containsBeanDefinition(BEAN_NAME)) {
updatePostProcessor(registry, packagesToScan);
}
else {
// 直接看到这里
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) {
// 添加了ServletComponentRegisteringPostProcessor类型的Bean
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这个bean,看起来像是一个BeanFactoryPostProcessor,点进去发现果然是。那么重点直接来到了postProcessBeanFactory方法了
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 此时此刻还没有servletcontext
if (isRunningInEmbeddedWebServer()) {
// 这个类看下来像是完成指定包下是否存在继承Filter接口并添加@WebFilter注解的类
ClassPathScanningCandidateComponentProvider componentProvider = createComponentProvider();
for (String packageToScan : this.packagesToScan) {
scanPackage(componentProvider, packageToScan);
}
}
}
看一下创建方法
private static final List<ServletComponentHandler> HANDLERS;
static {
// 添加各种类型的处理器
List<ServletComponentHandler> servletComponentHandlers = new ArrayList<>();
servletComponentHandlers.add(new WebServletHandler());
servletComponentHandlers.add(new WebFilterHandler());
servletComponentHandlers.add(new WebListenerHandler());
HANDLERS = Collections.unmodifiableList(servletComponentHandlers);
}
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;
}
private void scanPackage(ClassPathScanningCandidateComponentProvider componentProvider, String packageToScan) {
for (BeanDefinition candidate : componentProvider.findCandidateComponents(packageToScan)) {
if (candidate instanceof AnnotatedBeanDefinition) {
for (ServletComponentHandler handler : HANDLERS) {
// 获取得到各种handler来进行处理
handler.handle(((AnnotatedBeanDefinition) candidate),
(BeanDefinitionRegistry) this.applicationContext);
}
}
}
}
下面看一下具体的过程:
void handle(AnnotatedBeanDefinition beanDefinition, BeanDefinitionRegistry registry) {
// 获取得到各种属性来进行遍历
Map<String, Object> attributes = beanDefinition.getMetadata()
.getAnnotationAttributes(this.annotationType.getName());
if (attributes != null) {
// 不同的实现类可以来进行扫描处理
doHandle(attributes, beanDefinition, registry);
}
}
因为我们当前处理的是@WebFilter,所以看下org.springframework.boot.web.servlet.WebFilterHandler#doHandle中的处理过程
public void doHandle(Map<String, Object> attributes, AnnotatedBeanDefinition beanDefinition,
BeanDefinitionRegistry registry) {
// 直接创建了FilterRegistrationBean类型的bean
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));
// 获取得到fitler的名称
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());
}
4.2、如何添加到ServletContext中?
既然已经创建好啦fitler,那么如何添加到serlvet上下文中的呢?
那么直接在BFilter中添加一个构造方法
@WebFilter(urlPatterns = "/*",filterName = "b")
public class BFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(BFilter.class);
public BFilter() {
logger.info("实例化filter");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
logger.info("进入过滤器,但是不做拦截-----------------b");
chain.doFilter(request,response);
}
}
打上断点来进行观察,最终发现是通过:
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
// 这里调过去的
beans.onStartup(servletContext);
}
}
具体看一下:
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitializer>... initializerTypes) {
this.initializers = new LinkedMultiValueMap<>();
this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
: Collections.singletonList(ServletContextInitializer.class);
// 通过这里来进行进行调用的
addServletContextInitializerBeans(beanFactory);
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);
}
进一步来看:
private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory,
initializerType)) {
addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
}
}
}
直接来到重点:
private <T> List<Entry<String, T>> getOrderedBeansOfType(ListableBeanFactory beanFactory, Class<T> type,
Set<?> excludes) {
String[] names = beanFactory.getBeanNamesForType(type, true, false);
Map<String, T> map = new LinkedHashMap<>();
for (String name : names) {
if (!excludes.contains(name) && !ScopedProxyUtils.isScopedTarget(name)) {
T bean = beanFactory.getBean(name, type);
if (!excludes.contains(bean)) {
map.put(name, bean);
}
}
}
// 这里有@Order的排序
List<Entry<String, T>> beans = new ArrayList<>(map.entrySet());
beans.sort((o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getValue(), o2.getValue()));
return beans;
}
关键是这里的type为ServletContextInitializer,而我们的filter最终注册成的类型是FilterRegistrationBean,这里的FilterRegistrationBean是ServletContextInitializer的实现类,而DispatcherServletRegistrationBean也是其实现之一,所以接下来就应该是进行类型排除。
那么重点在于就在于seen和initializers这两个集合中的一个如何添加到servletcontext中去的?
那么就直接看一下ServletContextInitializer中的onStartup方法
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 Dynamic addRegistration(String description, ServletContext servletContext) {
Filter filter = getFilter();
return servletContext.addFilter(getOrDeduceName(filter), filter);
}
至此,追踪结束
五、总结
1、先是概述了Filter的作用;
2、两种方式配置Filter:①注解版;②配置版;
3、分析了Tomcat中的servletcontext中是如何添加filter的源码;