博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
DelegatingFilterProxy
阅读量:2350 次
发布时间:2019-05-10

本文共 7829 字,大约阅读时间需要 26 分钟。


前言

最近可算把一个项目整完了,然后最近打算来消化下项目中不懂的地方。项目中用到了spring-security来做验证授权,这一块是自己的知识盲区。所以先就来攻克这一块。

我们都知道在使用spring-security的时候都需要在web.xml做如下配置,这样的配置就表明了spring-security实际上是通过过滤器来实现的,很显然利用Filter确实是最好的选择。那么到底是如何通过以下配置,让过滤器和spring-security联系起来的呢,我们就来探讨一下,找到执行这一块的过程,接着,我们就使用spring-security的官方文档来一边学习一边看源码。

springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*

DelegatingFilterProxy

这个类的作用就和他的名字一样,他就是一个起代理作用的过滤器,而真正的拦截器当然就是是spring-security里面的配置咯。但是我们知道,过滤器是由Tomcat来解析加载的,跟Spring容器是没有关系的。

所以我们就带着两个问题来看看这个类:

0.该类是如何完成代理。

1.是如何将过滤器和Spring容器结合起来的。

我们看源码:

先看父类GenericFilterBean

public abstract class GenericFilterBean implements        Filter, BeanNameAware, EnvironmentAware, ServletContextAware, InitializingBean, DisposableBean {

他的父类GenericFilterBean实现了很多接口,其中我们最熟悉的就是InitializingBean了。我们知道注入到Spring的Bean,在实例化之后,如果有实现InitializingBean接口的,会调用他的afterPropertiesSet方法。

而我们也注意到虽然GenericFilterBean是一个抽象类,但是他显式的实现了Filter的init方法,而子类DelegatingFilterProxy并没有显式的实现init方法,所以,也就是说当我们向tomcat注册了DelegatingFilterProxy过滤器后,会调用该监过滤器的init方法,而实际上调用的是父类的init方法,我们来看看这个方法做了什么。

public final void init(FilterConfig filterConfig) throws ServletException {        Assert.notNull(filterConfig, "FilterConfig must not be null");        if (logger.isDebugEnabled()) {            logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");        }        this.filterConfig = filterConfig;        // Set bean properties from init parameters.        try {            PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);            ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));            initBeanWrapper(bw);            bw.setPropertyValues(pvs, true);        }        catch (BeansException ex) {            String msg = "Failed to set bean properties on filter '" +                filterConfig.getFilterName() + "': " + ex.getMessage();            logger.error(msg, ex);            throw new NestedServletException(msg, ex);        }        // Let subclasses do whatever initialization they like.        initFilterBean();        if (logger.isDebugEnabled()) {            logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");        }    }

这个init()方法主要前面很长一段代码,直到 initFilterBean();之前,都是用来获取Filter配置中的参数即init-param value的配置。我们需要关注的就是initFilterBean();方法,这个方法在父类中是一个抽象方法,具体的实现在

