Java与SpringBoot内置Tomcat源码运行原理与Servlet放置原理解析(org.apache.catalina.startup.Tomcat)

简单使用可以参考这篇文章:
https://blog.csdn.net/the_one_and_only/article/details/105177506

基本原理就是将Tomcat对象中的Host和Connector中的port设置成相应的host:port,配置Tomcat上下文(环境配置),最后加入Servlet,然后跑起来。

Tomcat是这样的:

在这里插入图片描述

Tomcat源码注释:

/**
 * Minimal tomcat starter for embedding/unit tests.
 *
 * <p>
 * Tomcat supports multiple styles of configuration and
 * startup - the most common and stable is server.xml-based,
 * implemented in org.apache.catalina.startup.Bootstrap.
 *
 * <p>
 * This class is for use in apps that embed tomcat.
 *
 * <p>
 * Requirements:
 * <ul>
 *   <li>all tomcat classes and possibly servlets are in the classpath.
 *       (for example all is in one big jar, or in eclipse CP, or in
 *        any other combination)</li>
 *
 *   <li>we need one temporary directory for work files</li>
 *
 *   <li>no config file is required. This class provides methods to
 *       use if you have a webapp with a web.xml file, but it is
 *       optional - you can use your own servlets.</li>
 * </ul>
 *
 * <p>
 * There are a variety of 'add' methods to configure servlets and webapps. These
 * methods, by default, create a simple in-memory security realm and apply it.
 * If you need more complex security processing, you can define a subclass of
 * this class.
 *
 * <p>
 * This class provides a set of convenience methods for configuring web
 * application contexts; all overloads of the method <code>addWebapp()</code>.
 * These methods are equivalent to adding a web application to the Host's
 * appBase (normally the webapps directory). These methods create a Context,
 * configure it with the equivalent of the defaults provided by
 * <code>conf/web.xml</code> (see {@link #initWebappDefaults(String)} for
 * details) and add the Context to a Host. These methods do not use a global
 * default web.xml; rather, they add a {@link LifecycleListener} to configure
 * the defaults. Any WEB-INF/web.xml and META-INF/context.xml packaged with the
 * application will be processed normally. Normal web fragment and
 * {@link javax.servlet.ServletContainerInitializer} processing will be applied.
 *
 * <p>
 * In complex cases, you may prefer to use the ordinary Tomcat API to create
 * webapp contexts; for example, you might need to install a custom Loader
 * before the call to {@link Host#addChild(Container)}. To replicate the basic
 * behavior of the <code>addWebapp</code> methods, you may want to call two
 * methods of this class: {@link #noDefaultWebXmlPath()} and
 * {@link #getDefaultWebXmlListener()}.
 *
 * <p>
 * {@link #getDefaultWebXmlListener()} returns a {@link LifecycleListener} that
 * adds the standard DefaultServlet, JSP processing, and welcome files. If you
 * add this listener, you must prevent Tomcat from applying any standard global
 * web.xml with ...
 *
 * <p>
 * {@link #noDefaultWebXmlPath()} returns a dummy pathname to configure to
 * prevent {@link ContextConfig} from trying to apply a global web.xml file.
 *
 * <p>
 * This class provides a main() and few simple CLI arguments,
 * see setters for doc. It can be used for simple tests and
 * demo.
 *
 * @see <a href="https://gitbox.apache.org/repos/asf?p=tomcat.git;a=blob;f=test/org/apache/catalina/startup/TestTomcat.java">TestTomcat</a>
 * @author Costin Manolache
 */


翻译:
用于嵌入/单元测试的最小tomcat启动器。
Tomcat支持多种类型的配置和startup,最常见和稳定的是基于server.xml的,在org.apache.catalina.startup.Bootstrap中实现。
这个类用于嵌入tomcat的应用程序。
所有tomcat类(可能还有servlet)都在类路径中。(例如所有的都在一个大的罐子里,或在eclipse CP里,或在任何其他组合)
我们需要一个临时目录来存放工作文件
不需要配置文件。该类提供方法如果你有一个带有web.xml文件的webapp,但它确实是optional,你可以使用你自己的servlet。
有各种各样的“添加”方法来配置servlet和webapps。这些方法,默认情况下,创建一个简单的内存安全域并应用它。如果你需要更复杂的安全处理,你可以定义这个类。
这个类提供了main()和一些简单的CLI参数,参见设置文件。它可以用于简单的测试和演示。

