AdSense

網頁

2020/9/13

Tomcat Servlet怎麼建構的 how servlet construct?

Tomcat原始碼中Servlet實例的建構過程如下。


Tomcat原始碼版本為8.5.x


簡單說Servlet是透過Java reflection(反射)建構成。

StandardWrapper.loadServlet()負責載入並初始化Servlet實例,
呼叫getParent()取得父容器(parent container)也就是StandardContext
然後呼叫StandardContext.getInstanceManager()取得DefaultInstanceManger
然後呼叫DefaultInstanceManger.newInstance(String className)以Class全名(fully qualified name)透過反射建構Servlet實例。如果Class名稱找不到會丟出ServletException

Servlet實例產生後接著調用initServlet()進行初始化,
initServlet()中呼叫Servlet.init(ServletConfig config)開始初始化。

StandardWrapper.loadServlet()在產生Servlet的實例之前會先判斷實例instance是否已存在,若已存在則回傳先前建構的實例,也就是說ServletContext中一個serlvet mapping的url 只會有一個Servlet,當有多個相同url的請求時所使用的Servet是同一個。

而Container會幫每個請求產生獨立的thread並交由Servlet.serivce()處理,這就是為什麼Servet中不應該有成員變數的原因,因為Servlet是非執行緒安全(non-thread-safe)且可能被多個執行緒/請求同時執行。

節錄原始碼如下。

StandardWrapper

package org.apache.catalina.core;
...
public class StandardWrapper extends ContainerBase
    implements ServletConfig, Wrapper, NotificationEmitter {
    ...
    public synchronized Servlet loadServlet() throws ServletException {

        // Nothing to do if we already have an instance or an instance pool
        if (!singleThreadModel && (instance != null)) // 若Servlet已存在則返回先前已建構的Servlet
            return instance;
        ...
        Servlet servlet; // Servlet變數
        try {
            ...
            InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
            try {
                servlet = (Servlet) instanceManager.newInstance(servletClass); // 取得Servlet的實例
            } catch (ClassCastException e) {
                ...
                throw new ServletException // 若class name找不到對應的類別最終拋出ServletException。
                    (sm.getString("standardWrapper.notServlet", servletClass), e);
            } catch (Throwable e) {
                ...
                throw new ServletException
                    (sm.getString("standardWrapper.instantiate", servletClass), e);
            }
            ...
            initServlet(servlet); // 初始化Servlet

        } finally {
            ...
        }
        return servlet;

    }
    ...
    private synchronized void initServlet(Servlet servlet)
            throws ServletException {
        ...
        // Call the initialization method of this servlet
        try {
            if( Globals.IS_SECURITY_ENABLED) {
                ...
            } else {
                servlet.init(facade); // 呼叫Servlet.init()初始化
            }
            ...
        } catch (UnavailableException f) {
            ...
        }
    }
}

DefaultInstanceManager

package org.apache.catalina.core;
...
public class DefaultInstanceManager implements InstanceManager {
    ...
    @Override
    public Object newInstance(String className) throws IllegalAccessException,
            InvocationTargetException, NamingException, InstantiationException,
            ClassNotFoundException, IllegalArgumentException, NoSuchMethodException, SecurityException {
        Class<?> clazz = loadClassMaybePrivileged(className, classLoader);
        return newInstance(clazz.getConstructor().newInstance(), clazz); // 反射建構Servlet的實例
    }
    ...
}

例如下面的DemoServletmapping的url為/demo,則每一個對/demo的請求所使用的Servlet都是同一個DemoServlet。例如第一個請求返回的count = 1,但第二個請求回應的count = 2

DemoServlet

package com.abc.demo;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DemoServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    
    private int count = 0; // 請求共用Servlet的成員變數。

    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        count++;
        
        response.getWriter().append("count = " + count);
    }

}


沒有留言:

AdSense