美文网首页
spring boot web filter 基于注解实现指定顺

spring boot web filter 基于注解实现指定顺

作者: Yellowtail | 来源:发表于2021-12-08 18:29 被阅读0次

概述

spring boot里大家都知道怎么去写一个 Filter 去对请求进行过滤
大概步骤是:

  • application 上用注解 @ServletComponentScan开启功能
  • coding, 写一个类,实现 javax.servlet.Filter,加上 WebFilter 注解

但是在实际开发过程中会发现:当有多个filter,且我们希望有先后顺序的时候,spring boot 没有提供这样的实现

因为 Filter 是给 web 容器(如 tomcat 使用的),不属于 bean, 所以 spring 无法管理他们的顺序
所以 在类上加上 Order Component 等 spring 的注解是没有用的

本文就记录一下我尝试的4种方法,如下:

  1. 加上 Order Component 等 spring 的注解, 没有用
  2. 改变类名,以 A B C.. 开头,不是100%生效
  3. 注入

尝试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的(通过 RegistrationBeanpublic 方法)
这个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

顺序也符合预期


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 包出来

相关文章

网友评论

      本文标题:spring boot web filter 基于注解实现指定顺

      本文链接:https://www.haomeiwen.com/subject/iuvtfrtx.html