程序员社区

How Tomcat Works读书笔记


title: How Tomcat Works(基于Tomcat4)
date: 2019/08/12 15:51


一、一个简单的web服务器

基于Java的Http服务器需要两个重要的类java.net.ServerSocketjava.net.Socket

1.1 HTTP

Http使用可靠的TCP连接,默认使用TCP/80端口

1.2 Socket类

套接字是网络连接的端点。套接字使程序可以从网络中读取数据,可以向网络中写入数据。不同计算机上的两个应用程序可以通过连接发送或接受字节流

Socket类表示客户端套接字(想要连接到远程服务器应用程序时需要创建的套接字)、ServerSocket类表示服务器套接字(等待客户端的连接请求的套接字),当服务器套接字收到请求后,他会创建一个Socket实例来处理与客户端的通信。

二、一个简单的Servlet容器

2.1 Servlet接口

2.2 应用程序1

一个功能健全的servlet容器对Http请求需要做以下几件事:

  1. 第一次调用某个servlet时,要载入该servlet类,之后调用它的init()方法。
// 连接器调用pipline,pipline调用基础阀,基础阀执行wrapper.allocate();
Servlet servlet = this.loadServlet();
servlet.init();
  1. 针对每个request请求,创建一个javax.servlet.ServletRequestjavax.servlet.ServletResponse对象。(连接器干的)
  2. 调用servlet的service方法(基础阀干的,通过过滤器链传递到servlet)
  3. 调用destory方法并卸载此类

连接器最终生成的是HttpRequestImpl的对象,由于HttpRequestImpl类中有部分公有方法不想让servlet程序员使用,有两种办法能够解决:

  1. 使用访问修饰符
  2. 使用Faced类

三、连接器

连接器主要负责创建HttpServletRequest和HttpServletResponse对象,并将他们作为参数传给servlet的service方法。

3.1 StringManager类

该类用来处理Catalina中错误消息的国际化操作。

Tomcat将错误消息存储到properties文件中,如果所有错误信息储存再一个文件中,会很难维护,所以Tomcat将properties文件划分到不同的包中,每个properties文件使用一个StringManager对象来处理。

private static Hashtable managers = new Hashtable();

public synchronized static StringManager getManager(String packageName) {
    StringManager mgr = (StringManager)managers.get(packageName);
    if (mgr == null) {
        mgr = new StringManager(packageName);
        managers.put(packageName, mgr);
    }
    return mgr;
}
How Tomcat Works读书笔记插图
properties文件格式

StringManager会根据该应用程序的服务器的语言环境选择使用哪个文件。

3.2 应用程序

连接器需要在请求来的时候从socket中一些值设置给Request对象,这些值包括:URI、查询字符串、参数(body中)、cookie、请求头等信息;由于查询字符串和参数可能巨大,所以一般再连接器中不解析它们,而是在servlet真正用到的时候进行解析的

连接器解析Http请求主要分为以下5块:

3.2.1 启动程序

public final class Bootstrap {
    public static void main(String[] args) {
        // HttpConnector负责接受连接,并调用HttpProcessor.process(Socket socket)方法
        HttpConnector connector = new HttpConnector();
        connector.start();
    }
}

3.2.2 HttpConnector类

public class HttpConnector implements Runnable {

    boolean stopped;
    private String scheme = "http";

    public String getScheme() {
        return scheme;
    }

    public void run() {
        ServerSocket serverSocket = null;
        int port = 8080;
        serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
        while (!stopped) {
            // 等待http请求
            Socket socket = serverSocket.accept();
            // 调用连接器的process方法,对请求进行解析
            HttpProcessor processor = new HttpProcessor(this);
            processor.process(socket);
        }
    }

    public void start() {
        Thread thread = new Thread(this);
        thread.start();
    }
}

3.2.3 HttpProcessor类

public void process(Socket socket) {
    SocketInputStream input = null;
    OutputStream output = null;
    try {
        // 获取socket中的输入流、输出流
        input = new SocketInputStream(socket.getInputStream(), 2048);
        output = socket.getOutputStream();

        // 创建Request和Response对象
        request = new HttpRequest(input);
        
        response = new HttpResponse(output);
        response.setRequest(request);
        response.setHeader("Server", "Pyrmont Servlet Container");

        // 解析请求
        parseRequest(input, output);
        parseHeaders(input);

        // 检查这是对servlet还是静态资源的请求,调用不同的处理程序
        // 对servlet的请求以/servlet/开头
        if (request.getRequestURI().startsWith("/servlet/")) {
            ServletProcessor processor = new ServletProcessor();
            processor.process(request, response);
        } else {
            StaticResourceProcessor processor = new StaticResourceProcessor();
            processor.process(request, response);
        }

        // Close the socket
        socket.close();
        // no shutdown for this application
    } catch (Exception e) {
        e.printStackTrace();
    }
}

3.2.3.1 创建HttpRequest对象

主要包含五部分:

  1. 读取套接字的输入流
  2. 解析请求行

    How Tomcat Works读书笔记插图1
    第一行就是请求行
  3. 解析请求头
  4. 解析cookie
  5. 读取参数

前面说过读取参数会延迟到servlet第一次获取参数时进行解析,所以在request对象中有一个parseParameters()方法,会在第一次获取参数时进行解析。

3.2.3.2 创建HttpResponse对象

获取输出流,转换成PrintWirter对象

四、Tomcat的默认连接器

功能:负责创建Request、Response对象,然后调用Container接口的invoke方法。

由于HttpProcessor将Request对象作为全局变量,所以多线程访问的时候有线程安全问题,所以HttpConnector内部维护一个对象池。

4.2 Connector接口

Tomcat的连接器必须实现该接口。

public interface Connector {

    // 返回与之关联的容器
    public Container getContainer();

    // 将连接器与某个servlet相关联;当request、response对象创建好了之后调用它的invoke方法
    public void setContainer(Container container);

    // 为引入的Http请求创建request对象
    public Request createRequest();

    // 创建response对象
    public Response createResponse();
    
    // 初始化连接器
    public void initialize() throws LifecycleException;

    ...
}

注:Tomcat8中没有Connector接口,改为org.apache.catalina.connector.Connector类,使用的是Coyote连接器。

4.3 HttpConnector类(连接器)

