在配置shiro的時候第一件事情就是在web.xml文件中配置一個由spring提供的類:org.springframework.web.filter.DelegatingFilterProxy 按照字面的翻譯這應該是一個代理過濾器的策略。css
<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <async-supported>true</async-supported> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> </filter-mapping>
這個類實際上是將上下文中名稱爲shiroFilter的類作成一個代理過濾器。該類將從spring上下文中找到本身要代理的過濾器類,並負責初始化和銷燬該過濾器。並在每次發起的攔截請求是先走該類的方法invokeDelegate()。java
##初始化web
protected Filter initDelegate(WebApplicationContext wac) throws ServletException { Filter delegate = wac.getBean(getTargetBeanName(), Filter.class); if (isTargetFilterLifecycle()) { delegate.init(getFilterConfig()); } return delegate; }
初始化過程就是將配置文件中的參數注入到ShiroFilterFactoryBean中的SpringShiroFilter中。spring
##請求調用 當咱們在瀏覽器中打印了一個可以被shiro攔截的uri的時候首先會進入到DelegatingFilterProxy的doFilter方法中。該方法首先判斷被代理的攔截器是否被初始化,若是沒有則實行懶加載策略初始化shiro攔截器。不然則開始調用shiro攔截器去執行攔截。設計模式
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { // Lazily initialize the delegate if necessary. Filter delegateToUse = this.delegate; if (delegateToUse == null) {//懶加載過程 synchronized (this.delegateMonitor) { if (this.delegate == null) { WebApplicationContext wac = findWebApplicationContext(); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?"); } this.delegate = initDelegate(wac); } delegateToUse = this.delegate; } } invokeDelegate(delegateToUse, request, response, filterChain); } protected void invokeDelegate( Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 調用真正shirofilter delegate.doFilter(request, response, filterChain); }
delegate實例最終類型是SpringShiroFilter。該類集成在AbstractShiroFilter。瀏覽器
private static final class SpringShiroFilter extends AbstractShiroFilter { protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) { super(); if (webSecurityManager == null) { throw new IllegalArgumentException("WebSecurityManager property cannot be null."); } setSecurityManager(webSecurityManager); if (resolver != null) { setFilterChainResolver(resolver); } } }
AbstractShiroFilter類繼承自OncePerRequestFiltersession
public abstract class AbstractShiroFilter extends OncePerRequestFilter{}
因此咱們首先來看看OncePerRequestFilter。mvc
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {//不攔截已經被攔截處理的請求 log.trace("Filter '{}' already executed. Proceeding without invoking this filter.", getName()); filterChain.doFilter(request, response); } else //不過濾被配置不過濾的請求 if (/* added in 1.2: */ !isEnabled(request, response) || /* retain backwards compatibility: */ shouldNotFilter(request) ) { log.debug("Filter '{}' is not enabled for the current request. Proceeding without invoking this filter.", getName()); filterChain.doFilter(request, response); } else { //未來自該過濾請求(如shiroFitler)設置成已經被過濾 log.trace("Filter '{}' not yet executed. Executing now.", getName()); request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); try { //執行shiro的過濾邏輯。 doFilterInternal(request, response, filterChain); } finally { // Once the request has finished, we're done and we don't // need to mark as 'already filtered' any more. request.removeAttribute(alreadyFilteredAttributeName); } } }
若是到達的請求沒有被OncePerRequestFilter過濾掉,則會走shiro的攔截請求。值得一塊兒的是,FilterChain chain實際上是spring的servlet上下文。這個是爲了包裝request、response的時候將servlet上下文也放置進來。app
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) throws ServletException, IOException { Throwable t = null; try { final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);//包裝request final ServletResponse response = prepareServletResponse(request, servletResponse, chain);//包裝response final Subject subject = createSubject(request, response);//建立subject對象 //執行回調函數。 //一、更新session的最後訪問時間 //二、執行攔截器鏈。 subject.execute(new Callable() { public Object call() throws Exception { updateSessionLastAccessTime(request, response); executeChain(request, response, chain); return null; } }); } catch (ExecutionException ex) { t = ex.getCause(); } catch (Throwable throwable) { t = throwable; } if (t != null) { if (t instanceof ServletException) { throw (ServletException) t; } if (t instanceof IOException) { throw (IOException) t; } //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one: String msg = "Filtered request failed."; throw new ServletException(msg, t); } }
shiro的攔截器首先將request和response進行了包裝。並建立subject執行回調函數。該函數主要執行了兩個部分:一、更新session的最後訪問時間 二、執行攔截器鏈。重點看下執行攔截器鏈邏輯。框架
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain) throws IOException, ServletException { FilterChain chain = getExecutionChain(request, response, origChain);//得到攔截器鏈 chain.doFilter(request, response);//執行攔截器鏈 }
攔截器邏輯主要分紅了兩個部分:一、得到攔截器鏈 二、執行攔截器鏈。 一、得到攔截器鏈
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) { FilterChain chain = origChain; //1.1得到攔截器鏈解析器 FilterChainResolver resolver = getFilterChainResolver(); if (resolver == null) { log.debug("No FilterChainResolver configured. Returning original FilterChain."); return origChain; } //1.2得到最終的攔截器執行鏈。 FilterChain resolved = resolver.getChain(request, response, origChain); if (resolved != null) { log.trace("Resolved a configured FilterChain for the current request."); chain = resolved; } else { log.trace("No FilterChain configured for the current request. Using the default."); } return chain; }
1.一、得到攔截器鏈解析器最終得到了PathMatchingFilterChainResolver的實例。 1.二、得到最終的攔截器執行鏈是執行PathMatchingFilterChainResolver中的getChain方法。
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) { //得到過濾鏈管理器。該管理器是在初始化SpringShiroFilter時候注入進來的。 FilterChainManager filterChainManager = getFilterChainManager(); if (!filterChainManager.hasChains()) { return null; } //得到請求uri String requestURI = getPathWithinApplication(request); //uri和攔截器鏈作匹配。 for (String pathPattern : filterChainManager.getChainNames()) { //若是匹配成功 if (pathMatches(pathPattern, requestURI)) { //1.3返回一個代理過濾器 return filterChainManager.proxy(originalChain, pathPattern); } } return null; } ``` 1.3是將當前的servlet上下文和請求uri一塊兒包裝成爲一個過濾器代理。 ```java FilterChainManager類 public FilterChain proxy(FilterChain original, String chainName) { NamedFilterList configured = getChain(chainName); if (configured == null) { String msg = "There is no configured chain under the name/key [" + chainName + "]."; throw new IllegalArgumentException(msg); } return configured.proxy(original); } public class SimpleNamedFilterList implements NamedFilterList { public FilterChain proxy(FilterChain orig) { return new ProxiedFilterChain(orig, this); } }
二、執行攔截器鏈 剛纔看到得到攔截器的過程其實就是生成了一個類型爲ProxiedFilterChain的實例。那麼執行天然就是該類的doFilter方法。
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { //若是過濾器鏈爲空,則執行servlet上下文的過濾器。 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); } else { //不然執行過濾器鏈的邏輯。 if (log.isTraceEnabled()) { log.trace("Invoking wrapped filter at index [" + this.index + "]"); } this.filters.get(this.index++).doFilter(request, response, this); } }
這裏面涵蓋了一個過濾器的設計模式。當咱們給某個uri設計多個攔截器的時候,這幾個攔截器是怎麼連續執行的呢?換句話說上面的index是如何一個一個累加直到this.filters.size() == this.index條件成立跳出循環的呢?咱們來看下shiro裏面的攔截器鏈實現。 首先觀察一下shiro配置文章的這個段
/static/** = anon /js/** =anon /css/** =anon /favicon.ico =anon /images/** = anon /logout = logout /user/login=authc /** =sysUser,onlineSession,syncOnlineSession,perms,roles
咱們不難發現這裏面的攔截器都擴展自接口AdviceFilter或接口AdviceFilter的實現。給接口做用就是在執行攔截器先後添加一些邏輯。
public abstract class AdviceFilter extends OncePerRequestFilter { protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { return true; } protected void postHandle(ServletRequest request, ServletResponse response) throws Exception { } public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception { } protected void executeChain(ServletRequest request, ServletResponse response, FilterChain chain) throws Exception { chain.doFilter(request, response); } //執行邏輯 public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { Exception exception = null; try { //執行前的邏輯,若是未返回true則不執行過濾器邏輯。 boolean continueChain = preHandle(request, response); if (log.isTraceEnabled()) { log.trace("Invoked preHandle method. Continuing chain?: [" + continueChain + "]"); } //執行過濾器邏輯 if (continueChain) { executeChain(request, response, chain); } //執行後邏輯。 postHandle(request, response); if (log.isTraceEnabled()) { log.trace("Successfully invoked postHandle method"); } } catch (Exception e) { exception = e; } finally { cleanup(request, response, exception); } } protected void cleanup(ServletRequest request, ServletResponse response, Exception existing) throws ServletException, IOException { } }
AdviceFilter擴展自OncePerRequestFilter類。
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); if ( request.getAttribute(alreadyFilteredAttributeName) != null ) { log.trace("Filter '{}' already executed. Proceeding without invoking this filter.", getName()); //執行過濾器鏈下一個過濾器 filterChain.doFilter(request, response); } else //noinspection deprecation if (/* added in 1.2: */ !isEnabled(request, response) || /* retain backwards compatibility: */ shouldNotFilter(request) ) { log.debug("Filter '{}' is not enabled for the current request. Proceeding without invoking this filter.", getName()); //執行過濾器鏈下一個過濾器 filterChain.doFilter(request, response); } else { // Do invoke this filter... log.trace("Filter '{}' not yet executed. Executing now.", getName()); request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); try { //執行本過濾器。 doFilterInternal(request, response, filterChain); } finally { // Once the request has finished, we're done and we don't // need to mark as 'already filtered' any more. request.removeAttribute(alreadyFilteredAttributeName); } } }
這兩個類的共同特色就是執行doFilterInternal方法是執行本過濾器的過濾邏輯。執行filterChain是執行過利器鏈裏面下一個過濾器的執行邏輯。filterChain的類型都是ProxiedFilterChain。
public class ProxiedFilterChain implements FilterChain { //TODO - complete JavaDoc private static final Logger log = LoggerFactory.getLogger(ProxiedFilterChain.class); private FilterChain orig; private List<Filter> filters; 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."); } //若是過濾器爲空,或者過濾器鏈已經執行完,則orig裏面若是有過濾器邏輯則執行。 this.orig.doFilter(request, response); } else { if (log.isTraceEnabled()) { log.trace("Invoking wrapped filter at index [" + this.index + "]"); } //得到第index個過濾器並執行。 this.filters.get(this.index++).doFilter(request, response, this); } }
過濾器模式簡單地說就是:到了某個過濾器他會執行本身的邏輯(doFilterInternal方法),執行完本身的邏輯以後,他還會到剛剛傳過來的過濾器鏈的doFilter方法返回到過濾器鏈下一個過濾器繼續執行。 這裏也有個疑惑,就是過濾器鏈執行完畢以後應該怎麼返回到springmvc的DispatcherServlet裏面的service()方法繼續spring的邏輯呢? 答案就在這行: this.orig.doFilter(request, response); 咱們在配置到web.xml文件中的過濾器和servlet的執行順序首先按照配置的前後順序執行過濾器,當過濾器執行完畢的時候會調用 FilterChain的doFilter()方法返回到servlet環境,而後再原路返回。那麼爲何orig.doFilter可以返回到servlet呢,查看源碼filterChian既不是servlet的代理也不是servlet的包裝器。那什麼時機讓orig成爲了servlet返回路徑的呢? dubug到這裏咱們發現瞭如圖的類:ServletHandler。原來這個ServletHandler是jetty用於管理Filter、FilterMapping、Servlet、ServletMapping的容器。(我用的是jetty,本段環境爲jetty環境)。以及用於實現一次請求所對應的Filter鏈和Servlet執行流程的類。對Servlet的框架實現中,它也被認爲是Handler鏈的末端,於是在它的doHandle()方法中沒有調用nextHandle()方法。至於具體實現就等分析jetty源碼的時候闡述了。