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
}

猜你喜欢

转载自blog.csdn.net/qq_39028580/article/details/81027844