栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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源码注释:



翻译:
用于嵌入/单元测试的最小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是这样启动的:

    
    public void start() throws LifecycleException {
        getServer();
        server.start();
    }
    
    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:

 

意思就是说,这是一个用来创建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);
    }

注释中有这么一段:

 

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

查看其子类:

随便看一个DefaultServlet:大多数web应用默认的资源服务servlet,用于提供静态资源,如HTML页面和图像。

    
    @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的源码是这样的:

    
    public Wrapper addServlet(String contextPath,
            String servletName,
            Servlet servlet) {
        Container ctx = getHost().findChild(contextPath);
        return addServlet((Context) ctx, servletName, 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,就是添加映射:

    
    @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

    
    @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();
也就是写入时复制的ArrayList,存的是ContainerListener。

public interface ContainerListener {

    
    public void containerEvent(ContainerEvent event);

}

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

    
    @Override
    public void addContainerListener(ContainerListener listener) {
        listeners.add(listener);
    }

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

    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,然后判断是否支持异步。
loadServlet(),看名字就是加载Servlet,用的是instance.init(facade);这行代码。
我们可以猜出,就是使用的设计模式中的Facade模式(包装器模式)。
facade是StandardWrapperFacade类对象,用来包装this,提供一些配置。
那么init方法呢?

    
    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
    
    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的资源路径上下文(有待考究)。

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/749126.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号