前言 開心一刻 已經報廢了一年多的電腦,今天特麽突然開機了,嚇老子一跳,只見電腦管家緩緩地出來了,本次開機一共用時一年零六個月,打敗了全國0%的電腦,電腦管家已經對您的電腦失去信心,然後它把自己卸載了,只剩我在一旁發呆。 路漫漫其修遠兮,吾將上下而求索! github:https://github. ...
前言
開心一刻
已經報廢了一年多的電腦,今天特麽突然開機了,嚇老子一跳,只見電腦管家緩緩地出來了,本次開機一共用時一年零六個月,打敗了全國0%的電腦,電腦管家已經對您的電腦失去信心,然後它把自己卸載了,只剩我在一旁發呆。
路漫漫其修遠兮,吾將上下而求索!
github:https://github.com/youzhibing
碼雲(gitee):https://gitee.com/youzhibing
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); } }View Code
會將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()); } }View Code
會將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 } } }View Code
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源碼