start是这样启动的:

    /**
     * Start the server.
     *
     * @throws LifecycleException Start error
     */
    public void start() throws LifecycleException {
        getServer();
        server.start();
    }

    /**
     * Get the server object. You can add listeners and few more
     * customizations. JNDI is disabled by default.
     * @return The Server
     */
    public Server getServer() {

        if (server != null) {
            return server;
        }

        System.setProperty("catalina.useNaming", "false");

        server = new StandardServer();

        initBaseDir();

        // Set configuration source
        ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));

        server.setPort( -1 );

        Service service = new StandardService();
        service.setName("Tomcat");
        server.addService(service);
        return server;
    }

要注意这个StandardService和StandardServer

public class StandardService extends LifecycleMBeanBase implements Service
public final class StandardServer extends LifecycleMBeanBase implements Server

我们从LifecycleMBeanBase 字面上可以看出,这是一个跟Tomcat组件生命周期有关的类
详情可以参考:https://blog.csdn.net/wojiushiwo945you/article/details/73331057

也就是说,到这里,我们已经知道Tomcat如何启动了。但是Tomcat里边有什么呢?

Tomcat是Servlet和JSP支持的容器,我们不管是传统的javaweb还是springboot都离不开Servlet。

在一开始的例子中,我们可以看到这两行代码:

Wrapper wrapper = tomcat.addServlet("/", "DemoServlet", new EasyServlet());
wrapper.addMapping("/embeddedTomcat");

其实就是在tomcat容器中丢入Servlet,并且配置路径映射。Servlet一般继承自HttpServlet。
HttpServlet:

/**
 * Provides an abstract class to be subclassed to create
 * an HTTP servlet suitable for a Web site. A subclass of
 * <code>HttpServlet</code> must override at least
 * one method, usually one of these:
 *
 * <ul>
 * <li> <code>doGet</code>, if the servlet supports HTTP GET requests
 * <li> <code>doPost</code>, for HTTP POST requests
 * <li> <code>doPut</code>, for HTTP PUT requests
 * <li> <code>doDelete</code>, for HTTP DELETE requests
 * <li> <code>init</code> and <code>destroy</code>,
 * to manage resources that are held for the life of the servlet
 * <li> <code>getServletInfo</code>, which the servlet uses to
 * provide information about itself
 * </ul>
 *
 * <p>There's almost no reason to override the <code>service</code>
 * method. <code>service</code> handles standard HTTP
 * requests by dispatching them to the handler methods
 * for each HTTP request type (the <code>do</code><i>Method</i>
 * methods listed above).
 *
 * <p>Likewise, there's almost no reason to override the
 * <code>doOptions</code> and <code>doTrace</code> methods.
 *
 * <p>Servlets typically run on multithreaded servers,
 * so be aware that a servlet must handle concurrent
 * requests and be careful to synchronize access to shared resources.
 * Shared resources include in-memory data such as
 * instance or class variables and external objects
 * such as files, database connections, and network
 * connections.
 * See the
 * <a href="http://java.sun.com/Series/Tutorial/java/threads/multithreaded.html">
 * Java Tutorial on Multithreaded Programming</a> for more
 * information on handling multiple threads in a Java program.
 */

意思就是说,这是一个用来创建Http站点的类的抽象。一般要至少重写里边的一些方法,如doGet、doPost、doPut、doDelete等方法。
servlet通常运行在多线程服务器上,所以要知道servlet必须处理并发请求,并注意同步访问共享资源。共享资源包括内存中的数据(如实例或类变量)和外部对象(例如文件、数据库连接和网络连接)。

我们来看其中一个doGet方法,如果我们不重写它,那么就是这样:

    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String msg = lStrings.getString("http.method_get_not_supported");
        sendMethodNotAllowed(req, resp, msg);
    }

注释中有这么一段:

/**
     * <p>Overriding this method to support a GET request also
     * automatically supports an HTTP HEAD request. A HEAD
     * request is a GET request that returns no body in the
     * response, only the request header fields.
     * **/

翻译:
覆盖此方法以支持GET请求,也自动支持HTTP HEAD请求(HEAD就是一个GET请求,但是在响应报文中,只有请求报头字段)。

查看其子类:
在这里插入图片描述
随便看一个DefaultServlet:大多数web应用默认的资源服务servlet,用于提供静态资源,如HTML页面和图像。

    /**
     * Process a GET request for the specified resource.
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     *
     * @exception IOException if an input/output error occurs
     * @exception ServletException if a servlet-specified error occurs
     */
    @Override
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
        throws IOException, ServletException {

        // Serve the requested resource, including the data content
        serveResource(request, response, true, fileEncoding);

    }

那我们探究了Tomcat、Servlet,那么Servlet放入Tomcat的具体流程呢?

重新来看这两行代码:

Wrapper wrapper = tomcat.addServlet("/", "DemoServlet", new EasyServlet());
wrapper.addMapping("/embeddedTomcat");

addServlet的源码是这样的:

    /**
     * Add an existing Servlet to the context with no class.forName or
     * initialisation.
     * @param contextPath   Context to add Servlet to
     * @param servletName   Servlet name (used in mappings)
     * @param servlet       The Servlet to add
     * @return The wrapper for the servlet
     */
    public Wrapper addServlet(String contextPath,
            String servletName,
            Servlet servlet) {
        Container ctx = getHost().findChild(contextPath);
        return addServlet((Context) ctx, servletName, servlet);
    }
        /**
     * Static version of {@link #addServlet(String, String, Servlet)}.
     * @param ctx           Context to add Servlet to
     * @param servletName   Servlet name (used in mappings)
     * @param servlet       The Servlet to add
     * @return The wrapper for the servlet
     */
    public static Wrapper addServlet(Context ctx,
                                      String servletName,
                                      Servlet servlet) {
        // will do class for name and set init params
        Wrapper sw = new ExistingStandardWrapper(servlet);
        sw.setName(servletName);
        ctx.addChild(sw);

        return sw;
    }

tomcat在同一个Host下创建了子容器ctx ,而这个容器ctx将Servlet的包装ExistingStandardWrapper给add进来,也就是tomcat容器中放进了Servlet。就如上诉那些图讲诉的那样。
至于addMapping,就是添加映射:

    /**
     * Add a mapping associated with the Wrapper.
     *
     * @param mapping The new wrapper mapping
     */
    @Override
    public void addMapping(String mapping) {

        mappingsLock.writeLock().lock();
        try {
            mappings.add(mapping);
        } finally {
            mappingsLock.writeLock().unlock();
        }
        if(parent.getState().equals(LifecycleState.STARTED)) {
            fireContainerEvent(ADD_MAPPING_EVENT, mapping);
        }

    }

mappingsLock是ReentrantReadWriteLock(可重入读写锁)
mappings是ArrayList<String>

    /**
     * Notify all container event listeners that a particular event has
     * occurred for this Container.  The default implementation performs
     * this notification synchronously using the calling thread.
     *
     * @param type Event type
     * @param data Event data
     */
    @Override
    public void fireContainerEvent(String type, Object data) {

        if (listeners.size() < 1) {
            return;
        }

        ContainerEvent event = new ContainerEvent(this, type, data);
        // Note for each uses an iterator internally so this is safe
        for (ContainerListener listener : listeners) {
            listener.containerEvent(event);
        }
    }

通知所有容器事件监听器,说某个特定事件的在此容器发生。默认实现执行使用调用线程同步通知。

listeners是CopyOnWriteArrayList<ContainerListener>();
也就是写入时复制的ArrayList,存的是ContainerListener。

/**
 * Interface defining a listener for significant Container generated events.
 * Note that "container start" and "container stop" events are normally
 * LifecycleEvents, not ContainerEvents.
 *
 * @author Craig R. McClanahan
 */
public interface ContainerListener {

    /**
     * Acknowledge the occurrence of the specified event.
     *
     * @param event ContainerEvent that has occurred
     */
    public void containerEvent(ContainerEvent event);

}

所有的ContainerListener通过以下方法加进来:

    /**
     * Add a container event listener to this component.
     *
     * @param listener The listener to add
     */
    @Override
    public void addContainerListener(ContainerListener listener) {
        listeners.add(listener);
    }

所以说我们这些监听器从哪里加过来,又监听哪些东西呢?
我们从Tomcat.addServlet一路追踪下去,看到了这么一个包装器的类,这也是我们之前所用到的Wrapper的最终实现。

