A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

Tomcat的启动与类的具体实现
一个WEB应用对应一个context容器,也就是servlet运行时的servlet容器。
添加一个web应用时将会创建一个StandardContext容器,并且给这个context容器设置必要的参数,url和path分别代表这个应用在tomcat中的访问路径和这个应用实际的物理路径,这两个参数与tomcat配置中的两个参数是一致的。
其中一个最重要的一个配置是ContextConfig,这个类会负责整个web应用配置的解析工作。
最后将这个context容器加入到父容器host中。
接下来会调用tomcat的start方法启动tomcat。
Tomcat的启动逻辑是基于观察者模式的,所有的容器都会继承Lifecycle接口,它管理着容器的整个生命周期,所有容器的修改和状态改变都会由它通知已经注册的观察者。
Tomcat启动的时序如下:

________________________________________
仔细看看基本上就清楚了,这里只记录一下细节:
________________________________________
ContextConfig的init方法
当context容器初始状态设置Init时,添加到context容器的listener将会被调用。ContextConfig继承了LifecycleListener接口,它是在调用Tomcat.addWebapp时被加入到StandardContext容器中的。ContextConfig类会负责整个WEB应用的配置文件的解析工作。
1.        ContextConfig的init方法将会主要完成一下工作:
2.        创建用于解析XML配置文件的contextDigester对象
3.        读取默认的context.xml文件,如果存在则解析它
4.        读取默认的Host配置文件,如果存在则解析它
5.        读取默认的Context自身的配置文件,如果存在则解析它
6.        设置Context的DocBase
________________________________________
startInternal方法
ContextConfig的init方法完成后,Context容器会执行startInternal方法,这个方法包括如下几个部分:
1.        创建读取资源文件的对象
2.        创建ClassLoader对象
3.        设置应用的工作目录
4.        启动相关的辅助类,如logger,realm,resources等
5.        修改启动状态,通知感兴趣的观察者
6.        子容器的初始化
7.        获取ServletContext并设置必要的参数
8.        初始化“load on startuo”的Servlet
________________________________________
Web应用的初始化
web应用的初始化在14步,下面是该初始化的详细内容
WEB应用的初始化工作是在ContextConfig的configureStart方法中实现的,应用的初始化工作主要是解析web.xml文件,这个文件是一个WEB应用的入口。
1.        Tomcat首先会找globalWebXml,这个文件的搜索路径是engine的工作目录下的org/apache/catalina/startup/NO-DEFAULT_XML或conf/web.xml。
2.        接着会找hostWebXml,这个文件可能会在System.getProperty(“catalina.base”)/conf/${EngineName}/${HostName}/web.xml.default中。
3.        接着寻找应用的配置文件examples/WEB-INF/web.xml,web.xml文件中的各个配置项将会被解析成相应的属性保存在WebXml对象中。
4.        接下来会讲WebXml对象中的属性设置到context容器中,这里包括创建servlet对象,filter,listerner等,这些在WebXml的configureContext方法中。
以及下面是解析servlet的代码对象:
for (ServletDef servlet : servlets.values()) {  
    Wrapper wrapper = context.createWrapper();  
    String jspFile = servlet.getJspFile();  
    if (jspFile != null) {  
        wrapper.setJspFile(jspFile);  
    }  
    if (servlet.getLoadOnStartup() != null) {  
        wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());  
    }  
    if (servlet.getEnabled() != null) {  
        wrapper.setEnabled(servlet.getEnabled().booleanValue());  
    }  
    wrapper.setName(servlet.getServletName());  
    Map<String,String> params = servlet.getParameterMap();  
    for (Entry<String, String> entry : params.entrySet()) {  
        wrapper.addInitParameter(entry.getKey(), entry.getValue());  
    }  
    wrapper.setRunAs(servlet.getRunAs());  
    Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();  
    for (SecurityRoleRef roleRef : roleRefs) {  
        wrapper.addSecurityReference(roleRef.getName(), roleRef.getLink());  
    }  
    wrapper.setServletClass(servlet.getServletClass());  
    MultipartDef multipartdef = servlet.getMultipartDef();  
    if (multipartdef != null) {  
        if (multipartdef.getMaxFileSize() != null &&  
            multipartdef.getMaxRequestSize()!= null &&  
            multipartdef.getFileSizeThreshold() != null) {  
                wrapper.setMultipartConfigElement(new MultipartConfigElement(  
                                                      multipartdef.getLocation(),  
                                                      Long.parseLong(multipartdef.getMaxFileSize()),  
                                                  Long.parseLong(multipartdef.getMaxRequestSize()),  
                                                  Integer.parseInt(  
                                                  multipartdef.getFileSizeThreshold())));  
        } else {  
        wrapper.setMultipartConfigElement(new MultipartConfigElement(  
                                              multipartdef.getLocation()));  
        }  
    }  
    if (servlet.getAsyncSupported() != null) {  
        wrapper.setAsyncSupported(  
            servlet.getAsyncSupported().booleanValue());  
    }  
    context.addChild(wrapper);  
}
3.创建Servlet实例
前面完成了servlet的解析工作,并且被包装成了StandardWrapper添加到Context容器中,但是它仍然不能为我们工作,它还没有被实例化。
3.1.创建
如果Servlet的load-on-startup配置项大于0,那么在Context容器启动时就会被实例化。
(这个是在web.xml中进行了配置)
前面提到的在解析配置文件时会读取默认的globalWebXml,在conf下的web.xml文件中定义了一些默认的配置项,其中定义了两个Servlet,分别是org.apache.catalina.servlets.DefaultServlet和org.apache.jsper.servlet.JspServelt,它们的load-on-startup分别是1和3,也就是当tomcat启动时这两个servlet就会被启动。
创建Servlet实例的方式是从Wrapper.loadServlet开始的,loadServlet方法要完成的就是获取servletClass,然后把它交给InstanceManager去创建一个基于servletClass.class的对象。如果这个Servlet配置了jsp-file,那么这个servletClass就是在conf/web.xml中定义的org.apache.jasper.servlet.JspServlet。
3.2.初始化
初始化Servlet在StandardWrapper的initServlet方法中,这个方法很简单,就是调用Servlet的init()方法,同时把包装了StandardWrapper对象的StandardWrapperFacade作为ServletConfig传给Servlet。
如果该Servlet关联的是一个JSP文件,那么前面初始化的就是JspServlet,接下来会模拟一次简单请求,请求调用这个JSP文件,以便编译这个JSP文件为类,并初始化这个类。
这样Servlet对象的初始化就完成了。
3.3.容器默认Servlet
每个servlet容器都有一个默认的servlet,一般都叫做default。
例如:tomcat中的 DefaultServlet 和 JspServlet (上面的部分)
4.创建HttpServlet
这一篇细细说说请求是怎么交到Servlet手上的。此时,包括Servlet都已经初始化化完毕,一切已经准备就绪。
HttpServletRequest的产生大概在第四步
步骤        过程        详情
1        AbstractEndpoint类及其子类来处理。        AbstractEndpoint这个抽象类中有一个抽象内部类Acceptor,这个Acceptor的实现类是AbstractEndpoint的三个子类的内部类Acceptor来实现的。
1                我们的请求就是被这Acceptor监听到并且接收的。这个类其实是一个线程类,因为AbstractEndpoint.Acceptor实现了Runnable接口。
1                AprEndpoint接收请求的过程。就是用一个接收器接收请求,过程中会使用套接字。但是好像并不是有的请求都会用这个Acceptor来接收。
1                当接收请求完毕,经过一系列的处理后就会由AprEndpoint的内部类SocketProcessor来将请求传给ProtocolHandler来处理。这个SocketProcessor也是一个线程类。它有一行代码将套接字传给了第二步来处理。
2        在/AbstractConnectionHandler接收到第一步传来的套接字以后,对套接字进行处理        wapper就是套接字包装类的对象,这里还是理解为套接字,套接字在这里传给了第③步的Processor接口的实例。state = processor.process(wrapper);
3        第二步完成后,就会交给Processor接口的实现类来处理。        在这里将会创建请求和响应,但不是我们熟悉的HttpServletRequest或HttpServletResponse类型或其子类型。而是org.apache.coyote.Request 和 org.apache.coyote.Response类型的
3                它将会在Connector中来进行处理,我说的是处理而不是类型转换是因为org.apache.coyote.Request 和HttpServletRequest并不是父子关系的类,总之,HttpServletRequest的请求是由Connector来创建,在CoyoteAdapter中处理成HttpServletRequest.
3                这个方法就是请求对象、响应对象和套接字进行信息交互的地方,也就是真真正正将套接字中的信息转化为请求信息,还要把响应信息写到套接字中。
4        第三步完成之后交给CoyoteAdapter来处理        CoyotoAdapter是将请求传入Server容器的切入点。Adapter. This represents the entry point in a coyote-based servlet container.
4        (具体怎么处理成HttpServletRequest看代码)        CoyoteAdapter中有一个service()方法。这个方法持有一个Connector的引用。这个Connector又持有一个Service容器的引用,而Service容器有持有一个Container(Container的实现类有StandardEngine、StandardHost等等)的引用。所以CoyoteAdapter就可以根据这些引用将请求传递到Server容器中了。
5        如果上面的请求传递到的Container是StandaradEngine,那么就会Engine就会调用它持有的StandardPipeline对象来处理请求。        StandardPipeline就相当于一条管道,这条管道中的有许多阀门,这些阀门会对请求进行处理,并且控制它下一步往哪里传递。StandardEngine的管道使用的阀门是StandardEngineValve。
6        容器继续逐层传递        和StandardEngine一样,StandardHost、StandardContext、StandardWrapper这几个容器都拥有自己的一条管道StandardPipeline来处理的请求。但是需要注意的是他们使用的阀门是不一样的。StandardHost则会使用StandardHostValve,其他的同理。
7        当最后一个StandardWrapperVale处理完请求后,此时请求已经到达了最底层的容器了。        StandardWrapper就是最底层的容器,它不允许再有子容器。其实每一个StandardWrapper代表一个Servlet,因为每一个StandardWrapper都会持有一个Servlet实例的引用。
//CoyoteAdapter的service(...)方法
/**
     * Service method.
     */
    @Override
    public void service(org.apache.coyote.Request req,
                        org.apache.coyote.Response res)
        throws Exception {
     //下面这两行代码就是将请求处理后转化为HttpServletRequest的
        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);
      //如果请求为null就让Connector来创建。
        if (request == null) {

            // Create objects
            request = connector.createRequest();
            request.setCoyoteRequest(req);
            response = connector.createResponse();
            response.setCoyoteResponse(res);
      //省略部分代码
        }

        if (connector.getXpoweredBy()) {
            response.addHeader("X-Powered-By", POWERED_BY);
        }

        boolean comet = false;
        boolean async = false;
        boolean postParseSuccess = false;

        try {
            // Parse and set Catalina and configuration specific
            // request parameters
            req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
            postParseSuccess = postParseRequest(req, request, res, response);
            if (postParseSuccess) {
                //check valves if we support async
                request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
                // Calling the container 在这里将请求传到Server容器中。
                connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

              //省略部分代码

            }

    }    /**
.Tomcat核心类
Tomcat的体系结构:
  
一个Connector+一个Container构成一个Service,Service就是对外提供服务的组件,有了Service组件Tomcat就可以对外提供服务了。
那么这些个组件到底是干嘛用的呢?
Connector是一个连接器,主要负责接收请求并把请求交给Container
Container就是一个容器,主要装的是具体处理请求的组件。
Service主要是为了关联Container与Connector,一个单独的Container或者一个单独的Connector都不能完整处理一个请求,只有两个结合在一起才能完成一个请求的处理。
5.1 Tomcat的两大组件
1. Connecter组件
一个Connecter将在某个指定的端口上侦听客户请求,接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理Engine(Container中的一部分),从Engine出获得响应并返回客户。
Tomcat中有两个经典的Connector,一个直接侦听来自Browser的HTTP请求,另外一个来自其他的WebServer请求。Cotote HTTP/1.1 Connector在端口8080处侦听来自客户Browser的HTTP请求,Coyote JK2 Connector在端口8009处侦听其他Web Server的Servlet/JSP请求。
Connector 最重要的功能就是接收连接请求然后分配线程让 Container 来处理这个请求,所以这必然是多线程的,多线程的处理是 Connector 设计的核心。
2. Container组件
主容器,包含Tomcat的各个容器。
Container是容器的父接口,由四个容器组成,分别是Engine,Host,Context,Wrapper。其中Engine包含Host,Host包含Context,Content包含Wrapper,一个Servlet class对应一个Wrapper

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马