Servlet在啟動時加載的tomcat源碼(原創)


tomcat 8.0.36

知識點:

  • 通過配置loadOnStartup可以設置Servlet是否在Tomcat啟動時加載,以及按值大小進行有序加載,其最小有效值為0,最大有效值為Integer.MAX_VALUE。
  • Jsp Servlet的類是org.apache.jasper.servlet.JspServlet。
  • Jsp Servlet是強制性啟動時加載,其loadOnStartup的默認值,或其值是失效值時,將使用最大有效值。
  • 通過配置Context或Host的failCtxIfServletStartFails屬性值,優先使用Context的,設置tomcat啟動時加載servlet時,是否忽略拋出的ServletException異常,如果不忽略,則tomcat啟動失敗,默認不忽略。
  • servlet有多線程模式,和單線程模式,默認是多線程模式,單線程模式需要實現SingleThreadModel接口。單線程模式下,開啟一個實例池,同一時間,一個實例只分配給一個線程占用。

 

解說源碼:

Tomcat在啟動時,就會調用loadOnStartup,傳入的參數是一個Container[]的數組,這個數組代表的是從我們的web.xml,web-fragment.xml,注解,以及動態添加等方式配置的關於Servlet等信息的數組。

ok設置為false,表示Tomcat啟動失敗,將無法運作。即loadOnStartup的返回值將影響Tomcat是否運作。

if (!loadOnStartup(findChildren())) {
    ok = false;
}

 

進入loadOnStartup方法后,這里首先定義了一個TreeMap,使用TreeMap的主要目的就是可以排序。

然后遍歷Servlet信息集,將遍歷中的Servlet信息轉化為實際類型Wrapper。

通過getLoadOnStartup方法得到Servlet的啟動序級,過濾掉啟動序級小於0的Servlet。

接下來,就把大於或等於0的啟動序級加入到TreeMap,映射的值是一個Wrapper表,即把Servlet分箱裝入到所屬啟動序級的箱子里面。

TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
for (int i = 0; i < children.length; i++) {
    Wrapper wrapper = (Wrapper) children[i];
    int loadOnStartup = wrapper.getLoadOnStartup();
    if (loadOnStartup < 0)
        continue;
    Integer key = Integer.valueOf(loadOnStartup);
    ArrayList<Wrapper> list = map.get(key);
    if (list == null) {
        list = new ArrayList<>();
        map.put(key, list);
    }
    list.add(wrapper);
}

 

實際上Wrapper只是一個接口,真正實現Wrapper方法的是StandardWrapper,而StandardWrapper還實現了servlet規范中的ServletConfig接口。

public class StandardWrapper extends ContainerBase 
implements ServletConfig, Wrapper

 

但是,最后傳遞給應用使用的ServletConfig並非StandardWrapper,而是StandardWrapperFacade,當然也是實現ServletConfig接口。

public final class StandardWrapperFacade
implements ServletConfig

 

正如名字上的意思,使用外觀模式再一次包裝StandardWrapper,然后調用其的同名方法。

protected final StandardWrapperFacade facade = new StandardWrapperFacade(this);

 

所以servlet的所有配置信息都存放在StandardWrapper,如成員變量loadOnStartup就是配置servlet中的一項,其默認值為-1。

protected int loadOnStartup = -1;

 

getLoadOnStartup方法主要獲取成員變量loadOnStartup值,但有特殊情況,成員變量isJspServlet是記錄配置的Servlet類是不是org.apache.jasper.servlet.JspServlet,即一個Servlet是不是Jsp Servlet就看它的類是不是這個類。

簡單的說,普通Servlet的啟動序級的默認值為-1,最小有效值為0,最大有效值為Integer.MAX_VALUE。Jsp Servlet的啟動序級的默認值為最大值,有效值與普通Servlet一致,並且其值失效時將使用最大值。

