美文网首页
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