public final class HttpConnector
        implements Connector, Lifecycle, Runnable {

4.3.1 创建服务器套接字

HttpConnector的initialize()方法会调用open()方法(私有方法)从服务器套接字工厂得到一个服务器套接字实例。

4.3.2 维护HttpProcessor实例(具体干活的人)

上面说了,由于一个HttpProcessor实例只能串行的干活,才能保证线程安全,所以默认连接器维护了一个处理器栈:

private Stack<HttpProcessor> processors = new Stack<>();

内部的数量由两个变量决定:

private int maxProcessors = 20;

protected int minProcessors = 5;

在HttpConnector启动(Lifecycle的start方法)的时候会对内部对象进行填充:

public void start() throws LifecycleException {

    // Validate and update our current state
    if (started)
        throw new LifecycleException
                (sm.getString("httpConnector.alreadyStarted"));
    threadName = "HttpConnector[" + port + "]";
    lifecycle.fireLifecycleEvent(START_EVENT, null);
    started = true;

    // Start our background thread
    threadStart();

    // 创建指定的**最小**处理器数
    while (curProcessors < minProcessors) {
        if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
            break;
        
        // 创建对象
        HttpProcessor processor = newProcessor();
        // 压入栈
        recycle(processor);
    }

}

4.3.3 提供Http请求服务

HttpConnector实现了Runable接口,上面的threadStart()方法将其启动:

public void run() {
    // Loop until we receive a shutdown command
    while (!stopped) {
        Socket socket = null;
        try {
            // 等待http请求
            socket = serverSocket.accept();
            if (connectionTimeout > 0)
                socket.setSoTimeout(connectionTimeout);
            socket.setTcpNoDelay(tcpNoDelay);
        } catch (IOException e) {
            try {
                // If reopening fails, exit
                synchronized (threadSync) {
                    if (started && !stopped)
                        log("accept error: ", e);
                    if (!stopped) {
                        serverSocket.close();
                        serverSocket = open();
                    }
            } ...
            continue;
        }

        // 获取一个HttpProcessor对象(可能是从栈中取出,可能是创建的,如果max <= current返回null)
        HttpProcessor processor = createProcessor();
        if (processor == null) {
            try {
                log(sm.getString("httpConnector.noProcessor"));
                socket.close();
            } catch (IOException e) {
                ;
            }
            continue;
        }
        // 传入socket对象,唤醒processor的run方法
        processor.assign(socket);
    }

    synchronized (threadSync) {
        threadSync.notifyAll();
    }

}

4.3.4 HttpProcessor类

public void run() {
    // Process requests until we receive a shutdown signal
    while (!stopped) {

        // 获取套接字对象,processor.assign(socket)传入的
        Socket socket = await();
        if (socket == null)
            continue;

        // 处理
        try {
            process(socket);
        } catch (Throwable t) {
            log("process.invoke", t);
        }

        // 将当前实例压会栈中
        connector.recycle(this);

    }

    // 告诉 threadstop()我们已经成功关闭了自己
    synchronized (threadSync) {
        threadSync.notifyAll();
    }
}
private void process(Socket socket) {
    // 表示处理过程是否出错
    boolean ok = true;
    boolean finishResponse = true;
    SocketInputStream input = null;
    OutputStream output = null;

    // Construct and initialize the objects we will need
    try {
        input = new SocketInputStream(socket.getInputStream(),
                                        connector.getBufferSize());
    } catch (Exception e) {
        log("process.create", e);
        ok = false;
    }

    keepAlive = true;

    // 不断的读取输入流,知道HttpProcessor实例终止
    while (!stopped && ok && keepAlive) {

        finishResponse = true;

        try {
            // request、response对象的初始化工作
            request.setStream(input);
            request.setResponse(response);
            output = socket.getOutputStream();
            response.setStream(output);
            response.setRequest(request);
            ((HttpServletResponse) response.getResponse()).setHeader
                ("Server", SERVER_INFO);
        } catch (Exception e) {
            log("process.create", e);
            ok = false;
        }


        // Parse the incoming request
        try {
            if (ok) {

                // 解析连接,取出internet地址赋值给Request对象
                parseConnection(socket);
                // 解析各种东西,参见第三章
                parseRequest(input, output);
                if (!request.getRequest().getProtocol()
                    .startsWith("HTTP/0"))
                    // 解析请求头
                    parseHeaders(input);
                if (http11) {
                    // Sending a request acknowledge back to the client if
                    // requested.
                    ackRequest(output);
                    // If the protocol is HTTP/1.1, chunking is allowed.
                    if (connector.isChunkingAllowed())
                        response.setAllowChunking(true);
                }

            }
        } catch (EOFException e) {
            // It's very likely to be a socket disconnect on either the
            // client or the server
            ok = false;
            finishResponse = false;
        } catch (ServletException e) {
            ok = false;
            try {
                ((HttpServletResponse) response.getResponse())
                    .sendError(HttpServletResponse.SC_BAD_REQUEST);
            } catch (Exception f) {
                ;
            }
        } catch (InterruptedIOException e) {
            if (debug > 1) {
                try {
                    log("process.parse", e);
                    ((HttpServletResponse) response.getResponse())
                        .sendError(HttpServletResponse.SC_BAD_REQUEST);
                } catch (Exception f) {
                    ;
                }
            }
            ok = false;
        } catch (Exception e) {
            try {
                log("process.parse", e);
                ((HttpServletResponse) response.getResponse()).sendError
                    (HttpServletResponse.SC_BAD_REQUEST);
            } catch (Exception f) {
                ;
            }
            ok = false;
        }

        // Ask our Container to process this request
        try {
            ((HttpServletResponse) response).setHeader
                ("Date", FastHttpDateFormat.getCurrentDate());
            if (ok) {
                // *** 获取容器执行invoke方法,最终到达servlet.service() ***
                connector.getContainer().invoke(request, response);
            }
        } catch (ServletException e) {
            log("process.invoke", e);
            try {
                ((HttpServletResponse) response.getResponse()).sendError
                    (HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            } catch (Exception f) {
                ;
            }
            ok = false;
        } catch (InterruptedIOException e) {
            ok = false;
        } catch (Throwable e) {
            log("process.invoke", e);
            try {
                ((HttpServletResponse) response.getResponse()).sendError
                    (HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            } catch (Exception f) {
                ;
            }
            ok = false;
        }

        // 直到finishResponse为true表示处理结束(包括循环读取请求头,调用service方法等),将结果发送给客户端。
        if (finishResponse) {
            try {
                response.finishResponse();
            } catch (IOException e) {
                ok = false;
            } catch (Throwable e) {
                log("process.invoke", e);
                ok = false;
            }
            try {
                request.finishRequest();
            } catch (IOException e) {
                ok = false;
            } catch (Throwable e) {
                log("process.invoke", e);
                ok = false;
            }
            try {
                if (output != null)
                    output.flush();
            } catch (IOException e) {
                ok = false;
            }
        }

        // We have to check if the connection closure has been requested
        // by the application or the response stream (in case of HTTP/1.0
        // and keep-alive).
        if ( "close".equals(response.getHeader("Connection")) ) {
            keepAlive = false;
        }

        // End of request processing
        status = Constants.PROCESSOR_IDLE;

        // Recycling the request and the response objects
        request.recycle();
        response.recycle();

    }

    try {
        shutdownInput(input);
        socket.close();
    } catch (IOException e) {
        ;
    } catch (Throwable e) {
        log("process.invoke", e);
    }
    socket = null;

}

五、Servlet容器

servlet容器是用来处理请求servlet资源,并为客户端填充response对象的模块。

Tomcat中有下面4中容器:

  • Engine:解析请求,分配到适当的虚拟主机
  • Host:虚拟主机;运行多个Web应用(一个Context代表一个Web应用),并负责安装、展开、启动和结束每个Web应用。
  • Context:代表在特定虚拟主机上运行的一个Web应用
  • Wrapper:表示一个独立的servlet

它们所有的实现类都继承ContainerBase抽象类。

https://www.cnblogs.com/kismetv/p/7228274.html#title3-1

5.1 Container接口

public interface Container {

    // 添加子容器;Host容器下只能添加Context容器。。。
    void addChild(Container child);

    // 移除子容器
    void removeChild(Container child);

    // 根据名称查找子容器
    public Container findChild(String name);

    // 查找子容器集合
    public Container[] findChildren();

    // 其它组件的get/set方法,包括:载入器(Loader)、记录器(Logger)、Session管理器(Manager)、领域(Realm)、资源(Resource)

    容器是Tomcat的核心,所以才将所有组件都与容器连接起来,而且通过Lifecyle接口,使我们可以只启动容器组件就可以了(他帮我们启动其它组件)
}

注:这是组合模式的一种使用

5.2 管道任务

当连接器调用容器的invoke()方法后,容器会调用pipline的invoke()方法(pipline是管理阀的容器,类似FilterChain;阀表示具体的执行任务),pipline会调用ValveContext的invokeNext方法,当所有阀全都调用完成之后就会调用基础阀的invoke方法。

看下StandardWrapperValve的invoke方法的部分代码:

StandardWrapper wrapper = (StandardWrapper) getContainer();

ServletRequest sreq = request.getRequest();
ServletResponse sres = response.getResponse();

// 如果是第一次的话,这个方法里面调用了servlet的init方法
Servlet servlet = wrapper.allocate();;

// 构造Filter责任链
ApplicationFilterChain filterChain = createFilterChain(request, servlet);

// 执行责任链和servlet的service方法
filterChain.doFilter(sreq, sres);

5.3 Wrapper接口

Wrapper的实现类主要负责管理其基础的servlet类的生命周期

// 载入并加载servlet,在它的子类StandardWrapper中直接调用的loadServlet方法
void load() throws ServletException;

// 该方法会返回已加载的servlet类,还要考虑它是否实现了SingleThreadModel
Servlet allocate() throws ServletException;

六、生命周期

Catalina包含很多组件,当Catalina启动时,这些组件也会一起启动,关闭时也会一起关闭。

通过实现Lifecyle接口就可以达到统一启动/关闭这些组件的效果。

Lifecycle可以触发下面6个事件,实现LifecycleListener的类可以通过addLifecycleListener()注册到相应的生命周期对象上。

public interface Lifecycle {

    // 添加事件监听器
    void addLifecycleListener(LifecycleListener listener);

    /**
     * The LifecycleEvent type for the "component start" event.
     */
    public static final String START_EVENT = "start";


    /**
     * The LifecycleEvent type for the "component before start" event.
     */
    public static final String BEFORE_START_EVENT = "before_start";


    /**
     * The LifecycleEvent type for the "component after start" event.
     */
    public static final String AFTER_START_EVENT = "after_start";


    /**
     * The LifecycleEvent type for the "component stop" event.
     */
    public static final String STOP_EVENT = "stop";


    /**
     * The LifecycleEvent type for the "component before stop" event.
     */
    public static final String BEFORE_STOP_EVENT = "before_stop";


    /**
     * The LifecycleEvent type for the "component after stop" event.
     */
    public static final String AFTER_STOP_EVENT = "after_stop";

6.1 Lifecycle接口

void addLifecycleListener(LifecycleListener listener);

public LifecycleListener[] findLifecycleListeners();

public void removeLifecycleListener(LifecycleListener listener);

// 启动操作
public void start() throws LifecycleException;

// 关闭操作
public void stop() throws LifecycleException;

其实也是组合模式的使用

6.2 LifecycleEvent

当事件触发的时候传给listener的对象

6.3 LifecycleListener

事件触发是的处理程序要放在lifecycleEvent方法中。

public interface LifecycleListener {

    public void lifecycleEvent(LifecycleEvent event);
}

6.4 LifecycleSupport(工具类)

public final class LifecycleSupport {

    // 构造需要一个生命周期对象
    public LifecycleSupport(Lifecycle lifecycle) {

        super();
        this.lifecycle = lifecycle;

    }

    private Lifecycle lifecycle = null;

    // 监听器集合
    private LifecycleListener listeners[] = new LifecycleListener[0];

    // 当调用生命周期的addLifecycleListener方法时,大多数代码的实现直接调用的LifecycleSupport的这个方法
    public void addLifecycleListener(LifecycleListener listener) {

      synchronized (listeners) {
          LifecycleListener results[] =
            new LifecycleListener[listeners.length + 1];
          for (int i = 0; i < listeners.length; i++)
              results[i] = listeners[i];
          results[listeners.length] = listener;
          listeners = results;
      }

    }


    public LifecycleListener[] findLifecycleListeners() {

        return listeners;

    }

    // 触发事件,type是上面Lifecyle中定义的那6个常量,data是要传给listener的数据
    public void fireLifecycleEvent(String type, Object data) {

        LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
        LifecycleListener interested[] = null;
        synchronized (listeners) {
            interested = (LifecycleListener[]) listeners.clone();
        }
        for (int i = 0; i < interested.length; i++)
            interested[i].lifecycleEvent(event);

    }

    public void removeLifecycleListener(LifecycleListener listener) {

        synchronized (listeners) {
            int n = -1;
            for (int i = 0; i < listeners.length; i++) {
                if (listeners[i] == listener) {
                    n = i;
                    break;
                }
            }
            if (n < 0)
                return;
            LifecycleListener results[] =
              new LifecycleListener[listeners.length - 1];
            int j = 0;
            for (int i = 0; i < listeners.length; i++) {
                if (i != n)
                    results[j++] = listeners[i];
            }
            listeners = results;
        }

    }
}

6.5 应用程序

能体现Lifecyle的就是启动类中的代码了:

public final class Bootstrap {
    public static void main(String[] args) {
        Connector connector = new HttpConnector();
        Wrapper wrapper1 = new SimpleWrapper();
        wrapper1.setName("Primitive");
        wrapper1.setServletClass("PrimitiveServlet");
        Wrapper wrapper2 = new SimpleWrapper();
        wrapper2.setName("Modern");
        wrapper2.setServletClass("ModernServlet");

        Context context = new SimpleContext();
        context.addChild(wrapper1);
        context.addChild(wrapper2);

        Mapper mapper = new SimpleContextMapper();
        mapper.setProtocol("http");
        // 将监听器绑定到context容器中
        LifecycleListener listener = new SimpleContextLifecycleListener();
        ((Lifecycle) context).addLifecycleListener(listener);
        context.addMapper(mapper);
        Loader loader = new SimpleLoader();
        context.setLoader(loader);
        // context.addServletMapping(pattern, name);
        context.addServletMapping("/Primitive", "Primitive");
        context.addServletMapping("/Modern", "Modern");
        connector.setContainer(context);
        try {
            // 初始化连接器(创建ServerSocket)
            connector.initialize();
            // 启动连接器(创建HttpProcesser集合)
            ((Lifecycle) connector).start();
            // 启动容器(它又会调用子容器的start()方法)
            ((Lifecycle) context).start();

            // make the application wait until we press a key.
            System.in.read();
            ((Lifecycle) context).stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

6.6 总结

通过生命周期就可以采用伪递归的方式让组件做一些开始、收尾工作。

LifecyleListener就是在启动/关闭过程中埋下的钩子。

七、日志记录器

日志记录器是用来记录消息的组件。

https://wiki.jikexueyuan.com/project/tomcat/logging.html

7.1 Logger接口

日志记录器都必须实现这个接口。

public interface Logger {

    // 定义的日志的等级
    public static final int FATAL = Integer.MIN_VALUE;

    public static final int ERROR = 1;

    public static final int WARNING = 2;

    public static final int INFORMATION = 3;

    public static final int DEBUG = 4;

7.2 Tomcat的日志记录器

Tomcat提供了3种日志记录器:FileLogger、SystemOutLogger、SystemErrLogger,它们3个都继承了LoggerBase抽象类。

7.2.1 LoggerBase类

实现了所有Logger接口的方法,除了下面这个:

public abstract void log(String msg);

LoggerBase中有几个重载的log方法,最终都会调用这个方法。

日志等级是由protected int verbosity = ERROR;变量定义的,默认为Error。

7.2.2 SystemOutLogger类

public class SystemOutLogger extends LoggerBase {
    public void log(String msg) {
        System.out.println(msg);
    }
}

7.2.3 SystemErrLogger类

public class SystemErrLogger extends LoggerBase {
    public void log(String msg) {
        System.err.println(msg);
    }
}

7.2.4 FileLogger

当我知道Tomcat8中没有Logger接口,就不想写了

八、载入器

由于整个Tomcat共用一个JVM,如果采用系统类加载器的话所有web应用程序(Context)之间可以互相使用对方的类,违背了下面的规范:

为部署在单个Tomcat实例中的每个Web应用程序创建一个类加载器。/WEB-INF/classesWeb应用程序目录中的所有解压缩的类和资源,以及Web应用程序/WEB-INF/lib目录下的JAR文件中的类和资源,都对此Web应用程序可见,但对其他应用程序不可见。 —— Tomcat7官方文档

还有另外一个原因是为了提供自动重载功能,当WEB-INF/classes和WEB-INF/lib目录发生变化时,Web应用要重新载入这些类。

Tomcat要自定义类加载器原因主要是以下3条:

  1. 在载入类中指定某些规则
  2. 缓存已经载入的类
  3. 实现类的预载入

8.1 类加载器

JVM使用了3种类加载器来载入需要的类,分别是引导类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)、系统类加载器(System ClassLoader)。

3种类加载器是父子继承关系,引导类加载器在最上层,系统类加载器在最下层。

引导类加载器(本地代码编写):引导启动Java虚拟机。用于加载JVM需要的类,以及所有Java核心类(例如java.lang,java.io包下的类);它会在rt.jar、i18n.jar中搜索要载入的类。

扩展类加载器:负责载入标准扩展目录中的类。标准扩展目录是/jdk/jre/lib/ext

系统类加载器:它会搜索在环境变量CLASSPATH中指明的路径和文件。

类载入过程(双亲委托机制):要载入一个类的时候,会先交给系统类加载器,系统类加载器交给扩展类加载器,扩展类加载器又会交给引导类加载器,如果引导类加载器无法加载就交给扩展类加载器,扩展类加载器无法加载就交给系统类加载器,系统类加载器无法加载就抛出ClassNotFoundException异常。

为啥要执行这样的循环过程呢?

如果我们自定义一个java.lang.Object类,假设他能被载入,由于JVM是信任java.lang.Object类的,安全管理器就不会监视这个类的活动,这是非常危险的。

采用双亲委托机制,就会将java.lang.Object类的加载交给引导类加载器,而引导类加载器会在rt.jar、i18n.jar中去寻找这个类进行加载,我们自定义的类并不会被加载。

package java.lang;

public class Object {
    public static void main(String[] args) {
        System.out.println(123);
    }
}

// 证明自定义Object类并没有加载进去
错误: 在类 java.lang.Object 中找不到 main 方法, 请将 main 方法定义为:
   public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application

8.2 Loader接口

仓库:表示类载入器会在哪里搜索要载入的类

资源:一个类载入器中的DirContext对象(代表着一个web应用的目录吗??走流程的时候看一下)

public interface Loader {
    
    // 返回类加载器
    ClassLoader getClassLoader();

    Context getContext();

    // 这个方法会在容器启动(start方法)的时候调用,setContext(this)
    void setContext(Context var1);

    boolean getDelegate();

    // 是否委托给父类载入器
    void setDelegate(boolean var1);

    boolean getReloadable();

    void setReloadable(boolean var1);

    void addPropertyChangeListener(PropertyChangeListener var1);

    boolean modified();

    void removePropertyChangeListener(PropertyChangeListener var1);
}

Loader的实现类

public class WebappLoader
    implements Lifecycle, Loader, PropertyChangeListener, Runnable {

    // ClassLoader的子类
    private WebappClassLoader classLoader = null;

8.3 Reloader接口

public interface Reloader {


    /**
     * 设置仓库路径(在哪里搜索要载入的类)
     */
    public void addRepository(String repository);


    /**
     * Return a String array of the current repositories for this class
     * loader.  If there are no repositories, a zero-length array is
     * returned.
     */
    public String[] findRepositories();


    /**
     * 是否修改了一个或多个类或资源
     */
    public boolean modified();
}

8.4 WebappLoader

public class WebappLoader
    implements Lifecycle, Loader, PropertyChangeListener, Runnable {

    // ClassLoader的子类
    private WebappClassLoader classLoader = null;

它负责载入Web应用程序中所使用到的类,支持自动重载功能(通过Runable的run方法不断轮询其类加载器的modified()实现)

当调用该类的start()方法时会做以下几项工作:

8.4.1 创建类载入器

private WebappClassLoader createClassLoader()
        throws Exception {
        
        // loaderClass可以通过setLoaderClass方法改变
        Class clazz = Class.forName(loaderClass);

8.4.2 设置仓库

// 伪代码
private void setRepositories() {

    String classesPath = "/WEB-INF/classes";
    // addRepository的重载方法,第二个参数传入的是 new File(应用/WEB-INF/classes)对象
    classLoader.addRepository(classesPath + "/", classRepository);

    String libPath = "/WEB-INF/lib";
    classLoader.setJarPath(libPath);

8.4.3 设置类路径

为jasper JSP编译器设置一个字符串形式的属性来表明类路径信息。

8.4.4 设置访问权限

如果运行时使用了安全管理器,setPermissions()方法会为泪在如期设置访问目录的权限,例如:设置只能访问WEB-INF/classes和WEB-INF/lib目录。

8.4.5 开启新线程执行类的重新载入

public void run() {

    // Loop until the termination semaphore is set
    while (!threadDone) {

        // Wait for our check interval
        threadSleep();

        if (!started)
            break;

        try {
            // Perform our modification check
            if (!classLoader.modified())
                continue;
        } catch (Exception e) {
            log(sm.getString("webappLoader.failModifiedCheck"), e);
            continue;
        }

        // 重新加载;最终调用的是context.reload()方法
        notifyContext();
        // 为啥这里要退出呢??会不会在上面reload的过程中重新启动了一个WebappLoader呢?
        break;

    }
}

Tomcat5看书 P119

8.5 WebappClassLoader类

public class WebappClassLoader
    extends URLClassLoader
    implements Reloader, Lifecycle {

继承自URLClassLoader,实现了Reloader接口,支持重载。

它会缓存之前已经加载过的类来提高性能,它还会缓存加载失败的类的名字,当再次请求的时候就会直接抛出ClassNotFoundException异常,而不会在指定的仓库或jar包中搜索需要载入的类。

为了安全性,不让这个加载器加载某些类,类的名称存储在一个字符串数组变量triggerd中:

private static final String[] triggers = {
    "javax.servlet.Servlet"                     // Servlet API
};

此外,某些特殊包下的类也是不允许载入的:

private static final String[] packageTriggers = {
    "javax",                                     // Java extensions
    "org.xml.sax",                               // SAX 1 & 2
    "org.w3c.dom",                               // DOM 1 & 2
    "org.apache.xerces",                         // Xerces 1 & 2
    "org.apache.xalan"                           // Xalan
};

8.5.1 类缓存

java.lang.ClassLoader类会维护一个Vector<Class<?>>对象,保存已经载入的类,防止这些类在不使用的时候被垃圾回收了。

每个由WebappClassLoader载入的类(从/WEB-INF/classes,/WEB-INF/lib等目录下载入的类),都视为“资源”,“资源”使用ResourceEntry进行表示:

public class ResourceEntry {

    /**
     * The "last modified" time of the origin file at the time this class
     * was loaded, in milliseconds since the epoch.
     */
    public long lastModified = -1;


    /**
     * Binary content of the resource.
     */
    public byte[] binaryContent = null;


    /**
     * Loaded class.
     */
    public Class loadedClass = null;


    /**
     * URL source from where the object was loaded.
     */
    public URL source = null;


    /**
     * URL of the codebase from where the object was loaded.
     */
    public URL codeBase = null;


    /**
     * Manifest (if the resource was loaded from a JAR).
     */
    public Manifest manifest = null;


    /**
     * Certificates (if the resource was loaded from a JAR).
     */
    public Certificate[] certificates = null;

}

所有已经缓存的类会存储在一个HashMap中:

protected HashMap<String, ResourceEntry> resourceEntries = new HashMap<>();

8.5.2 载入类

  1. 先检查本地缓存
  2. 本地缓存没有,则检查上一层缓存(调用ClassLoader.findLoadedClass())
  3. 如果两个缓存中没有,则使用系统类加载器加载,防止Web应用程序的类覆盖J2EE的类(原因前面讲了)
  4. 如果使用了安全管理器(与8.4.4对应),则检查是否允许载入该类,如果不允许则抛出ClassNotFoundException
  5. 如果设置了Delegate属性或待载入的类属于包触发器(packageTriggers)的包名,则调用父类加载器来载入相关类,如果父类加载器为null,则使用系统类加载器。
  6. 从当前仓库载入相关类
  7. 如果当前仓库没有需要的类,且标志位delegate关闭,则使用父类加载器;如果父类加载器为null,则使用系统类加载器
  8. 如果还是没找到需要的类,则抛出ClassNotFoundException异常。

九、Session管理

Catalina通过一个称为Session管理器的组件来管理建立的Session对象,该组件必须实现Manager接口。Session管理器必须与一个Context容器相关联。

我们可以通过request.getSession()来获取Session:

// HttpRequestBase.java
public HttpSession getSession() {
    return (getSession(true));
}

public HttpSession getSession(boolean create) {
    if( System.getSecurityManager() != null ) {
        PrivilegedGetSession dp = new PrivilegedGetSession(create);
        return (HttpSession)AccessController.doPrivileged(dp);
    }
    return doGetSession(create);
}

private HttpSession doGetSession(boolean create) {
    // There cannot be a session if no context has been assigned yet
    if (context == null)
        return (null);

    // Return the current session if it exists and is valid
    if ((session != null) && !session.isValid())
        session = null;
    if (session != null)
        return (session.getSession());


    // Return the requested session if it exists and is valid
    Manager manager = null;
    if (context != null)
        // 从context中获取session管理器
        manager = context.getManager();

    if (manager == null)
        return (null);      // Sessions are not supported

    if (requestedSessionId != null) {
        try {
            // 如果sessionId不为空,那么从管理器中寻找他
            session = manager.findSession(requestedSessionId);
        } catch (IOException e) {
            session = null;
        }
        if ((session != null) && !session.isValid())
            session = null;
        if (session != null) {
            return (session.getSession());
        }
    }

    // Create a new session if requested and the response is not committed
    if (!create)
        return (null);
    if ((context != null) && (response != null) &&
        context.getCookies() &&
        response.getResponse().isCommitted()) {
        throw new IllegalStateException
            (sm.getString("httpRequestBase.createCommitted"));
        }

        // 通过管理器创建一个session
        session = manager.createSession();
        if (session != null)
            return (session.getSession());
        else
            return (null);

    }

9.1 Session对象

9.1.1 Session接口

public interface Session {

    // 获取该session的标识符
    public String getId();

    public void setId(String id);

    // 获取session管理器
    public Manager getManager();


    // 设置session管理器
    public void setManager(Manager manager);

    // 返回客户端上次发送与此会话关联的请求的时间(可以通过该返回值判断session对象的有效性)
    public long getLastAccessedTime();

    // 设置session的有效性
    public void setValid(boolean isValid);

    // 当访问一个session实例的时候,session管理器会调用该方法修改session对象的最终访问时间
    public void access();

    // 将session设置为过期
    public void expire();

    // 获取一个被外观类包装的HttpSession类
    public HttpSession getSession();

}

9.1.2 StandardSession类

class StandardSession
        implements HttpSession, Session, Serializable {

    // 构造函数接收的是session管理器对象
    public StandardSession(Manager manager) {
        super();
        this.manager = manager;
        if (manager instanceof ManagerBase)
            this.debug = ((ManagerBase) manager).getDebug();
    }

    public HttpSession getSession() {
        if (facade == null)
            facade = new StandardSessionFacade(this);
        return facade;
    }

    // 设置session过期
    public void expire() {
        expire(true);
    }

    public void expire(boolean notify) {

        // Mark this session as "being expired" if needed
        if (expiring)
            return;
        // 将过期标识设置为true,有效标识设置为false
        expiring = true;
        setValid(false);

        // 从session管理器移除当前session对象
        if (manager != null)
            manager.remove(this);

        // 解绑与当前session相关联的任何对象
        String keys[] = keys(); // 获取session域的所有key值
        for (int i = 0; i < keys.length; i++)
            removeAttribute(keys[i], notify);

        // 触发session过期事件
        if (notify) {
            fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
        }

        // Notify interested application event listeners
        // FIXME - Assumes we call listeners in reverse order
        Context context = (Context) manager.getContainer();
        Object listeners[] = context.getApplicationListeners();
        if (notify && (listeners != null)) {
            HttpSessionEvent event =
                    new HttpSessionEvent(getSession());
            for (int i = 0; i < listeners.length; i++) {
                int j = (listeners.length - 1) - i;
                if (!(listeners[j] instanceof HttpSessionListener))
                    continue;
                HttpSessionListener listener =
                        (HttpSessionListener) listeners[j];
                try {
                    // 触发容器事件
                    fireContainerEvent(context,
                            "beforeSessionDestroyed",
                            listener);
                    listener.sessionDestroyed(event);
                    fireContainerEvent(context,
                            "afterSessionDestroyed",
                            listener);
                } catch (Throwable t) {
                    try {
                        fireContainerEvent(context,
                                "afterSessionDestroyed",
                                listener);
                    } catch (Exception e) {
                        ;
                    }
                    // FIXME - should we do anything besides log these?
                    log(sm.getString("standardSession.sessionEvent"), t);
                }
            }
        }

        // We have completed expire of this session
        expiring = false;
        if ((manager != null) && (manager instanceof ManagerBase)) {
            recycle();
        }

    }

9.2 Manager

Session管理器负责管理Session对象(Session对象的创建、摧毁)。

ManagerBase抽象类对Manager接口进行了部分实现,它有2个直接子类:

  • StandardManager:将session对象储存在内存中,当Catalina关闭时,他会将Session对象存储到一个文件中(Lifecycle的stop方法中),重启后,又会将这些Session对象重新载入内存中。

  • PersistentManagerBase:它也是一个抽象类,用于将session对象存储到辅助存储器中。

public interface Manager {
    
    // 将Manager与容器相关联
    public void setContainer(Container container);

    // 创建一个session实例
    public Session createSession();

    // 将session实例添加到Session池中
    public void add(Session session);

    // 将session实例从Session池中移除
    public void remove(Session session);

    // 设置此管理器创建的session对象的默认最长存活时间
    public void setMaxInactiveInterval(int interval);

    // 持久化到储存器中
    public void unload() throws IOException;
    
    // 从储存器中拿出来
    public void load() throws ClassNotFoundException, IOException;

9.2.2 ManagerBase类

Context容器中所有活动的Session对象都存储在一个名为sessions的HashMap变量中:

protected HashMap sessions = new HashMap();

public void add(Session session) {
    synchronized (sessions) {
        sessions.put(session.getId(), session);
    }
}

9.2.3 StandardManager类

实现了Lifecyle接口,通过它对Session对象进行load和unload;会生成一个SESSION.ser的文件中。

session管理器还要负责销毁那些失效的Session对象:

public void run() {
    while (!threadDone) {
        threadSleep();
        processExpires();
    }
}

9.2.4 PersistentManagerBase类

// 使用stroe代表辅助储存器
private Store store = null;

本章剩下的看书把。

十一、StandardWrapper

11.1 方法调用序列

How Tomcat Works读书笔记插图2

1、连接器创建request和response对象

2、连接器调用StandardContext实例的invoke()方法

3、StandardContext实例的invoke()方法会调用其管道对象(Pipline)的invoke()方法。最终会调用管道对象中的基础阀StandardContextValve实例的invoke()方法。

4、StandardContextValve实例的invoke()方法会获取相应的Wrapper实例处理Http请求,调用Wrapper实例的invoke方法。

5、同上,StandardWrapper实例的invoke()方法调用其管道对象的invoke()方法。

6、同上,调用基础阀StandardWrapperValve实例的invoke()方法,invoke方法会调用Wrapper实例的allocate()方法获取servlet实例。

7、allocate()调用load()方法载入相应的servlet类,如果已经载入,无需重新载入。

8、load()方法会调用servlet实例的init()方法

9、StandardWrapper实例调用servlet实例的service()方法。

11.3 StandardWrapper

StandardWrapper对象的主要任务是载入(loader.load())它所代表的servlet类,并进行实例化(clazz.newInstance())。

11.3.1 分配servlet实例(Wrapper的allocate()方法)

public Servlet allocate() throws ServletException {

    // SingleThreadedModel现在已经废弃,就不介绍了
    if (!singleThreadModel) {

        // 双重检查锁,检查servlet之前是否加载过
        if (instance == null) {
            synchronized (this) {
                if (instance == null) {
                    try {
                        instance = loadServlet();
                    } catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        throw new ServletException
                                (sm.getString("standardWrapper.allocate"), e);
                    }
                }
            }
        }

        if (!singleThreadModel) {
            if (debug >= 2)
                log("  Returning non-STM instance");
            // 将已分配的计数+1
            countAllocated++;
            return (instance);
        }

    }
}

11.3.2 载入Servlet类(Wrapper的load()方法)

public synchronized void load() throws ServletException {
    instance = loadServlet();
}

// allocate()和load()方法都调用了该方法
public synchronized Servlet loadServlet() throws ServletException {

    // 如果之前已经加载过了,直接返回
    if (instance != null)
        return instance;

    PrintStream out = System.out;
    SystemLogHandler.startCapture();

    Servlet servlet = null;
    try {
        // 将servlet类的类名赋值给actualClass
        String actualClass = servletClass;
        // 检查请求的servlet是不是一个jsp页面,如果是的话重新赋值actualClass
        if ((actualClass == null) && (jspFile != null)) {
            Wrapper jspWrapper = (Wrapper)
                    ((Context) getParent()).findChild(Constants.JSP_SERVLET_NAME);
            if (jspWrapper != null)
                actualClass = jspWrapper.getServletClass();
        }

        // 如果它是servlet,但是没有setServletClass()就会报错
        if (actualClass == null) {
            unavailable(null);
            throw new ServletException
                    (sm.getString("standardWrapper.notClass", getName()));
        }

        // 获取载入器(如果当前容器没有载入器,就获取它父容器的载入器)
        Loader loader = getLoader();
        if (loader == null) {
            unavailable(null);
            throw new ServletException
                    (sm.getString("standardWrapper.missingLoader", getName()));
        }

        // 获取载入器中的类加载器
        ClassLoader classLoader = loader.getClassLoader();

        // Catalina提供了一些用于访问servlet容器内部数据专用的servlet类,如果要加载的类是这种专用的servlet(参见19章),则将ClassLoader辅值为当前类的加载器
        if (isContainerProvidedServlet(actualClass)) {
            classLoader = this.getClass().getClassLoader();
            log(sm.getString
                    ("standardWrapper.containerServlet", getName()));
        }

        // Load the specified servlet class from the appropriate class loader
        Class classClass = null;
        try {
            if (classLoader != null) {
                System.out.println("Using classLoader.loadClass");
                // 使用类加载器加载该类
                classClass = classLoader.loadClass(actualClass);
            } else {
                System.out.println("Using forName");
                classClass = Class.forName(actualClass);
            }
        } catch (ClassNotFoundException e) {
            unavailable(null);
            throw new ServletException
                    (sm.getString("standardWrapper.missingClass", actualClass),
                            e);
        }
        if (classClass == null) {
            unavailable(null);
            throw new ServletException
                    (sm.getString("standardWrapper.missingClass", actualClass));
        }

        // Instantiate and initialize an instance of the servlet class itself
        try {
            // 实例化
            servlet = (Servlet) classClass.newInstance();
        } catch (ClassCastException e) {
            unavailable(null);
            // Restore the context ClassLoader
            throw new ServletException
                    (sm.getString("standardWrapper.notServlet", actualClass), e);
        } catch (Throwable e) {
            unavailable(null);
            // Restore the context ClassLoader
            throw new ServletException
                    (sm.getString("standardWrapper.instantiate", actualClass), e);
        }

        // 检查该类是否允许载入
        if (!isServletAllowed(servlet)) {
            throw new SecurityException
                    (sm.getString("standardWrapper.privilegedServlet",
                            actualClass));
        }

        // 检查该servlet是否实现了ContainerServlet接口(参见19章),如果实现了,将当前Wrapper对象设置进去
        if ((servlet instanceof ContainerServlet) &&
                isContainerProvidedServlet(actualClass)) {
            System.out.println("calling setWrapper");
            ((ContainerServlet) servlet).setWrapper(this);
            System.out.println("after calling setWrapper");
        }

        try {
            // 触发BEFORE_INIT_EVENT事件
            instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT, servlet);

            // 初始化servlet
            servlet.init(facade);

            // loadOnStartup:此servlet的启动时加载顺序值,负值表示首次调用时加载
            // 如果是jsp页面,调用该servlet的service方法
            if ((loadOnStartup > 0) && (jspFile != null)) {
                // Invoking jspInit
                HttpRequestBase req = new HttpRequestBase();
                HttpResponseBase res = new HttpResponseBase();
                req.setServletPath(jspFile);
                req.setQueryString("jsp_precompile=true");
                servlet.service(req, res);
            }
            // 触发AFTER_INIT_EVENT事件
            instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, servlet);
        } catch (UnavailableException f) {
            instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
                    servlet, f);
            unavailable(f);
            throw f;
        } catch (ServletException f) {
            instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
                    servlet, f);
            // If the servlet wanted to be unavailable it would have
            // said so, so do not call unavailable(null).
            throw f;
        } catch (Throwable f) {
            instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
                    servlet, 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);
        }

    } finally {
        String log = SystemLogHandler.stopCapture();
        if (log != null && log.length() > 0) {
            if (getServletContext() != null) {
                getServletContext().log(log);
            } else {
                out.println(log);
            }
        }
    }
    return servlet;

}

11.3.3 ServletConfig对象

StandardWrapper本身就是ServletConfig对象。

上面loadServlet()方法中加载完servlet之后对其进行初始化,init()方法需要传入一个ServletConfig对象:

// 初始化servlet;因为不信任servlet程序员所以使用一个门面进行包装一下
servlet.init(facade);
public interface ServletConfig {
    String getServletName();

    ServletContext getServletContext();

    String getInitParameter(String var1);

    Enumeration getInitParameterNames();
}
public final class StandardWrapper
        extends ContainerBase
        implements ServletConfig, Wrapper {

    public String getServletName() {
        return name;
    }

    public ServletContext getServletContext() {
        if (parent == null)
            return (null);
        else if (!(parent instanceof Context))
            return (null);
        else
            return (((Context) parent).getServletContext());
    }

    private HashMap parameters = new HashMap();

    public String getInitParameter(String name) {
        return (findInitParameter(name));
    }

    public String findInitParameter(String name) {
        synchronized (parameters) {
            return ((String) parameters.get(name));
        }
    }

    public Enumeration getInitParameterNames() {
        synchronized (parameters) {
            return (new Enumerator(parameters.keySet()));
        }
    }

11.3.4 Servlet容器的父子关系

Wrapper实例代表一个servlet实例,不能再有子容器,所以调用它的addChildren()方法会报错。

11.5 StandardWrapperValve(基础阀)

它主要要干以下两个操作:

  1. 执行与该servlet实例关联的全部过滤器(Filter)
  2. 调用servlet实例的service()方法

它的invoke方法:

public void invoke(Request request, Response response,
                       ValveContext valveContext)
            throws IOException, ServletException {
        ...

        // Allocate a servlet instance to process this request
        try {
            if (!unavailable) {
                // 获取servlet实例
                servlet = wrapper.allocate();
            }
        } catch (ServletException e) {
            log(sm.getString("standardWrapper.allocateException",
                    wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
            servlet = null;
        } catch (Throwable e) {
            log(sm.getString("standardWrapper.allocateException",
                    wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
            servlet = null;
        }

        // Acknowlege the request
        try {
            response.sendAcknowledgement();
        } catch (IOException e) {
            sreq.removeAttribute(Globals.JSP_FILE_ATTR);
            log(sm.getString("standardWrapper.acknowledgeException",
                    wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
        } catch (Throwable e) {
            log(sm.getString("standardWrapper.acknowledgeException",
                    wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
            servlet = null;
        }

        // 创建过滤器链
        ApplicationFilterChain filterChain = createFilterChain(request, servlet);

        // Call the filter chain for this request
        // NOTE: This also calls the servlet's service() method
        try {
            String jspFile = wrapper.getJspFile();
            if (jspFile != null)
                sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
            else
                sreq.removeAttribute(Globals.JSP_FILE_ATTR);
            if ((servlet != null) && (filterChain != null)) {

                // 调用过滤器链
                filterChain.doFilter(sreq, sres);
            }
            sreq.removeAttribute(Globals.JSP_FILE_ATTR);
        } catch (IOException e) {
            sreq.removeAttribute(Globals.JSP_FILE_ATTR);
            log(sm.getString("standardWrapper.serviceException",
                    wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
        } catch (UnavailableException e) {
            sreq.removeAttribute(Globals.JSP_FILE_ATTR);
            log(sm.getString("standardWrapper.serviceException",
                    wrapper.getName()), e);
            //            throwable = e;
            //            exception(request, response, e);
            wrapper.unavailable(e);
            long available = wrapper.getAvailable();
            if ((available > 0L) && (available < Long.MAX_VALUE))
                hres.setDateHeader("Retry-After", available);
            hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                    sm.getString("standardWrapper.isUnavailable",
                            wrapper.getName()));
            // Do not save exception in 'throwable', because we
            // do not want to do exception(request, response, e) processing
        } catch (ServletException e) {
            sreq.removeAttribute(Globals.JSP_FILE_ATTR);
            log(sm.getString("standardWrapper.serviceException",
                    wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
        } catch (Throwable e) {
            sreq.removeAttribute(Globals.JSP_FILE_ATTR);
            log(sm.getString("standardWrapper.serviceException",
                    wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
        }

        // Release the filter chain (if any) for this request
        try {
            if (filterChain != null)
                // 释放过滤器链
                filterChain.release();
        } catch (Throwable e) {
            log(sm.getString("standardWrapper.releaseFilters",
                    wrapper.getName()), e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, e);
            }
        }

        // Deallocate the allocated servlet instance
        try {
            if (servlet != null) {
                // 释放对该servlet的“引用计数”
                wrapper.deallocate(servlet);
            }
        } catch (Throwable e) {
            log(sm.getString("standardWrapper.deallocateException",
                    wrapper.getName()), e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, e);
            }
        }

        // If this servlet has been marked permanently unavailable,
        // unload it and release this instance
        try {
            if ((servlet != null) &&
                    (wrapper.getAvailable() == Long.MAX_VALUE)) {
                // 如果该servlet类被标记为永久不可用,则调用Wrapper.unload()方法
                wrapper.unload();
            }
        } catch (Throwable e) {
            log(sm.getString("standardWrapper.unloadException",
                    wrapper.getName()), e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, e);
            }
        }

    }

11.6 FilterDef

// FilterDef类中的每个属性表示在定义filter元素时声明的子元素(web.xml)。parameters中保存的是初始化过滤器时所需要的参数。
public final class FilterDef {

    /**
     * The description of this filter.
     */
    private String description = null;

    /**
     * The display name of this filter.
     */
    private String displayName = null;

    /**
     * The fully qualified name of the Java class that implements this filter.
     */
    private String filterClass = null;

    /**
     * The name of this filter, which must be unique among the filters
     * defined for a particular web application.
     */
    private String filterName = null;

    /**
     * The large icon associated with this filter.
     */
    private String largeIcon = null;

    /**
     * The set of initialization parameters for this filter, keyed by
     * parameter name.
     */
    private Map parameters = new HashMap();


    /**
     * The small icon associated with this filter.
     */
    private String smallIcon = null;


    // --------------------------------------------------------- Public Methods


    /**
     * Add an initialization parameter to the set of parameters associated
     * with this filter.
     *
     * @param name The initialization parameter name
     * @param value The initialization parameter value
     */
    public void addInitParameter(String name, String value) {

        parameters.put(name, value);

    }

11.7 ApplicationFilterConfig类

final class ApplicationFilterConfig implements FilterConfig {

    // context对象表示一个web应用程序
    // filterDef表示一个过滤器的定义
    public ApplicationFilterConfig(Context context, FilterDef filterDef)
        throws ClassCastException, ClassNotFoundException,
               IllegalAccessException, InstantiationException,
               ServletException {

        super();
        this.context = context;
        setFilterDef(filterDef);

        // 尝试从filterDef中获取Filter对象,如果为空则通过反射的形式创建filter对象
        if (filterDef.getFilter() == null) {
            getFilter();
        } else {
            this.filter = filterDef.getFilter();
            getInstanceManager().newInstance(filter);
            initFilter();
        }
    }

    // 负责载入并实例化一个过滤器类
    Filter getFilter() throws ClassCastException, ClassNotFoundException,
        IllegalAccessException, InstantiationException, ServletException {

        // Return the existing filter instance, if any
        if (this.filter != null)
            return (this.filter);

        // 获取过滤器的全类路径
        String filterClass = filterDef.getFilterClass();
        ClassLoader classLoader = null;
        if (filterClass.startsWith("org.apache.catalina."))
            classLoader = this.getClass().getClassLoader();
        else
            classLoader = context.getLoader().getClassLoader();

        ClassLoader oldCtxClassLoader =
            Thread.currentThread().getContextClassLoader();

        // 使用类加载器加载该类
        Class clazz = classLoader.loadClass(filterClass);
        // 构造+初始化
        this.filter = (Filter) clazz.newInstance();
        filter.init(this);
        return (this.filter);

    }

11.9 ApplicationFilterChain类

Filter的doFilter()方法会接受一个ApplicationFilterChain对象,可以直接调用chain.doFilter()调用下一个过滤器。

十二、StandardContext类

Context容器需要其它组件支持,例如Session管理器,载入器等。

12.1 StandardContext的配置

StandardContext实例创建后会调用它的start()方法,但是可能会启动失败,这时它的available属性会被设置为false,该属性表明StandardContext对象是否可用。

StandardContext类的configured属性是一个布尔变量,表明StandardContext实例是否正确设置。当start()方法被调用时,他会触发一个生命周期事件,事件触发监听器(ContextConfig),对StandardContext实例进行配置。

12.1.2 启动流程

1、触发BEFORE_START事件

2、将available属性设置为false

3、将configured属性设置为false

4、配置资源(设置web应用的目录)

5、设置载入器(setLoder(new WebappLoader()))

6、设置session管理器(setManger(new Manger()))

7、初始化字符集映射器

8、启动与该Context容器相关联的组件

9、启动子容器

10、启动管道对象

11、启动session管理器

12、触发START事件,此时ContextConfig监听器会对Context进行一些配置操作,如果设置成功就会将configured属性设置为true

13、检查configured属性的值,如果为true则调用postWelcomePages()方法,载入哪些在启动时就载入的子容器(Wrapper实例),将available属性设置为true。若configured属性为false

12.1.3 invoke()方法

该方法由与Context相关的连接器或者父容器(Host)的invoke()方法调用。

public void invoke(Request request, Response response)
        throws IOException, ServletException {

    // 如果正在重载(paused属性为true),则等待
    while (getPaused()) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            ;
        }
    }

    // Normal request processing
    if (swallowOutput) {
        try {
            SystemLogHandler.startCapture();
            // 调用父类ContainerBase的invoke()方法
            super.invoke(request, response);
        } finally {
            String log = SystemLogHandler.stopCapture();
            if (log != null && log.length() > 0) {
                log(log);
            }
        }
    } else {
        super.invoke(request, response);
    }

}

public void invoke(Request request, Response response)
        throws IOException, ServletException {
    pipeline.invoke(request, response);
}

12.2 映射器

Tomcat5中映射器已经移除,通过request对象获取Wrapper实例:

// 是不是解析URI的工作交给了连接器??
Wrapper wrapper = request.getWrapper();

12.3 对重载的支持

StandardContext通过reloadable属性来表明应用程序是否启用了重载功能,当启用了重载功能后,web.xml文件发生变化或WEB-INF/classes目录下的其中一个文件被重新编译后,应用程序就会重载。

StandardContext时通过其载入器实现应用程序的重载的(见8.4)。WebappLoader实现了Runable接口使用另一个线程来不断检查WEB-INF目录中的所有类和JAR文件的时间戳。只需要调用WebappLoader的setContainer()方法将容器和载入器相关联就可以启动该检查线程。

// WebappLoader.java

public void setContainer(Container container) {

    // Deregister from the old Container (if any)
    if ((this.container != null) && (this.container instanceof Context))
        ((Context) this.container).removePropertyChangeListener(this);

    // Process this property change
    Container oldContainer = this.container;
    this.container = container;
    support.firePropertyChange("container", oldContainer, this.container);

    // Register with the new Container (if any)
    if ((this.container != null) && (this.container instanceof Context)) {
        // 启动检查线程
        setReloadable( ((Context) this.container).getReloadable() );
        ((Context) this.container).addPropertyChangeListener(this);
    }

}

public void setReloadable(boolean reloadable) {

    // Process this property change
    boolean oldReloadable = this.reloadable;
    this.reloadable = reloadable;
    support.firePropertyChange("reloadable",
                                new Boolean(oldReloadable),
                                new Boolean(this.reloadable));

    // Start or stop our background thread if required
    if (!started)
        return;
    // 之前没启动,现在启动了,则启动重载功能
    if (!oldReloadable && this.reloadable)
        // run()方法参见8.4.5
        threadStart();
    // 之前启动了,现在没启动,则关闭
    else if (oldReloadable && !this.reloadable)
        threadStop();

}

12.4 backgroundProcess()方法

Context容器的运行需要其它组件的支持,例如:载入器、Session管理器。而它们大多都需要执行一个后台任务,Tomcat4中它们都实现了Runable接口,拥有自己的线程。

在Tomcat5中为了节省资源,所有后台处理共享一个线程。这个共享线程有ContainerBase的start()方法调用threadStart()创建。

protected void threadStart() {

    if (thread != null)
        return;
    if (backgroundProcessorDelay <= 0)
        return;

    threadDone = false;
    String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
    thread = new Thread(new ContainerBackgroundProcessor(), threadName);
    thread.setDaemon(true);
    thread.start();

}


protected class ContainerBackgroundProcessor implements Runnable {

    @Override
    public void run() {
        Throwable t = null;
        String unexpectedDeathMessage = sm.getString(
                "containerBase.backgroundProcess.unexpectedThreadDeath",
                Thread.currentThread().getName());
        try {
            while (!threadDone) {
                try {
                    Thread.sleep(backgroundProcessorDelay * 1000L);
                } catch (InterruptedException e) {
                    // Ignore
                }
                if (!threadDone) {
                    processChildren(ContainerBase.this);
                }
            }
        } catch (RuntimeException|Error e) {
            t = e;
            throw e;
        } finally {
            if (!threadDone) {
                log.error(unexpectedDeathMessage, t);
            }
        }
    }

    protected void processChildren(Container container) {
        ClassLoader originalClassLoader = null;

        try {
            if (container instanceof Context) {
                Loader loader = ((Context) container).getLoader();
                // Loader will be null for FailedContext instances
                if (loader == null) {
                    return;
                }

                // Ensure background processing for Contexts and Wrappers
                // is performed under the web app's class loader
                originalClassLoader = ((Context) container).bind(false, null);
            }
            // 调用容器的后台进程
            container.backgroundProcess();
            Container[] children = container.findChildren();
            for (int i = 0; i < children.length; i++) {
                if (children[i].getBackgroundProcessorDelay() <= 0) {
                    processChildren(children[i]);
                }
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error("Exception invoking periodic operation: ", t);
        } finally {
            if (container instanceof Context) {
                ((Context) container).unbind(false, originalClassLoader);
            }
        }
    }
}

// StandardContext的backgroundProcess()方法
@Override
public void backgroundProcess() {

    if (!getState().isAvailable())
        return;

    Loader loader = getLoader();
    if (loader != null) {
        try {
            loader.backgroundProcess();
        } catch (Exception e) {
            log.warn(sm.getString(
                    "standardContext.backgroundProcess.loader", loader), e);
        }
    }
    Manager manager = getManager();
    if (manager != null) {
        try {
            manager.backgroundProcess();
        } catch (Exception e) {
            log.warn(sm.getString(
                    "standardContext.backgroundProcess.manager", manager),
                    e);
        }
    }
    WebResourceRoot resources = getResources();
    if (resources != null) {
        try {
            resources.backgroundProcess();
        } catch (Exception e) {
            log.warn(sm.getString(
                    "standardContext.backgroundProcess.resources",
                    resources), e);
        }
    }
    InstanceManager instanceManager = getInstanceManager();
    if (instanceManager instanceof DefaultInstanceManager) {
        try {
            ((DefaultInstanceManager)instanceManager).backgroundProcess();
        } catch (Exception e) {
            log.warn(sm.getString(
                    "standardContext.backgroundProcess.instanceManager",
                    resources), e);
        }
    }
    super.backgroundProcess();
}

十三、Host和Engine

如果想要在同一个Tomcat部署多个Context容器的话,就需要使用Host容器。

13.1 Host接口

// 用于返回用于处理引入的Http请求的Context容器实例(Tomcat5后已经移除,从request对象中获取)
Context map(String uri);

13.2 StandardHost

public class StandardHost
    extends ContainerBase
    implements Deployer, Host {

    public StandardHost() {
        super();
        pipeline.setBasic(new StandardHostValve());
    }

    public synchronized void start() throws LifecycleException {
        // Set error report valve
        if ((errorReportValveClass != null)
            && (!errorReportValveClass.equals(""))) {
            try {
                Valve valve = (Valve) Class.forName(errorReportValveClass)
                    .newInstance();
                // 添加一个ErrorReportValve阀
                addValve(valve);
            } catch (Throwable t) {
                log(sm.getString
                    ("standardHost.invalidErrorReportValveClass",
                     errorReportValveClass));
            }
        }

        // 添加一个ErrorDispatcherValve阀
        addValve(new ErrorDispatcherValve());

        super.start();
    }

后面的放弃了,感觉没啥用...

十四、服务器组件(Service)和服务组件(Server)

Server代表着整个tomcat

Service作用将容器(engine)和连接器组装起来,为外界提供服务

<Server port="8005" shutdown="SHUTDOWN">
    <Service name="Catalina">
        <Connector port="8080" protocol="HTTP/1.1"
                    connectionTimeout="20000"
                    redirectPort="8443" />
        <!-- Define an AJP 1.3 Connector on port 8009 -->
        <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
        
        <Engine name="Catalina" defaultHost="localhost">
            <Host name="localhost"  appBase="webapps/1"
                unpackWARs="true" autoDeploy="true">
            </Host>
            <Host name="www.baidu.com"  appBase="webapps/3"
                unpackWARs="true" autoDeploy="true">
            </Host>
        </Engine>
    </Service>
</Server>

14.1 服务器组件

它提供了一种优雅的方法来启动/关闭整个系统,不需要再对连接器和容器分别启动/关闭。

public interface Server {
    
    // 关闭命令
    String getShutdown();

    // 监听的端口号
    int getPort();

    // 添加服务组件
    void addService(Service service);

    // 删除服务组件
    void removeService(Service service);

14.2 StandardServer类

StandardServer类有4个与生命周期相关的方法,分别是initialize()方法、start()方法、stop()方法、await()方法。

调用await()方法后会一直阻塞住,直到8085端口上接收到关闭命令。当await()方法返回后,会运行stop()方法来关闭其下的所有子组件(Catalina的start()方法调用的)。

public final class StandardServer
    implements Lifecycle, Server {

14.2.1 initialize()方法

初始化服务器组件

public void initialize() throws LifecycleException {
    // 使用这个变量防止服务器组件初始化两次
    if (initialized)
        throw new LifecycleException (sm.getString("standardServer.initialize.initialized"));
    initialized = true;

    // Initialize our defined Services
    for (int i = 0; i < services.length; i++) {
        // 初始化它下面的服务组件
        services[i].initialize();
    }
}

14.2.2 start()方法

启动服务器组件

public void start() throws LifecycleException {

    // Validate and update our current component state
    if (started)
        throw new LifecycleException
            (sm.getString("standardServer.start.started"));
    // Notify our interested LifecycleListeners
    lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);

    lifecycle.fireLifecycleEvent(START_EVENT, null);
    started = true;

    // Start our defined Services
    synchronized (services) {
        for (int i = 0; i < services.length; i++) {
            // 启动它下面的服务组件,服务组件启动时会启动连接器组件和servlet容器
            if (services[i] instanceof Lifecycle)
                ((Lifecycle) services[i]).start();
        }
    }

    // Notify our interested LifecycleListeners
    lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);

}

14.2.3 stop()方法

关闭服务器组件

public void stop() throws LifecycleException {

    // Validate and update our current component state
    if (!started)
        throw new LifecycleException
            (sm.getString("standardServer.stop.notStarted"));

    // Notify our interested LifecycleListeners
    lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);

    lifecycle.fireLifecycleEvent(STOP_EVENT, null);
    started = false;

    // 调用下面的服务组件的stop方法
    for (int i = 0; i < services.length; i++) {
        if (services[i] instanceof Lifecycle)
            ((Lifecycle) services[i]).stop();
    }

    // Notify our interested LifecycleListeners
    lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);

}

14.2.4 await()方法

负责等待关闭整个Tomcat部署的命令

14.3 Service接口

一个服务组件可以有一个servlet容器和多个连接器实例。

14.1 StandardService类

它的initialize()方法用于初始化添加到其中的所有连接器。

start()方法负责启动被添加到该服务组件的连接器和servlet容器。

十五、Digester库

Digester可以将xml转换成对象。

15.2 ContextConfig(见12.1)

与其它容器不同,Context容器必须设置一个监听器来对Context进行配置,设置成功后将configured属性为true。

工作内容:

  1. 他会为Context的pipline添加一个许可阀和验证器阀;
  2. 读取和解析默认的web.xml和应用自定义的web.xml文件,将xml转换成java对象;
  3. 为每个servlet创建一个StandardWrapper对象。
public void lifecycleEvent(LifecycleEvent event) {

    // Identify the context we are associated with
    try {
        context = (Context) event.getLifecycle();
        if (context instanceof StandardContext) {
            int contextDebug = ((StandardContext) context).getDebug();
            if (contextDebug > this.debug)
                this.debug = contextDebug;
        }
    } catch (ClassCastException e) {
        log(sm.getString("contextConfig.cce", event.getLifecycle()), e);
        return;
    }

    // 处理开始和结束事件
    if (event.getType().equals(Lifecycle.START_EVENT))
        start();
    else if (event.getType().equals(Lifecycle.STOP_EVENT))
        stop();

}

private synchronized void start() {

    if (debug > 0)
        log(sm.getString("contextConfig.start"));
    context.setConfigured(false);
    ok = true;

    // Set properties based on DefaultContext
    Container container = context.getParent();
    if( !context.getOverride() ) {
        if( container instanceof Host ) {
            // 和DefaultContext有关(它加载了好多的东西,包括listener)
            ((Host)container).importDefaultContext(context);
            container = container.getParent();
        }
        if( container instanceof Engine ) {
            // 和DefaultContext有关(它加载了好多的东西,包括listener)
            ((Engine)container).importDefaultContext(context);
        }
    }

    // 读取默认的web.xml
    defaultConfig();
    // 读取应用的web.xml
    applicationConfig();
    if (ok) {
        validateSecurityRoles();
    }

    // Scan tag library descriptor files for additional listener classes
    if (ok) {
        try {
            tldScan();
        } catch (Exception e) {
            log(e.getMessage(), e);
            ok = false;
        }
    }

    // Configure a certificates exposer valve, if required
    if (ok)
        certificatesConfig();

    // Configure an authenticator if we need one
    if (ok)
        authenticatorConfig();

    // Dump the contents of this pipeline if requested
    if ((debug >= 1) && (context instanceof ContainerBase)) {
        log("Pipline Configuration:");
        Pipeline pipeline = ((ContainerBase) context).getPipeline();
        Valve valves[] = null;
        if (pipeline != null)
            valves = pipeline.getValves();
        if (valves != null) {
            for (int i = 0; i < valves.length; i++) {
                log("  " + valves[i].getInfo());
            }
        }
        log("======================");
    }

    // Make our application available if no problems were encountered
    if (ok)
        context.setConfigured(true);
    else {
        log(sm.getString("contextConfig.unavailable"));
        context.setConfigured(false);
    }

}

十六、关闭钩子

Java虚拟机关闭之前会先启动所有的关闭钩子。

Tomcat利用关闭钩子进行清理工作(调用组件们的stop()方法)

protected class CatalinaShutdownHook extends Thread {

    public void run() {

        if (server != null) {
            try {
                ((Lifecycle) server).stop();
            } catch (LifecycleException e) {
                System.out.println("Catalina.stop: " + e);
                e.printStackTrace(System.out);
                if (e.getThrowable() != null) {
                    System.out.println("----- Root Cause -----");
                    e.getThrowable().printStackTrace(System.out);
                }
            }
        }

    }
}

十七、启动Tomcat

Tomcat启动时会用到两个类,分别时Catalina类和Bootstrap类。

Catalina类用于启动和关闭Server对象,并负责解析Tomcat配置文件server.xml。

Bootstrap类是一个入口点,负责创建Catalina实例,并调用其process方法。

17.1 Catalina类

其中包含一个Digester对象,用于解析server.xml。

process()方法:

public void process(String args[]) {
    // 设置连个系统属性,分别时catalina.home和catalina.base.catalina.home
    setCatalinaHome();
    setCatalinaBase();
    try {
        // argument()处理命令行参数
        if (arguments(args))
            // 
            execute();
    } catch (Exception e) {
        e.printStackTrace(System.out);
    }
}

protected void execute() throws Exception {
    if (starting)
        start();
    else if (stopping)
        stop();
}

17.1.1 start()方法

start()方法部分代码:

// 使用Digester对象解析server.xml
...

if (server instanceof Lifecycle) {
    try {
        // 初始化服务器组件
        server.initialize();
        // 启动服务器组件
        ((Lifecycle) server).start();
        try {
            // 注册关闭时的钩子
            Runtime.getRuntime().addShutdownHook(shutdownHook);
        } catch (Throwable t) {
            // This will fail on JDK 1.2. Ignoring, as Tomcat can run
            // fine without the shutdown hook.
        }
        // Wait for the server to be told to shut down
        server.await();
    } catch (LifecycleException e) {
        System.out.println("Catalina.start: " + e);
        e.printStackTrace(System.out);
        if (e.getThrowable() != null) {
            System.out.println("----- Root Cause -----");
            e.getThrowable().printStackTrace(System.out);
        }
    }
}

17.1.2 stop()方法

protected void stop() {

    // Create and execute our Digester
    Digester digester = createStopDigester();
    File file = configFile();
    try {
        InputSource is =
            new InputSource("file://" + file.getAbsolutePath());
        FileInputStream fis = new FileInputStream(file);
        is.setByteStream(fis);
        digester.push(this);
        digester.parse(is);
        fis.close();
    } catch (Exception e) {
        System.out.println("Catalina.stop: " + e);
        e.printStackTrace(System.out);
        System.exit(1);
    }

    // Stop the existing server
    try {
        // 向server组件监听的端口发送SHUTDOWN命令
        Socket socket = new Socket("127.0.0.1", server.getPort());
        OutputStream stream = socket.getOutputStream();
        String shutdown = server.getShutdown();
        for (int i = 0; i < shutdown.length(); i++)
            stream.write(shutdown.charAt(i));
        stream.flush();
        stream.close();
        socket.close();
    } catch (IOException e) {
        System.out.println("Catalina.stop: " + e);
        e.printStackTrace(System.out);
        System.exit(1);
    }

}

Bootstrap主要是创建了3个类加载器,将sharedClassLoader设置给了Catalina的parentClassLoader属性。

十八、部署器

要使用一个web应用程序,必须要将该应用程序的Context实例部署到一个Host实例中。在Tomcat中,Context实例可以用war文件的形式来部署。

部署器是Deployer接口的实例。部署器与Host实例相关联,用来安装Context实例(既创建一个StandardContext实例,并添加到Host实例中)

Tomcat8中已经找不到它的身影。

赞(0) 打赏
未经允许不得转载:IDEA激活码 » How Tomcat Works读书笔记

一个分享Java & Python知识的社区