@Override    protected void initFilterBean() throws ServletException {        synchronized (this.delegateMonitor) {            if (this.delegate == null) {                // If no target bean name specified, use filter name.                if (this.targetBeanName == null) {                    this.targetBeanName = getFilterName();                }                // Fetch Spring root application context and initialize the delegate early,                // if possible. If the root application context will be started after this                // filter proxy, we'll have to resort to lazy initialization.                WebApplicationContext wac = findWebApplicationContext();                if (wac != null) {                    this.delegate = initDelegate(wac);                }            }        }    }

我们分析下这个方法

0.首先要做一个同步操作。

1.然后进入同步块,如果当前的代理对象为null,查看targetBeanName对象是否为null,targetBeanName属性可以通过在web.xml中的filter中的init-param中配置。一般我们不显式的配置。

2.如果我们没有显式的配置的话,就调用getFilterName方法来得到targetBeanName:

protected final String getFilterName() {        return (this.filterConfig != null ? this.filterConfig.getFilterName() : this.beanName);    }

方法很简单,如果这时我们没有在filter初始化参数中配置beanName的话,我们就取当前filterName,filterName是filter配置中必须要配置的。

3.得到当前的spring上下文,因为当我们通过ContextLoaderListener这个监听器去初始化Spring容器的时候,会把根上下文当做一个attribute注入到servletContext(即servlet上下文)中,key为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,value为根上下文,而我们在过滤器中又能通过FIlterConfig.getServletContext去得到ServletContext,所以我们也就顺理成章的能得到spring上下文了。

4.当前上下文不为空的时候,调用initDelegate(wac);来得到真正用来执行的过滤器。

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {        Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);        if (isTargetFilterLifecycle()) {            delegate.init(getFilterConfig());        }        return delegate;    }

这个方法很简单,就是通过限定BeanName以及Bean的类型为Filter来获取bean,而这里的BeanName就是targetName,当没有在filter配置中配置的时候,我们直接用的是filterName,而我们这里的配置的filterName是springSecurityFilterChain。看到这里,你肯定能得出一个结论,那就是如果你不在filter里配置targetBeanName或者beanName的时候,你的filterName就必须是springSecurityFilterChain,否则,肯定就无法在Spring找到delegate对象了,而BeanName为springSecurityFilterChain的Bean对象肯定在我们配置spring-security配置的时候以默认的方式注入到了spring,只有这样才说的通。

而对于DelegatingFilterProxy的dofilter方法,我们不用多看,猜也猜得到内部是调用delegate.dofilter方法。

那文章的最后,我们就来找一下到底这个BeanName为springSecurityFilterChain的Bean对象是哪个把,也算为我们之后阅读spring-security模块的代码开个头。

我们知道spring-security的配置是如下的形式:

而且会使用spring-security命名空间。很明显又是使用了命名空间来解析xml配置。这里我们不赘述整个过程了,我们直接找到解析http节点的BeanDefinitionParser实现类。

这个类就是org.springframework.security.config.http.HttpSecurityBeanDefinitionParser,我们直接看他的parse方法。

public BeanDefinition parse(Element element, ParserContext pc) {        CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(                element.getTagName(), pc.extractSource(element));        pc.pushContainingComponent(compositeDef);        registerFilterChainProxyIfNecessary(pc, pc.extractSource(element));        // Obtain the filter chains and add the new chain to it        BeanDefinition listFactoryBean = pc.getRegistry().getBeanDefinition(                BeanIds.FILTER_CHAINS);        List
filterChains = (List
) listFactoryBean .getPropertyValues().getPropertyValue("sourceList").getValue(); filterChains.add(createFilterChain(element, pc)); pc.popAndRegisterContainingComponent(); return null; }

这个方法的具体实现我们先不分析,我们看到他的一个子方法

registerFilterChainProxyIfNecessary

static void registerFilterChainProxyIfNecessary(ParserContext pc, Object source) {        if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_PROXY)) {            return;        }        // Not already registered, so register the list of filter chains and the        // FilterChainProxy        BeanDefinition listFactoryBean = new RootBeanDefinition(ListFactoryBean.class);        listFactoryBean.getPropertyValues().add("sourceList", new ManagedList());        pc.registerBeanComponent(new BeanComponentDefinition(listFactoryBean,                BeanIds.FILTER_CHAINS));        BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder                .rootBeanDefinition(FilterChainProxy.class);        fcpBldr.getRawBeanDefinition().setSource(source);        fcpBldr.addConstructorArgReference(BeanIds.FILTER_CHAINS);        fcpBldr.addPropertyValue("filterChainValidator", new RootBeanDefinition(                DefaultFilterChainValidator.class));        BeanDefinition fcpBean = fcpBldr.getBeanDefinition();        pc.registerBeanComponent(new BeanComponentDefinition(fcpBean,                BeanIds.FILTER_CHAIN_PROXY));        pc.getRegistry().registerAlias(BeanIds.FILTER_CHAIN_PROXY,                BeanIds.SPRING_SECURITY_FILTER_CHAIN);    }}

我们看到倒数第二行代码,将一个Bean注入到spring中,名字为BeanIds.FILTER_CHAIN_PROXY。

接着我们看到方法的最后一行代码,给这个Bean赋一个别名BeanIds.SPRING_SECURITY_FILTER_CHAIN,而对应的值就是springSecurityFilterChain

/** External alias for FilterChainProxy bean, for use in web.xml files */    public static final String SPRING_SECURITY_FILTER_CHAIN = "springSecurityFilterChain";

所以这里也就完成了DelegatingFilterProxy中delegate对象注入到spring容器的过程。

转载地址:http://sdmvb.baihongyu.com/

你可能感兴趣的文章
android错误之android.content.res.Resources$NotFoundException:
查看>>
Android监听软键盘打开收起事件(软键盘自带收起按钮)
查看>>
huffman code and encode
查看>>
exception in c++
查看>>
java并发编程lock
查看>>
阿里云技术教程系列-ECS远程连接 Linux 实例
查看>>
Linux新建用户并允许docker
查看>>
Drools Workbench 7.5.0.Final安装运行
查看>>
Docker快速部署Redis
查看>>
Spring boot shiro session cache ecache redis 共存配置
查看>>
一看就懂的设计模式--设计模式分类
查看>>
一看就懂的设计模式--模板方法
查看>>
一看就懂的设计模式--享元模式
查看>>
一看就懂的设计模式--策略模式
查看>>
spring Cloud 组建图
查看>>
腾讯云
查看>>
什么服务器比较好?
查看>>
阿里云+腾讯云采购季优惠攻略
查看>>
PCB设计容易出错的地方都有哪些?
查看>>
挠性电路板和刚性电路板的区别,以及柔性电路板焊接方法操作步骤
查看>>