public int getLoadOnStartup() {
    if (isJspServlet && loadOnStartup < 0) {
        return Integer.MAX_VALUE;
    } else {
        return (this.loadOnStartup);
    }
}

 

回到原來的getLoadOnStartup方法里,map已經把需要啟動時加載的Servlet分裝到按啟動序級排列好的箱子里面,接下來就是先按啟動序級順序,再按加入順序遍歷servlet,並且執行其load方法。

load方法如果拋出ServletException異常,將通過getComputedFailCtxIfServletStartFails獲取的值來決定是否讓Tomcat啟動失敗。

而獲取的值是Context配置的failCtxIfServletStartFails屬性,如果該屬性未配置,則使用Host配置failCtxIfServletStartFails的屬性,其默認值為false。

for (ArrayList<Wrapper> list : map.values()) {
    for (Wrapper wrapper : list) {
        try {
            wrapper.load();
        } catch (ServletException e) {
            if (getComputedFailCtxIfServletStartFails()) {
                return false;
            }
        }
    }
}
return true;

 

進入load方法后,load方法是一個線程安全的方法,為什么需要線程安全?因為,servlet大多數情況下是不進行預加載,什么時候用到,就什么時候加載。換句話說,為了節省從來沒用到的servlet資源空間,所以等到有請求訪問到servlet時才加載。

所以這種后加載的方式,就有可能出現兩個請求,在同一時間訪問到同一個servlet時,這時候就需要線程安全了。那什么時候需要預加載,什么時候不需要預加載,這個問題可以思考到,為什么定義Jsp Servlet時,tomcat是強制性要預加載,這里不再往深敘述了。

成員變量instance就是配置的servlet類的實例,實例是需要加載與初始化,才能對請求做處理。

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

    if (!instanceInitialized) {
        initServlet(instance);
    }
}

 

來到loadServlet方法,在加載實例之前,這里要驗證servlet實例是一個單例還是多例。如果是單例,並且實例已經存在,那就返回該實例。如果是多例,不管實例存與不存在,都將再創建實例。

判斷是使用單例還是多例,由成員變量singleThreadModel的值來判斷,其含意就是如果同一個servlet實例,只允許在同一時間讓一個請求(線程)訪問,所以叫單線程模式。反之,同一個servlet實例,允許同一時間,讓多個請求(線程)訪問,就叫多線程模式。

顯然,單線程與多線程的區別在於,servlet的成員變量共享與不共享,設計的重點就得往這里思考。那變量singleThreadModel默認值為false,即servlet模認是多線程模式,其如何改變為單線程模式在后面。

if (!singleThreadModel && (instance != null))
    return instance;

 

接下來獲取實例管理器,然后通過servlet類加載並創建servlet,這個過實例化的過程,內容也是挺豐富多彩的,其中包括JNDI注入,POST構造方法調用等,這里就不往里面敘述了。

InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
Servlet servlet = (Servlet) instanceManager.newInstance(servletClass);

 

判斷servlet是否單線程模式,如果serlvet實現了SingleThreadModel接口,那么成員變量singleThreadModel標記為true,同時創建出實例池。

if (servlet instanceof SingleThreadModel) {
    if (instancePool == null) {
        instancePool = new Stack<>();
    }
    singleThreadModel = true;
}

 

如果servlet需要得到Wrapper對象實例,那么servlet實現ContainerServlet接口,並且在Context的配置里設置屬性privileged為true,默認為false。

if ((servlet instanceof ContainerServlet) 
    &&(Context) getParent()).getPrivileged()) {
    ((ContainerServlet) servlet).setWrapper(this);
}

 

來到initServlet方法,如果servlet是多線程模式,則實例只執行一次初始化。反之,如果servlet是單線程模式,每次創建的實例,都會執行一次初始化。

private synchronized void initServlet(Servlet servlet) throws ServletException {
    if (instanceInitialized && !singleThreadModel)
        return;
    servlet.init(facade);
    instanceInitialized = true;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM