Web應用服務器 相關知識梳理(三)Tomcat的設計模式

(一)門面設計模式

1. 應用場景

        在一個大的系統中有多個子系統,而每個子系統肯定不能將自己的內部數據過多的暴露給其他系統,否則將失去劃分各個子系統的意義;則此時每個子系統都會設計一個門面,將其他系統經常訪問或者感興趣的數據封裝在這個門面類中,通過這個門面類與其他系統進行數據交互。

2. 示意圖

                     

3. 在Tomcat中的應用示例      

        如  在Tomcat中當瀏覽器發過來的TCP連接請求通過Request及Response對象進行和Container交流時,那Request及Response對象在一次請求中的變化情況:

                   

         另如    StandardWrapper 及 StandardWrapperFacade 都實現了ServletConfig接口,而StandardWrapperFacade作爲StandardWrapper的門面類,所以在Wrapper容器中裝載Servlet時,將StandardWrapperFacade作爲ServletConfig參數傳遞到Servlet保證了從StandardWrapper中拿到ServletConfig所規定的的參數,不會將無關數據也暴露到StandardWrapper傳入Servlet:

private synchronized void initServlet(Servlet servlet)throws ServletException {
 
    if (instanceInitialized && !singleThreadModel) return;
 
    // Call the initialization method of this servlet
    try {
        if( Globals.IS_SECURITY_ENABLED) {
            boolean success = false;
            try {
                Object[] args = new Object[] { facade };
                SecurityUtil.doAsPrivilege("init",servlet,classType,args);
                success = true;
            } finally {
                if (!success) {
                    // destroy() will not be called, thus clear the reference now
                    SecurityUtil.remove(servlet);
                }
            }
        } else {
            //核心:調用Servlet的init方法,並將StandardWrapper對象的門面類對象StandardWrapperFacade作爲ServletConfig參數傳入
            servlet.init(facade);
        }
 
        instanceInitialized = true;
    } catch (UnavailableException f) {
        unavailable(f);
        throw f;
    } catch (ServletException f) {
        // If the servlet wanted to be unavailable it would have
        // said so, so do not call unavailable(null).
        throw f;
    } catch (Throwable f) {
        ExceptionUtils.handleThrowable(f);
        getServletContext().log("StandardWrapper.Throwable", f );
        // If the servlet wanted to be unavailable it would have
        // said so, so do not call unavailable(null).
        throw new ServletException(sm.getString("standardWrapper.initException",getName()),f);
    }
}

      另如  ServletContext,在Servlet中能拿到的實際對象也是ApplicationContextFacade對象,同樣保證ServletContext只能從容器中拿到它該拿的數據。

(二)觀察者設計模式

1. 應用場景       

      也稱發佈-訂閱模式,也就是事件監聽機制;A盯着B做事,如果B做得事A也感興趣,則B做的同時也會觸發讓A做某些操作;但同時A必須向B進行註冊,否則無法進行觸發關聯操作。

      Subject抽象主題:管理註冊的所有觀察者A的引用,以及一些事件操作;

      ConcreteSubject具體主題:實現Subject抽象主題中的那些事件接口,當發生變化時通知觀察者A;

      Observer觀察者:監聽主題B發生變化時觸發的另外一系列事件操作.

2. 示意圖

                       

3. 在Tomcat中的應用示例

                     

                  LifecycleListener代表抽象觀察者,其中包含一個lifecycleEvent方法:定義了當主題變化時要執行的邏輯;

                  ServerLifecycleListener代表具體的觀察者實現;

                  Lifecycle代表抽象主題,定義了管理觀察者的方法及其他操作;

                  StandardSubject代表具體主題;

                  同時存在兩個觀察者輔助擴展類:LifecycleSupport、LifecycleEvent.

                  那麼主題是如何通知觀察者的吶???

               

                

               那這裏不得不提到目前Servlet中提供的6種兩類事件的觀察者接口:

                   

                  注意:ServletContextListener 在容器啓動之後就不能再添加新的。

(三)命令設計模式

1. 應用場景

        命令模式主要作用:封裝命令,然後將 發出命令 及 執行命令 的責任分開;不同模塊可以對同一命令做出不同的解釋。

        Client:將 發出命令 及 執行命令 等過程中使用到的角色進行組裝

        Invoker:請求者,負責調用ConcreteCommand來執行請求

        Command:命令接口,定義一個抽象命令

        ConcreteCommand:具體命令,負責調用接受者的相關操作

        Receiver:接受者,負責具體實施和執行一次請求的邏輯

2. 示意圖

            

 代碼示例:

//抽象命令
public interface Command {

    //這個方法是一個返回結果爲空的方法
    //實際項目中,可以根據需求設計多個不同的方法 
    void execute();
}

//具體命令實現
public class ConcreteCommand implements Command{
    
    private Receiver receiver; //命令的真正執行者
    
    public ConcreteCommand(Receiver receiver) {
        super();
        this.receiver = receiver;
    }

    @Override
    public void execute() {
        //真正之前或後,執行相關的處理
        receiver.action();
    }
    
}

//調用者/發起者
public class Invoker {

    public Command command; //也可以通過容器List<Command>容納很多命令對象,進行批處理,數據庫底層事務管理就是類似的構造
    public Invoker(Command command) {
        super();
        this.command = command;
    }
    
    //業務方法,用於調用命令類的方法 
    public void call(){
        command.execute();
    }
}

//真正的命令執行者
public class Receiver {
    public void action(){
        System.out.println("Receiver.action()");
    }
}

