5 tomcat容器-context

1、context容器是什麼?

直觀上面看,context容器就是tomcat的webapps下面的一個應用

java的web應用,你是不是一下就想到了servlet,filter,listener這三大神器。context容器就是管理它們的。

這篇博文就是了解context如何管理它們的

從功能是我們回顧一下

servlet:處理能匹配到的請求的類

fiter:針對指定請求必須經過的類

listener:發生特定時間通知到的類

2、Servlet管理

之前我們有講過Wrapper容器就是Servlet的一個包裝類。主要負責的就是實例化和調用service。

我們知道web.xml的Servlet配置有一個loadOnStartUp,如果這個參數配置大於1,就會在啓動tomcat的時候就實例化這個Servlet,這個工作就是在Context中管理完成的。

StandardContext中的StartInternal有這麼一段


if (ok) {
                if (!loadOnStartup(findChildren())){
                    log.error(sm.getString("standardContext.servletFail"));
                    ok = false;
                }
            }

public boolean loadOnStartup(Container children[]) {
     
        TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
        // children中保存的就是web.xml中配置的所有的Servlet對應的Wrapper容器
        // 這裏就是遍歷所有的容器,查看其loadOnStartUp
        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);
            }
            // 如果loadOnStartUp大於0,加入Map中
            list.add(wrapper);
        }

        // 遍歷map,也就是所有符合啓動就加載的Wrapper
        for (ArrayList<Wrapper> list : map.values()) {
            for (Wrapper wrapper : list) {
                try {
                    // 實例化Wrapper中的Servlet
                    wrapper.load();
                } 
            }
        }
        return true;

    }

Context在啓動的時候就會找到所有滿足啓動需要加載的Servlet,也就是Wrapper中配置的loadOnStartup參數大於0,實例化這個Servlet。

3 Filter管理

在Wrapper中涉及到Filter的業務操作,但是我並沒有選擇在那一篇中講解Filter,畢竟真正管理Filter的是Context容器。

我們看看Context容器是怎麼管理Filter

下面代碼是StandardContext啓動器中的一段

 if (ok) {
                if (!filterStart()) {
                    log.error(sm.getString("standardContext.filterFail"));
                    ok = false;
                }
            }


synchronized (filterConfigs) {
            filterConfigs.clear();
            // filterDefs在解析web.xml的時候生成
            for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
                String name = entry.getKey();
                
                // 創建一個FilterConfig
                    ApplicationFilterConfig filterConfig =
                            new ApplicationFilterConfig(this, entry.getValue());
                    filterConfigs.put(name, filterConfig);
            }
        }

這裏創建了FilterConfig,我們來看看filterConfig是些什麼內容

比如web.xml中配置了一個名爲filter1的過濾器,全限定名爲com.yanlink.filter.Filter1,匹配模式爲/hello,配置如下

<filter>
    <filter-name>filter1</filter-name>
    <filter-class>com.yanlink.filter.Filter1</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>filter1</filter-name>
    <url-pattern>/hello</url-pattern>
  </filter-mapping>

先看看filterDef保存的什麼

filterDef{

        filterClass=com.yanlink.filter.Filter1

        filterName=filter1

}

再看看filterConfig

filterConfig{

context:當前容器對象

filterDef:上面的對象

filter:對應的filter對象

}

我們還是來看看new ApplicationFilterConfig做了些什麼

ApplicationFilterConfig(Context context, FilterDef filterDef)
        throws ClassCastException, ClassNotFoundException,
               IllegalAccessException, InstantiationException,
               ServletException, InvocationTargetException, NamingException {

        super();

        // 保存容器上下文
        this.context = context;
        // 保存過濾器定義
        this.filterDef = filterDef;
        
        if (filterDef.getFilter() == null) {
        // 創建過濾器
            getFilter();
        } else {
            this.filter = filterDef.getFilter();
            getInstanceManager().newInstance(filter);
            initFilter();
        }
    }

Filter getFilter() throws ClassCastException, ClassNotFoundException,
        IllegalAccessException, InstantiationException, ServletException,
        InvocationTargetException, NamingException {

        // Return the existing filter instance, if any
        if (this.filter != null)
            return (this.filter);

        // 獲取類的全限定名
        String filterClass = filterDef.getFilterClass();
        // 實例化過濾器對象
        this.filter = (Filter) getInstanceManager().newInstance(filterClass);
        // 初始化過濾器對象也就是調用filter的init
        initFilter();
        // 
        return (this.filter);

    }

我們再來看看filter在Wrapper容器中的使用

public static ApplicationFilterChain createFilterChain(ServletRequest request,
            Wrapper wrapper, Servlet servlet) {

        // 必須需要一個servlet,沒有servlet的過濾器沒意義
        if (servlet == null)
            return null;

        // Create and initialize a filter chain object
            filterChain = new ApplicationFilterChain();
            req.setFilterChain(filterChain);
        } else {
            // Request dispatcher in use
            filterChain = new ApplicationFilterChain();
        }

        filterChain.setServlet(servlet);

        // 獲取wrapper對應的Context容器
        StandardContext context = (StandardContext) wrapper.getParent();
        // 上下文中獲取filterMap,這個是用於filter與請求的uri匹配的,找到合適的filter
        FilterMap filterMaps[] = context.findFilterMaps();
    
        String servletName = wrapper.getName();

        // 過濾器匹配
        for (int i = 0; i < filterMaps.length; i++) {
            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                continue;
            }
            if (!matchFiltersURL(filterMaps[i], requestPath))
                continue;
            // 在context中根據過濾器的名字找到對應的filterConfig
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMaps[i].getFilterName());
             // 找到filterConfig加入到過濾器鏈
            filterChain.addFilter(filterConfig);
        }

        // Return the completed filter chain
        return filterChain;
    }