/**
     * Helper class for wrapping existing servlets. This disables servlet
     * lifecycle and normal reloading, but also reduces overhead and provide
     * more direct control over the servlet.
     */
    public static class ExistingStandardWrapper extends StandardWrapper {
        private final Servlet existing;

        @SuppressWarnings("deprecation")
        public ExistingStandardWrapper( Servlet existing ) {
            this.existing = existing;
            if (existing instanceof javax.servlet.SingleThreadModel) {
                singleThreadModel = true;
                instancePool = new Stack<>();
            }
            this.asyncSupported = hasAsync(existing);
        }

        private static boolean hasAsync(Servlet existing) {
            boolean result = false;
            Class<?> clazz = existing.getClass();
            WebServlet ws = clazz.getAnnotation(WebServlet.class);
            if (ws != null) {
                result = ws.asyncSupported();
            }
            return result;
        }

        @SuppressWarnings("deprecation")
        @Override
        public synchronized Servlet loadServlet() throws ServletException {
            if (singleThreadModel) {
                Servlet instance;
                try {
                    instance = existing.getClass().getConstructor().newInstance();
                } catch (ReflectiveOperationException e) {
                    throw new ServletException(e);
                }
                instance.init(facade);
                return instance;
            } else {
                if (!instanceInitialized) {
                    existing.init(facade);
                    instanceInitialized = true;
                }
                return existing;
            }
        }
        @Override
        public long getAvailable() {
            return 0;
        }
        @Override
        public boolean isUnavailable() {
            return false;
        }
        @Override
        public Servlet getServlet() {
            return existing;
        }
        @Override
        public String getServletClass() {
            return existing.getClass().getName();
        }
    }

首先,我们传给它我们之前创建的Servlet,然后它会判断是否是singleThreadModel(单线程模型),是的话把instancePool这个Stack<Servlet>,然后判断是否支持异步。
loadServlet(),看名字就是加载Servlet,用的是instance.init(facade);这行代码。
我们可以猜出,就是使用的设计模式中的Facade模式(包装器模式)。
facade是StandardWrapperFacade类对象,用来包装this,提供一些配置。
那么init方法呢?

    /**
     * Called by the servlet container to indicate to a servlet that the servlet
     * is being placed into service. See {@link Servlet#init}.
     * <p>
     * This implementation stores the {@link ServletConfig} object it receives
     * from the servlet container for later use. When overriding this form of
     * the method, call <code>super.init(config)</code>.
     *
     * @param config
     *            the <code>ServletConfig</code> object that contains
     *            configuration information for this servlet
     * @exception ServletException
     *                if an exception occurs that interrupts the servlet's
     *                normal operation
     * @see UnavailableException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
    /**
     * A convenience method which can be overridden so that there's no need to
     * call <code>super.init(config)</code>.
     * <p>
     * Instead of overriding {@link #init(ServletConfig)}, simply override this
     * method and it will be called by
     * <code>GenericServlet.init(ServletConfig config)</code>. The
     * <code>ServletConfig</code> object can still be retrieved via
     * {@link #getServletConfig}.
     *
     * @exception ServletException
     *                if an exception occurs that interrupts the servlet's
     *                normal operation
     */
    public void init() throws ServletException {
        // NOOP by default
    }

也就是说,这里并不提供Servlet的初始化(毕竟它也只是个抽象类)
那我们依旧来看DefaultServlet。其中init方法太多内容,大多数的任务就是将对象中的属性赋值。
我们选其中重要的一行分析:

        // Load the web resources
        resources = (WebResourceRoot) getServletContext().getAttribute(Globals.RESOURCES_ATTR);

就是将上下文中的org.apache.catalina.resources域中的相关部分提取到resources中。
那么这个resources什么时候被用到呢?
doGet方法,定位到serveResource方法,有这么几行:

protected void serveResource(HttpServletRequest request,
                                 HttpServletResponse response,
                                 boolean content,
                                 String inputEncoding) {
		// ...
        // Identify the requested resource path
        String path = getRelativePath(request, true);
        // ...
        WebResource resource = resources.getResource(path);
        // ...
        ostream = response.getOutputStream();
        // ...
		writer = response.getWriter();
		// ...
		copy(...)

资源只有接口类型,不知道使用哪个实现类的方法,就用instanceOf来识别,以调用同样接口的不同解决方法。
很粗略,细节不继续探究。
我们回想一下,一开始的Context context = tomcat.addContext(host, “/”, classpath),或许也变成了后来DefaultServlet的资源路径上下文(有待考究)。

@_@
既然来了,觉得不错的话,点个赞或者关注呗

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值