shiro源码篇 - shiro的filter,你值得拥有
前言
开心一刻
已经报废了一年多的电脑,今天特么突然开机了,吓老子一跳,只见电脑管家缓缓地出来了,本次开机一共用时一年零六个月,打败了全国0%的电脑,电脑管家已经对您的电脑失去信心,然后它把自己卸载了,只剩我在一旁发呆。
路漫漫其修远兮,吾将上下而求索!
github:
码云(gitee):
shirofilter的注册
讲到了springboot的filter注册,但只是filter注册的一种方式:通过filterregistrationbean实现。而shiro filter的注册是采用另外的方式实现的,我们接着往下看
shirofilterfactorybean实现了factorybean,我们来看下shirofilterfactorybean对factorybean的getobject方法的实现
git图一
可以看到createinstance()返回的是springshirofilter的实例;springshirofilter的类图如下
getobject方法只是创建了一个springshirofilter实例,并注册到了spring容器中,那是如何注册到servlet容器的呢?我们来跟下源码
其实涉及到了,只是那时候没细讲,我们还是从那里开始
gif图二
servletcontextinitializerbeans.java的构造方法,里面有addservletcontextinitializerbeans(beanfactory)方法和addadaptablebeans(beanfactory),我们来好好瞅一瞅
addservletcontextinitializerbeans(beanfactory)
private void addservletcontextinitializerbeans(listablebeanfactory beanfactory) { for (entry<string, servletcontextinitializer> initializerbean : getorderedbeansoftype( beanfactory, servletcontextinitializer.class)) { addservletcontextinitializerbean(initializerbean.getkey(), initializerbean.getvalue(), beanfactory); } } private void addservletcontextinitializerbean(string beanname, servletcontextinitializer initializer, listablebeanfactory beanfactory) { if (initializer instanceof servletregistrationbean) { servlet source = ((servletregistrationbean<?>) initializer).getservlet(); addservletcontextinitializerbean(servlet.class, beanname, initializer, beanfactory, source); } else if (initializer instanceof filterregistrationbean) { filter source = ((filterregistrationbean<?>) initializer).getfilter(); addservletcontextinitializerbean(filter.class, beanname, initializer, beanfactory, source); } else if (initializer instanceof delegatingfilterproxyregistrationbean) { string source = ((delegatingfilterproxyregistrationbean) initializer) .gettargetbeanname(); addservletcontextinitializerbean(filter.class, beanname, initializer, beanfactory, source); } else if (initializer instanceof servletlistenerregistrationbean) { eventlistener source = ((servletlistenerregistrationbean<?>) initializer) .getlistener(); addservletcontextinitializerbean(eventlistener.class, beanname, initializer, beanfactory, source); } else { addservletcontextinitializerbean(servletcontextinitializer.class, beanname, initializer, beanfactory, initializer); } }
会将spring bean工厂(beanfactory)中类型是servletcontextinitalizer类型的实例(包括servletregistrationbean、filterregistrationbean、delegatingfilterproxyregistrationbean、servletlistenerregistrationbean)添加进servletcontextinitializerbeans的initializers属性中。
private final multivaluemap<class<?>, servletcontextinitializer> initializers;
servletcontextinitalizer的子类图如下所示
这也就是注册filter的方式,以registrationbean方式实现的,但是springshirofilter不是在这添加进servletcontextinitializerbeans的initializers中的哦
addadaptablebeans(beanfactory)
private void addadaptablebeans(listablebeanfactory beanfactory) { multipartconfigelement multipartconfig = getmultipartconfig(beanfactory); addasregistrationbean(beanfactory, servlet.class, new servletregistrationbeanadapter(multipartconfig)); addasregistrationbean(beanfactory, filter.class, new filterregistrationbeanadapter()); for (class<?> listenertype : servletlistenerregistrationbean .getsupportedtypes()) { addasregistrationbean(beanfactory, eventlistener.class, (class<eventlistener>) listenertype, new servletlistenerregistrationbeanadapter()); } }
会将beanfactory中的servlet、filter、listener实例封装成对应的registrationbean,然后添加到servletcontextinitializerbeans的initializers;部分细节没去跟,有兴趣的可以自行去跟下源代码。
servletcontextinitializerbeans的sortedlist的内容最终如下
然后遍历这个sortedlist,逐个注入到servlet容器
小结
springboot下有3种方式注册filter(servlet、listener类似),filterregistrationbean、@webfilter 和@bean,@webfilter我还没试过,另外这3种方式注册的filter的优先级是:filterregistrationbean > @webfilter > @bean(网上查阅的资料,我没试哦,使用过程中需要注意!)。
不管是filterregistrationbean方式、@webfilter方式,还是@bean方式,只要是受spring容器管理,最终都会添加到servletcontextinitializerbeans的initializers中,都会成功注册到servlet容器。@webfilter方式和@bean方式注册的filter都会被封装成filterregistrationbean,然后添加进servletcontextinitializerbeans的initializers;3中方式最终殊途同归,都以filterregistrationbean的形式存在servletcontextinitializerbeans的initializers中。springshirofilter的注册算是@bean方式注册的,至此springshirofilter就注册到了servlet容器中了。
servletcontextinitializerbeans的sortedlist如下:
private list<servletcontextinitializer> sortedlist;
是一个有序的servletcontextinitializer list,这个有序针对的同类型,比如所有的filterregistrationbean有序,所有的servletregistrationbean有序,filterregistrationbean与servletregistrationbean之间有没有序是无意义的。
shiro中的filter链
shiro的默认filter列表
除了springshirofilter之外,shiro还有默认的11个filter;细心的朋友应该在git图一中已经发现了,在创建defaultfilterchainmanager,就把默认的11个filter添加到它的filters中
private map<string, filter> filters; //pool of filters available for creating chains private map<string, namedfilterlist> filterchains; //key: chain name, value: chain public defaultfilterchainmanager() { this.filters = new linkedhashmap<string, filter>(); this.filterchains = new linkedhashmap<string, namedfilterlist>(); adddefaultfilters(false); // 将默认的11个filter添加到filters }
这11个filter具体如下
anon(anonymousfilter.class), authc(formauthenticationfilter.class), authcbasic(basichttpauthenticationfilter.class), logout(logoutfilter.class), nosessioncreation(nosessioncreationfilter.class), perms(permissionsauthorizationfilter.class), port(portfilter.class), rest(httpmethodpermissionfilter.class), roles(rolesauthorizationfilter.class), ssl(sslfilter.class), user(userfilter.class);
filter链
springshirofilter注册到servlet容器中,请求肯定会经过springshirofilter的dofilter方法,我们就从此开始跟一跟源代码
gif图三
上图中可能展示的不够细,主要就是两点:1、路径匹配:pathmatches(pathpattern, requesturi),默认的fliter逐个与请求uri进行匹配;2、代理filterchain:proxiedfilterchain。如果匹配不上,那么直接走servlet的filterchain,否则先走shiro的代理filterchain(proxiedfilterchain),之后再走servlet的filterchain。
proxiedfilterchain源代码如下
/* * licensed to the apache software foundation (asf) under one * or more contributor license agreements. see the notice file * distributed with this work for additional information * regarding copyright ownership. the asf licenses this file * to you under the apache license, version 2.0 (the * "license"); you may not use this file except in compliance * with the license. you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, * software distributed under the license is distributed on an * "as is" basis, without warranties or conditions of any * kind, either express or implied. see the license for the * specific language governing permissions and limitations * under the license. */ package org.apache.shiro.web.servlet; import org.slf4j.logger; import org.slf4j.loggerfactory; import javax.servlet.*; import java.io.ioexception; import java.util.list; /** * a proxied filter chain is a {@link filterchain} instance that proxies an original {@link filterchain} as well * as a {@link list list} of other {@link filter filter}s that might need to execute prior to the final wrapped * original chain. it allows a list of filters to execute before continuing the original (proxied) * {@code filterchain} instance. * * @since 0.9 */ public class proxiedfilterchain implements filterchain { //todo - complete javadoc private static final logger log = loggerfactory.getlogger(proxiedfilterchain.class); private filterchain orig; // 原filterchain,也就是servlet容器的filterchain private list<filter> filters; // shiro默认的11个filter private int index = 0; public proxiedfilterchain(filterchain orig, list<filter> filters) { if (orig == null) { throw new nullpointerexception("original filterchain cannot be null."); } this.orig = orig; this.filters = filters; this.index = 0; } public void dofilter(servletrequest request, servletresponse response) throws ioexception, servletexception { if (this.filters == null || this.filters.size() == this.index) { //we've reached the end of the wrapped chain, so invoke the original one: if (log.istraceenabled()) { log.trace("invoking original filter chain."); } this.orig.dofilter(request, response); // 当shiro的11个filter走完之后,继续走servlet容器的filterchain } else { if (log.istraceenabled()) { log.trace("invoking wrapped filter at index [" + this.index + "]"); } this.filters.get(this.index++).dofilter(request, response, this); // 递归逐个走shiro的11个filter } } }
shiro对servlet容器的filterchain进行了代理,即shirofilter在继续servlet容器的filter链的执行之前,通过proxiedfilterchain对servlet容器的filterchain进行了代理;即先走shiro自己的filter体系,然后才会委托给servlet容器的filterchain进行servlet容器级别的filter链执行;shiro的proxiedfilterchain执行流程:1、先执行shiro自己的filter链;2、再执行servlet容器的filter链(即原始的 filter)。
总结
1、springshirofilter注册到spring容器,会被包装成filterregistrationbean,通过filterregistrationbean注册到servlet容器;
2、一般而言,shiro的pathmatchingfilterchainresolver会匹配所有的请求,shiro对servlet容器的filterchain进行了代理,生成代理filterchain:proxiedfilterchain,请求先走shiro自己的filter链,再走servelt容器的filter链;
3、题外话,springboot注册filter、servlet、listener方式类似,都有3种,具体是哪三种,大家去上文看;关于shiro的filter,本文没做更详细的讲解,需要了解的可以去看《跟我学shiro》
参考
《跟我学shiro》
shiro源码
推荐阅读
-
shiro源码篇 - shiro的filter,你值得拥有
-
spring-boot-2.0.3不一样系列之源码篇 - run方法(三)之createApplicationContext,绝对有值得你看的地方
-
shiro源码篇 - shiro的session管理,你值得拥有
-
shiro源码篇 - shiro的session共享,你值得拥有
-
spring-boot-2.0.3不一样系列之源码篇 - SpringApplication的run方法(一)之SpringApplicationRunListener,绝对有值得你看的地方
-
shiro源码篇 - shiro认证与授权,你值得拥有
-
shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有
-
spring-boot-2.0.3不一样系列之源码篇 - run方法(三)之createApplicationContext,绝对有值得你看的地方
-
shiro源码篇 - shiro的session管理,你值得拥有
-
spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方