概述
在 spring boot
里大家都知道怎么去写一个 Filter
去对请求进行过滤
大概步骤是:
- application 上用注解
@ServletComponentScan
开启功能 - coding, 写一个类,实现
javax.servlet.Filter
,加上WebFilter
注解
但是在实际开发过程中会发现:当有多个filter,且我们希望有先后顺序的时候,spring boot 没有提供这样的实现
因为 Filter
是给 web 容器(如 tomcat 使用的),不属于 bean
, 所以 spring 无法管理他们的顺序
所以 在类上加上 Order
Component
等 spring 的注解是没有用的
本文就记录一下我尝试的4种方法,如下:
- 加上
Order
Component
等 spring 的注解, 没有用 - 改变类名,以
A B C..
开头,不是100%生效 - 注入
尝试1,spring 注解 Order
不生效
尝试2, 类名顺序
因为大部分代码对数组处理的时候,都是有顺序的
要么是 磁盘加载顺序,要么是字母顺序,
因为我没有了解过 内嵌的 tomcat 到底是怎么加载 filter的,所以盲猜 字母顺序,
所以把类名改为了如
- AxxFilter
- BxxFilter
- CxxFilter
一番测试下来宣告失败
尝试3,手写 FilterRegistrationBean
spring boot
提供了 一个功能,可以手动注入 一种类型为 FilterRegistrationBean
的 bean
代码为
public class FilterRegistrationBean extends AbstractFilterRegistrationBean {
}
abstract class AbstractFilterRegistrationBean extends RegistrationBean {
}
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
private String name;
private int order = Ordered.LOWEST_PRECEDENCE;
private boolean asyncSupported = true;
private boolean enabled = true;
}
可以看到 FilterRegistrationBean
是可以去 设置 order
的(通过 RegistrationBean
的 public
方法)
这个order
就是最后 filter
的顺序
所以我的思路就是:手写一个配置类 FilterRegister
,把所有filter按照业务顺序注册进去,并且把类上的 WebFilter
注解删掉,避免自动注册
最后有了这样的代码
image.png每一个方法的实现类似于这样的:
image.png
测试之后,可以用
尝试4
上面的方案其实已经在线上稳定运行了,虽然能用,但是非常不友好,改一个顺序,加一个 filter 都很麻烦,后面进入团队的同学可能不知道这个坑,如果没有在这里修改,那么 filter 就不会生效
所以最近在想,能不能在类上加一个注解,类似于spring
Order
那样的,指定一个顺序,我在框架层面写一些代码自动注册并且指定顺序,那样不就可以一劳永逸的解决这个问题了么
最好看到其他同学实现了一套 自动注册spring bean
的功能,我就研究之后改了改,实现了 自动注册 filter,并且支持指定顺序
效果如下
image.png
以下是code
Step1- 功能开关注解
先实现一个 功能开关注解 EnableFilterAutoRegistrar
(当然了,做成 starter
那种 SPI
的也行)
import org.springframework.context.annotation.Import;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自动注册 filter
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(WebFilterAutoRegistrar.class)
@Documented
public @interface EnableFilterAutoRegistrar {
}
WebFilterAutoRegistrar
是具体注册逻辑,接下来会讲到
Step2-自定义filter顺序注解
懒的取名,就叫 MyFilterOrder
吧
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface MyFilterOrder {
int value() default 50;
}
Step3- 自动注册
代码关键步骤有注释
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 自动注册 web filter
* @author YellowTail
* @since 2021-11-05
*/
public class WebFilterAutoRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
private static final Logger LOGGER = LoggerFactory.getLogger(WebFilterAutoRegistrar.class);
// 包名
public static final String DEFAULT_PACKAGE = "com.xxxxx.filter";
private ResourceLoader resourceLoader;
private Environment environment;
@Override
public void setEnvironment(final Environment environment) {
this.environment = environment;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 1. 初始化一个类扫描器
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false, this.environment) {
@Override
protected boolean isCandidateComponent(
AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent()) {
if (!beanDefinition.getMetadata().isAnnotation()) {
isCandidate = true;
}
}
return isCandidate;
}
};
// 2. 给扫描器加过滤条件:需要在类上有 MyFilterOrder 注解
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(MyFilterOrder.class);
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(annotationTypeFilter);
// 3. 扫描指定的包,得到 一些列 bean 定义,循环处理之
Set<BeanDefinition> candidateBeanDefinitionSet = scanner.findCandidateComponents(DEFAULT_PACKAGE);
for (BeanDefinition beanDefinition : candidateBeanDefinitionSet) {
if (beanDefinition instanceof ScannedGenericBeanDefinition) {
ScannedGenericBeanDefinition annotatedBeanDefinition = (ScannedGenericBeanDefinition) beanDefinition;
AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
String[] interfaceNames = annotationMetadata.getInterfaceNames();
// 带这个注解的类,必须实现 Filter 接口,否则不注册
boolean isImplFilter = List.of(interfaceNames).contains("javax.servlet.Filter");
if (isImplFilter) {
// 4. 开始注册
registerWebFilter(registry, annotationMetadata);
LOGGER.info("register web filter {} to bean factory", annotationMetadata.getClassName());
}
}
}
}
/**
* 生成一个 FilterRegistrationBean 并注册到 spring
* @param registry
* @param annotationMetadata
*/
private void registerWebFilter(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata) {
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(MyFilterOrder.class.getCanonicalName());
Integer order = (Integer) attributes.get("value");
String className = annotationMetadata.getClassName();
try {
// 生成 filter 实例
Object newInstance = Class.forName(className).getConstructor().newInstance();
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(WebFilterFactoryBean.class)
.addPropertyValue("filter", newInstance)
.addPropertyValue("order", order)
.addPropertyValue("name", className)
.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
.getBeanDefinition();
// 注册
registry.registerBeanDefinition(className, beanDefinition);
LOGGER.info("register WebFilter {} success", className);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) {
LOGGER.error("error, ", e);
}
}
}
上面使用到了一个类,叫 WebFilterFactoryBean
是用来生成 FilterRegistrationBean
对象的
Step4- 生成 FilterRegistrationBean
import org.springframework.beans.factory.FactoryBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import javax.servlet.Filter;
/**
* @author YellowTail
* @since 2021-11-05
*/
public class WebFilterFactoryBean implements FactoryBean<FilterRegistrationBean> {
private Filter filter;
private int order;
private String name;
@Override
public FilterRegistrationBean getObject() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setOrder(order); //设置顺序
registration.setFilter(filter); //设置过滤器对象
registration.setName(name); //过滤器名称
registration.addUrlPatterns("/*"); //路径
return registration;
}
@Override
public Class<?> getObjectType() {
return FilterRegistrationBean.class;
}
@Override
public boolean isSingleton() {
return true;
}
public Filter getFilter() {
return filter;
}
public WebFilterFactoryBean setFilter(final Filter filter) {
this.filter = filter;
return this;
}
public int getOrder() {
return order;
}
public WebFilterFactoryBean setOrder(final int order) {
this.order = order;
return this;
}
public String getName() {
return name;
}
public WebFilterFactoryBean setName(final String name) {
this.name = name;
return this;
}
}
最后在 application
加上注解功能就生效了
看日志
自动注册的日志
image.png
顺序也符合预期
image.png
最后和 spring boot 默认提供的一些 filter
共同组成了 责任链,日志为:
2021-12-08 18:19:41.360 DEBUG [ restartedMain ] o.s.b.web.servlet.ServletContextInitializerBeans:235 - Mapping filters:
com.xxxxx.filter.xxxxFilter urls=[/*] order=10,
com.xxxxx.filter.xxxsionFilter urls=[/*] order=20,
com.xxxxx.filter.xxxxxFilter urls=[/*] order=30,
com.xxxxx.filter.xxxxxerAcceptFilter urls=[/*] order=40,
com.xxxxx.filter.xxxEncodingFilter urls=[/*] order=50,
com.xxxxx.filter.xxxxx4ApiOriginFilter urls=[/*] order=60,
com.xxxxx.filter.xxxSignCheckFilter urls=[/*] order=70,
com.xxxxx.filter.xxxAuthenticationCheckFilter urls=[/*] order=80,
com.xxxxx.filter.xxAccessRestrictionFilter urls=[/*] order=90,
com.xxxxx.filter.xxxxreyFilter urls=[/*] order=100,
com.xxxxx.filter.xxxCheckAppVersionFilter urls=[/*] order=110,
characterEncodingFilter urls=[/*] order=-2147483648,
formContentFilter urls=[/*] order=-9900,
requestContextFilter urls=[/*] order=-105
last
感觉后续可以做一个 starter
包出来
网友评论