// 客戶端 各個角色組裝
public class Client {
    public static void main(String[] args) {
        Command c = new ConcreteCommand(new Receiver());
        Invoker i = new Invoker(c);        
        i.call();
    }
}

3. 在Tomcat中的應用示例

          在以前老的版本中,Connector是利用HttpConnector、HttpProcessor、ContainerBase通過命令設計模式來調用Container的。

         

            Connector:抽象請求者、HttpConnector:具體請求者

            HttpProcessor:命令

            Container:抽象接受者、ContainerBase:具體接受者

            Server組件:客戶端

 

(四)責任鏈設計模式

1. 應用場景

           形成一條由每個對象對其下家的引用而連接成的鏈,請求在這條鏈上傳遞,直到鏈上的 某個對象處理此請求 或 每個對象都可以處理請求,並傳給‘ 下家 ’,直到鏈上每個對象都處理完;此過程中不影響客戶端在鏈上增加任意處理節點。

2. 示例圖

                 

                         抽象處理者 Handler:定義一個處理請求的接口

                         具體處理者 ConcreteHandler:具體處理請求的類,或者傳遞給‘ 下家 ’

                         詳情參考: https://blog.csdn.net/tuzhihai/article/details/75035865

3. 在Tomcat中的應用示例

          (1)在Tomcat中的Container設計便利用該設計模式,從 Engine——Host——Context——Wrapper一直將請求正確地傳遞給最終處理請求的那個Servlet。

        (2)javax.servlet.FilterConfig 及 javax.servlet.FilterChain 在Tomcat中的實現類分別是ApplicationFilterConfig和ApplicationFilterChain;在doFilter(ServletRequest,ServletResponse,FilterChain)方法中,FilterChain就代表當前整個請求鏈,所以通過調用FilterChain.doFilter可以將請求繼續傳遞下去;如果想攔截這個請求,可以不調用FilterChain.doFilter,則該請求將直接返回,這就是責任鏈設計模式。

               Filter類的傳遞還是FilterChain對象,這個對象保存了到最終Servlet對象的所有Filter對象,這些對象都保存在ApplicationFilterChain對象的filters數組中。在FilterChain鏈上每執行一個Filter對象,數組的當前計數都會加1,直到計數等於數組的長度,當FilterChain上多有的Filter對象都執行完後,就會執行最終的Servlet,所以在ApplicationFilterChain對象中會持有Servlet對象的引用;在Tomcat的Wrapper容器中的Filter執行時序圖如下:

            

             那麼一次請求中URL又是如何解析並匹配到最終的Filter的????

             在web.xml中<servlet-mapping>和<filter-mapping>都有<url-pattern>配置項:Filter的url-pattern匹配是在創建

ApplicationFilterChain對象時進行的,它會把所有定義的Filter的url-pattern與當前URL匹配,如果匹配成功就將這個Filter

保存到ApplicationFilterChain的filters數組中,但是在匹配之前首先要利用StandardContext中的validateURLPattern方法

檢查url-pattern配置是否符合規則,如果檢查不成功,Context容器啓動會失敗:    

private boolean validateURLPattern(String urlPattern) {

    if (urlPattern == null)
        return false;
    if (urlPattern.indexOf('\n') >= 0 || urlPattern.indexOf('\r') >= 0) {
        return false;
    }
    if (urlPattern.equals("")) {
        return true;
    }
    if (urlPattern.startsWith("*.")) {
        if (urlPattern.indexOf('/') < 0) {
            checkUnusualURLPattern(urlPattern);
            return true;
        } else
            return false;
    }
    if ( (urlPattern.startsWith("/")) &&
            (urlPattern.indexOf("*.") < 0)) {
        checkUnusualURLPattern(urlPattern);
        return true;
    } else
        return false;
}

  而匹配規則分爲:

         精確匹配:如 /foo.html,只會匹配foo.html這個文件URL

         路徑匹配:如 /foo/*,匹配以foo爲前綴的URL

         後綴匹配:如 *.html,會匹配所有以.html爲後綴的URL

  Servlet及Filter匹配順序均爲: 精確匹配——最長路徑匹配——後綴匹配、

  區別:Servlet中一次請求只會成功匹配到一個Servlet;

             Filter中只要通過ApplicationFilterFactory類中的matchFiltersURL方法匹配成功,在請求鏈上都會被調用:

private static boolean matchFiltersURL(String testPath, String requestPath) {

    if (testPath == null)
        return false;

    // Case 1 - Exact Match
    if (testPath.equals(requestPath))
        return true;

    // Case 2 - Path Match ("/.../*")
    if (testPath.equals("/*"))
        return true;
    if (testPath.endsWith("/*")) {
        if (testPath.regionMatches(0, requestPath, 0, testPath.length() - 2)) {
            if (requestPath.length() == (testPath.length() - 2)) {
                return true;
            } else if ('/' == requestPath.charAt(testPath.length() - 2)) {
                return true;
            }
        }
        return false;
    }

    // Case 3 - Extension Match
    if (testPath.startsWith("*.")) {
        int slash = requestPath.lastIndexOf('/');
        int period = requestPath.lastIndexOf('.');
        if ((slash >= 0) && (period > slash) && (period != requestPath.length() - 1)
        		         && ((requestPath.length() - period) == (testPath.length() - 1))) {
            return (testPath.regionMatches(2, requestPath, period + 1, testPath.length() - 2));
        }
    }

    // Case 4 - "Default" Match
    return false; // NOTE - Not relevant for selecting filters
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章