首页 > 其他分享 >SpringBoot中给Tomcat添加过滤器

SpringBoot中给Tomcat添加过滤器

时间:2023-10-22 11:58:16浏览次数:41  
标签:String Tomcat void Filter 过滤器 import public SpringBoot

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、配置版本

配置版本这里就不再演示了。直接放上参考链接:

1、https://www.cnblogs.com/cy0628/p/16379612.html

2、https://www.cnblogs.com/longan-wang/p/15527516.html

四、原理探究

下面以注解版本为例,来进行探究。因为@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的源码;

标签:String,Tomcat,void,Filter,过滤器,import,public,SpringBoot
From: https://www.cnblogs.com/likeguang/p/17780207.html

相关文章

  • SpringBoot——yaml配置文件
    yaml简介YAML是"YAMLAin'taMarkupLanguage"(YAML不是一种标记语言)。在开发的这种语言时,YAML的意思其实是:"YetAnotherMarkupLanguage"(是另一种标记语言)。设计目标,就是方便人类读写层次分明,更适合做配置文件使用.yaml或.yml作为文件后缀基本语法大小写敏感使......
  • 基于SpringBoot与Vue技术的高校毕设管理平台-计算机毕业设计源码+LW文档
    开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:Maven3.3.9浏览器:谷歌浏览器数据库部分表:DROPTABLEIFEXISTSbisheketi;/*!40101SET@saved_cs_client=@@characte......
  • SpringBoot与jdk版本冲突
    问题:SpringBoot项目无法正常启动原因:SpringBoot2.0以上版本最低需要java8支持;SpringBoot3.0以上的版本最低需要java17支持。只需要降低pom文件中springboot版本即可。SpringBoot版本参见于https://spring.io/projects/spring-boot#learn文章参考了https://www.cnblogs.co......
  • 基于Springboot框架的优质衣产品系统-计算机毕业设计源码+LW文档
    开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:Maven3.3.9浏览器:谷歌浏览器前台用户模块涵盖了:用户登录、注册功能,包括消费者进行优质衣产品系统的登录可进行衣产品的选购等......
  • 基于SpringBoot框架的教学评价系统的设计与实现-计算机毕业设计源码+LW文档
    摘要随着时代的发展,我国的教育水平在不断的提高,但是很多时候为了更好的提高教学的质量,会让学生对当前的教学进行评价,教育工作者根据学生的评价发现当下教学中的一些不足,从而更好的提高教学质量,为了让教学评价变的更加的方便我们开发了本次的教学评价系统。本系统从用户的角度出......
  • 基于springboot洗衣店管理系统-计算机毕业设计源码+LW文档
    摘要随着时代的发展,人们的工作也学习压力越来越大,很多时候空闲时间也越来也少,经常没有时间去洗自己的衣服,很多商家在看到这种情况之后开设了洗衣机店专门用于服务这些没有时间洗衣服的人,但是传统的洗衣店都是用手动的的模式在管理,这种模式很落后,为了改善这一情况很多地方开设了线......
  • springboot使用form标签在两个html页面之间实现界面跳转,出现405问题,但是一刷新就能出
    问题描述在我使用form标签的action属性实现两个html页面之间的跳转,但是出现了这样的问题:问题解决我尝试将这一块内容去掉:然后再次尝试:页面出来啦~问题解决啦~~......
  • IDEA新建SpringBoot项目突然报错问题的解决
    问题描述在我使用IDEA新建SpringBoot项目时,突然出现这个错误:之前也是一直这么新建项目,这次突然出现这样的错误,哎呦,我真服啦~问题解决就是说吧,在我看了网上解决问题的教程之后,发现都没有问题,然后我就不死心地又试了试,发现就成功创建了,具体怎么解决的,我确实是不太清楚了。......
  • SpringBoot常见异步编程,你会多少?
    微信公众号地址:SpringBoot常见异步编程,你会多少? 前言:异步执行对于开发者来说并不陌生,在实际的开发过程中,很多场景都会使用到异步,相比同步执行,异步可以大大缩短请求链路耗时时间,比如:「发送短信、消息、邮件、异步更新、缓存一致性等」,这些都是典型的可以通过异步实现的场景。一、什......
  • SpringBoot项目的POM文件分析
    pom.xml内容如下:<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.ap......