context中創建的filterConfig在這裏就有用到,上面的代碼是由工廠方法創建了一個FilterChain,這個filterChain包含了一個servlet和一個filter數組。

servlet是必須有的,沒有目標的servlet的過濾器鏈是沒有意義的,所有如果servlet爲null,返回null

從context中找一個filterMap,字面意思這是個映射器,我們看看保存的是什麼

filterMap{

filterName=filter1,

urlPatterns:["/hello"]

}

從數據結構可以看出來,這個映射器是通過請求的uri來匹配對應的filter名稱

然後在context中找到之前配置的filterConfigs,根據filterName匹配到對應的filterConfig,加入filterChain中

接下來看看filterChain的調用

public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {

        internalDoFilter(request,response);
    }

private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

        // 
        if (pos < n) {
            // 從過這個pos定位執行到的filter
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
                Filter filter = filterConfig.getFilter();

                // 並且把自己的對象傳過去,在這裏爲什麼沒有for,關鍵點在於傳入的這個this
                    filter.doFilter(request, response, this);
                
            return;
        }

        // We fell off the end of the chain -- call the servlet instance
        try {
            // 過濾器執行完畢再執行service
                servlet.service(request, response);
            }
    }

看完這個大家是不是存在一些疑惑,沒有for循環,怎麼執行完所有的filter的。我們結合下應用使用filter的代碼

@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("filter1 dofilter");
        // 這裏通過chain再次調用了doFilter
        chain.doFilter(request, response);
    }

看到chain.doFilter沒,這個chain就是上面的this,也就是把自己這個對象傳過去,應用的時候需要靠開發人員加入這個chain.doFilter如果沒有則訪問不到servlet,宏觀層面看看,這個是不是和pipeline一樣還是一個責任鏈的模式,只是使用了另一種實現方式

4、Listener管理

這裏的listener我針對的是ServletContextListener ,我們來看看其接口

public interface ServletContextListener extends EventListener {
	/**
	 ** Notification that the web application initialization
	 ** process is starting.
	 ** All ServletContextListeners are notified of context
	 ** initialization before any filter or servlet in the web
	 ** application is initialized.
	 */

    public void contextInitialized ( ServletContextEvent sce );

	/**
	 ** Notification that the servlet context is about to be shut down.
	 ** All servlets and filters have been destroy()ed before any
	 ** ServletContextListeners are notified of context
	 ** destruction.
	 */
    public void contextDestroyed ( ServletContextEvent sce );
}

我們來看看關於監聽器的代碼

public boolean listenerStart() {
        // Instantiate the required listeners
        String listeners[] = findApplicationListeners();
        Object results[] = new Object[listeners.length];
        boolean ok = true;
        for (int i = 0; i < results.length; i++) {
                // listener是監聽器的全限定名
                String listener = listeners[i];
                // 實例化listener
                results[i] = getInstanceManager().newInstance(listener);
        }
    
        ArrayList<Object> eventListeners = new ArrayList<>();
        ArrayList<Object> lifecycleListeners = new ArrayList<>();
        for (int i = 0; i < results.length; i++) {
           // 加入lifecycleListeners
           lifecycleListeners.add(results[i]);
        }
      
        
        getServletContext();
        // 獲取當前關注容器狀態的監聽者
        Object instances[] = getApplicationLifecycleListeners();
        if (instances == null || instances.length == 0) {
            return ok;
        }
        // 創建事件,也就是把context對象帶過去,這裏是一個外觀類
        ServletContextEvent event = new ServletContextEvent(getServletContext());
      
            ServletContextListener listener = (ServletContextListener) instances[i];
            // 執行listener的容器初始化
            listener.contextInitialized(event);
        return (ok);

    }

這裏也就是一個典型的觀察者模式,容器中註冊 了所有的對context當前狀態感興趣的對象,context在啓動和銷燬的時候會給這些對象發送通知。

我們看看ServletContextListener接口中的參數ServletContextEvent ,在tomcat中創建這個事件的時候傳入了一個容器對象,這個容器對象並不是this,而是一個外觀類。這樣保護了context類,免得一些不安全的方法開放給了開發者。

5、基礎閥

再看看context與wrapper管道對接的基礎閥StandardContextValve

// Disallow any direct access to resources under WEB-INF or META-INF
     
        Wrapper wrapper = request.getWrapper();
        if (wrapper == null || wrapper.isUnavailable()) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        wrapper.getPipeline().getFirst().invoke(request, response);

這塊邏輯還是相對比較簡單的,找到Wrapper容器,調用Wrapper的管道的第一個閥的invoke,這裏找到Wrapper是在request中實現的,我們可以大概猜測一下,Wrapper容器中是有Name的這個name也就是,我們在請求的uri中去掉ContextPath部分,剩下的部分做wrapper匹配,找到匹配度最高的Wrapper。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章