Tomcat 源碼分析(三)-(二)-WEB應用中的Listener、Filter、Servlet 的加載和調用

Tomcat 源碼分析(三)-WEB加載原理(二)

三、WEB應用中的Listener、Filter、Servlet 的加載和調用

web配置的關聯

前文提到了,在Tomcat進行加載web.xml配置的時候,就是在org.apache.catalina.deploy.WebXml類的 configureContext 方法中,可以看到很多的setXX,和addXX的方法,把文件解析後表示Servlet、Listener、Filter 的配置信息都與表示web應用的Context對象關聯起來。

代碼在之前就有完整的,這裏選一點點看看:

configureContext 方法中 Servlet、Listener、Filter 的配置信息設置相關的調用代碼:

......   //設置 Filter 相關配置信息的。
for (FilterDef filter : filters.values()) {  
    if (filter.getAsyncSupported() == null) {
        filter.setAsyncSupported("false");
    }
    context.addFilterDef(filter);
}
for (FilterMap filterMap : filterMaps) { 
    context.addFilterMap(filterMap);
}
......//給應用添加 Listener 的。
 for (String listener : listeners) {  
            context.addApplicationListener(listener);
 }
...... //設置 Servlet 的相關配置信息的
for (ServletDef servlet : servlets.values()) { 
    Wrapper wrapper = context.createWrapper();
	......
......

這些是web.xml中的相關配置的設置,【servvlet3的話還包括註解中的配置,這些配置是在ContextConfig類的webConfig方法中會合並】。

需要注意的是,這裏配置的相關配置信息,僅僅是把配置信息保存到Context的相應實例變量中,而真正的在請求中響應的 Servlet、Listener、Filter 的實例並沒有構造出來。這裏的配置實例時其額封裝類的實例:StandardWrapper、ApplicationListener、FilterDef、FilterMap。

真正響應實例的構建

在StandardContext也就是web應用被Host構建的時候,會發布事件,最終會調用解析加載web.xml的方法。然後,這裏會把解析的配置封裝類關聯到StandardContext中去。

在配置好了之後,真正的處理實例的構建確實在別的地方:

org.apache.catalina.core.StandardContext類的 startInternal 方法中:

protected synchronized void startInternal() throws LifecycleException {

    // Add missing components as necessary  添加缺失的必要組件
  .........

    // Initialize character set mapper
    getCharsetMapper();

    // Post work directory
    postWorkDirectory();

    // Validate required extensions 驗證所需擴展
    boolean dependencyCheck = true;
    try {
        dependencyCheck = ExtensionValidator.validateApplication
            (getResources(), this);
    }
......

    if (!dependencyCheck) {
        // 如果依賴項檢查失敗,則不使應用程序可用
        ok = false;
    }
......
    // Standard container startup 標準容器啓動
    // Binding thread
    ClassLoader oldCCL = bindThread();

    try {
        if (ok) {
            // Start our subordinate components, if any
            Loader loader = getLoaderInternal();
            if ((loader != null) && (loader instanceof Lifecycle))
                ((Lifecycle) loader).start();
......
            Cluster cluster = getClusterInternal();
            if ((cluster != null) && (cluster instanceof Lifecycle))
                ((Lifecycle) cluster).start();
            Realm realm = getRealmInternal();
            if ((realm != null) && (realm instanceof Lifecycle))
                ((Lifecycle) realm).start();
            DirContext resources = getResourcesInternal();
            if ((resources != null) && (resources instanceof Lifecycle))
                ((Lifecycle) resources).start();

            // Notify our interested LifecycleListeners  
            // ************  發佈事件,這裏會觸發web.xml的解析   ***************
            fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

            // Start our child containers, if not already started 啓動子容器
            for (Container child : findChildren()) {
                if (!child.getState().isAvailable()) {
                    child.start();
                }
            }

            // Start the Valves in our pipeline (including the basic),啓動管道中的閥門
            if (pipeline instanceof Lifecycle) {
                ((Lifecycle) pipeline).start();
            }

            // Acquire clustered manager 獲取羣集管理器??
            Manager contextManager = null;
            Manager manager = getManagerInternal();
          ......
              contextManager = new StandardManager();
            ...
               manager = contextManager;
......

            // Configure default manager if none was specified
           ......


    // We put the resources into the servlet context
    if (ok)
        getServletContext().setAttribute
            (Globals.RESOURCES_ATTR, getResources());
   ......

    if (ok ) {
        if (getInstanceManager() == null) {
            javax.naming.Context context = null;
            if (isUseNaming() && getNamingContextListener() != null) {
                context = getNamingContextListener().getEnvContext();
            }
            Map<String, Map<String, String>> injectionMap = buildInjectionMap(
                    getIgnoreAnnotations() ? new NamingResources(): getNamingResources());
            //**********設置實例管理器爲 DefaultInstanceManager **********
            setInstanceManager(new DefaultInstanceManager(context,
                    injectionMap, this, this.getClass().getClassLoader()));
            getServletContext().setAttribute(
                    InstanceManager.class.getName(), getInstanceManager());
        }
    }

    try {
        // Create context attributes that will be required
        if (ok) {
            getServletContext().setAttribute(
                    JarScanner.class.getName(), getJarScanner());
        }

        // Set up the context init params 設置初始化參數
        mergeParameters();

        // Call ServletContainerInitializers
      ......

        // Configure and call application event listeners
        if (ok) {
            if (!listenerStart()) {  //*******就是這裏配置調用Listener*******
               ......
            }
        }
......

        // Configure and call application filters
        if (ok) {
            if (!filterStart()) { //*******就是這裏配置調用Filter*******
                log.error(sm.getString("standardContext.filterFail"));
                ok = false;
            }
        }

        // Load and initialize all "load on startup" servlets
        if (ok) {
            if (!loadOnStartup(findChildren())){  //*******就是這裏配置調用Servlet *******
                log.error(sm.getString("standardContext.servletFail"));
                ok = false;
            }
        }
        // Start ContainerBackgroundProcessor thread
        super.threadStart();
    } finally {
        // Unbinding thread
        unbindThread(oldCCL);
    }
.............................................................
}

這裏的代碼很長,做了刪減,反正只看得懂註釋╮(╯_╰)╭

這裏的初始化開始的方法中,打星號註釋的是要關注的代碼。這裏裏面:

  • 發佈了一個CONFIGURE_START_EVENT事件,就是前面所說的觸發web.xml解析的地方。
  • 然後之後設置了實例管理器爲 DefaultInstanceManager(這個類在後面談實例構造時會用到)。
  • 再後面調用了listenerStart ,filterStart ,loadOnStartup 方法,這裏即觸發 Listener、Filter、Servlet 真正對象的構造。

分析listenerStart 方法的-構造代碼

代碼還是很長的,刪減一下只看Listenner 對象構造相關的代碼:

public class StandardContext extends ContainerBase
        implements Context, NotificationEmitter {
/** 配置實例化的應用程序事件偵聽器集
 * Configure the set of instantiated application event listeners
 * for this Context. 
 */
public boolean listenerStart() {
.......
    ApplicationListener listeners[] = applicationListeners;
    Object results[] = new Object[listeners.length];
    boolean ok = true;
    for (int i = 0; i < results.length; i++) {
        ......
        try {
            ApplicationListener listener = listeners[i];
            results[i] = getInstanceManager().newInstance(
                    listener.getClassName());
            if (listener.isPluggabilityBlocked()) {
                noPluggabilityListeners.add(results[i]);
            }
        } catch (Throwable t) {
          ......
        }
    }
 ......
}

這裏從Context對象中取出實例變量applicationListeners,這個變量在 web.xml 解析的時候設置的。然後通過:

getInstanceManager().newInstance(listener.getClassName());

這裏getInstanceManager所得到的對象就是startInternal方法裏面設置的:DefaultInstanceManager對象。所以,這裏的操作就是DefaultInstanceManager 類的 newInstance 方法:

@Override
public Object newInstance(String className) throws IllegalAccessException,
InvocationTargetException, NamingException, InstantiationException,
ClassNotFoundException, IllegalArgumentException, NoSuchMethodException, SecurityException {
    Class<?> clazz = loadClassMaybePrivileged(className, classLoader);
    return newInstance(clazz.getDeclaredConstructor().newInstance(), clazz);
}

這裏就已經構建出來了,在web.xml中配置的Listener對象了。

分析filterStart 方法的-Filter 的構建

這裏的代碼和實例化Listener的其實挺像的。。

public boolean filterStart() {
    if (getLogger().isDebugEnabled())
        getLogger().debug("Starting filters");
    // Instantiate and record a FilterConfig for each defined filter
    boolean ok = true;
    synchronized (filterConfigs) {
        filterConfigs.clear();
        for (Entry<String, FilterDef> entry : filterDefs.entrySet()) {
            String name = entry.getKey();
            if (getLogger().isDebugEnabled())
                getLogger().debug(" Starting filter '" + name + "'");
            ApplicationFilterConfig filterConfig = null;
            try {
                filterConfig =
                    new ApplicationFilterConfig(this, entry.getValue());
                filterConfigs.put(name, filterConfig);
            } catch (Throwable t) {
                t = ExceptionUtils.unwrapInvocationTargetException(t);
                ExceptionUtils.handleThrowable(t);
                getLogger().error
                    (sm.getString("standardContext.filterStart", name), t);
                ok = false;
            }
        }
    }
    return (ok);
}

這段代碼比較簡單,就是取出web.xml解析的時候保存的filter配置信息的集合,進行處理。

這裏的看一下15行:new ApplicationFilterConfig(this, entry.getValue());這裏創建的新的實例。

public final class ApplicationFilterConfig implements FilterConfig, Serializable {
ApplicationFilterConfig(Context context, FilterDef filterDef)
        throws ...... {
    super();
    this.context = context;
    this.filterDef = filterDef;
    // Allocate a new filter instance if necessary
    if (filterDef.getFilter() == null) {
        getFilter();
    } else {
        this.filter = filterDef.getFilter();
        getInstanceManager().newInstance(filter);
        initFilter();
    }
}
 //這裏在默認情況下filterDef中是沒有Filter對象的,所以會調用getFilter()方法:
Filter getFilter() throws...... {
        // Return the existing filter instance, if any
        if (this.filter != null)
            return (this.filter);

        // Identify the class loader we will be using
        String filterClass = filterDef.getFilterClass();
        this.filter = (Filter) getInstanceManager().newInstance(filterClass);  //<<--看這裏
        initFilter();  //當然 這裏會按照Servlet規範進行初始化
        return (this.filter);
    }

這裏可以看到最後實例化的方法也是:getInstanceManager().newInstance

這裏每一個Filter就是一個ApplicationFilterConfig,內部有Filter的實例對象。

分析loadOnStartup方法-Servlet 的構建

???

/**
* 加載並初始化中標記爲“啓動時加載”的所有servlet
* Web應用程序部署描述符。
 */
public boolean loadOnStartup(Container children[]) {

    // Collect "load on startup" servlets that need to be initialized
    TreeMap<Integer, ArrayList<Wrapper>> map =
        new TreeMap<Integer, ArrayList<Wrapper>>();
    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<Wrapper>();
            map.put(key, list);
        }
        list.add(wrapper);
    }

    // Load the collected "load on startup" servlets
    for (ArrayList<Wrapper> list : map.values()) {
        for (Wrapper wrapper : list) {
            try {
                wrapper.load();
            } catch (ServletException e) {
                getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
                      getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
                if(getComputedFailCtxIfServletStartFails()) {
                    return false;
                }
            }
        }
    }
    return true;
}

這裏會對Servlet進行初始化實例操作代碼是:wrapper.load(); 【StandardWrapper.load()】

需要注意的是,這裏只針對配置了 load-on-startup 屬性的 Servlet。

public class StandardWrapper extends ContainerBase
    implements ServletConfig, Wrapper, NotificationEmitter {
@Override
public synchronized void load() throws ServletException {
    instance = loadServlet();

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

    if (isJspServlet) {
        StringBuilder oname =
            new StringBuilder(MBeanUtils.getDomain(getParent()));

        oname.append(":type=JspMonitor,name=");
        oname.append(getName());
        oname.append(getWebModuleKeyProperties());

        try {
            jspMonitorON = new ObjectName(oname.toString());
            Registry.getRegistry(null, null)
                .registerComponent(instance, jspMonitorON, null);
        } catch( Exception ex ) {...... }
    }
}
//在上面的代碼中,第5行,會調用下面的方法↓↓↓↓↓↓↓代碼太多了,都刪了 ╮(╯▽╰)╭  ↓↓↓↓↓↓↓↓↓
    public synchronized Servlet loadServlet() throws ServletException {
      ......
        Servlet servlet;
        try {
           ......  //要看的就是這下面的兩行實例化的代碼 ,跟Filter和Listener 差不多的
            InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
            try {
                servlet = (Servlet) instanceManager.newInstance(servletClass);
            } catch (ClassCastException e) {...... }
            ......
            initServlet(servlet);
         ......
        } finally {
           ......
        }
        return servlet;
    }

這裏構造Servlet對象,與Filter類似。看看就好知道了

需要注意的是:這裏的加載只是針對配置了 load-on-startup 屬性的 Servlet 而言,其它一般 Servlet 的加載和初始化會推遲到真正請求訪問 web 應用而第一次調用該 Servlet 時

請求時 相關Filter、Servlet的構建

在非配置load-on-startup 屬性的 Servlet 而言,是不會再系統加載的時候創建具體的處理實例對象,依舊還只是個配置記錄在Context中。真正的創建則是在第一次被請求的時候,纔會實例化【這也是爲什麼有時候系統第一次訪問會相對慢一點點了】

根據Tomcat一次請求的流程,可以知道請求會在容器Engine、Host、Context、Wrapper 各級組件中匹配,並且在他們的管道中流轉。最終是會適配到一個StandardWrapper 的基礎閥的-org.apache.catalina.core.StandardWrapperValve的 invoke 方法。

先在就來看看,請求匹配到最基礎的StandardWrapper 組件的管道中,之後是如何處理的。

看下StandardWrapperValve閥的invoke 方法:

final class StandardWrapperValve extends ValveBase {
@Override
public final void invoke(Request request, Response response)
    throws IOException, ServletException {

    // Initialize local variables we may need
    boolean unavailable = false;
    Throwable throwable = null;
    // This should be a Request attribute...
    long t1=System.currentTimeMillis();
    requestCount.incrementAndGet();
    StandardWrapper wrapper = (StandardWrapper) getContainer();
    Servlet servlet = null;
    Context context = (Context) wrapper.getParent();

    // Check for the application being marked unavailable 檢查是否爲不可用
    if (!context.getState().isAvailable()) {
      ......
    }

    // Check for the servlet being marked unavailable
    if (!unavailable && wrapper.isUnavailable()) {
        ...... unavailable = true;
    }

    // Allocate a servlet instance to process this request
    //*****分配一個servlet實例來處理這個請求 *****
    try {
        if (!unavailable) {
            servlet = wrapper.allocate();  //********這裏是要關注的*******
            // 在allocate 方法中可能會調用 loadServlet() 方法,就是前邊那個構建servlet的方法
        }
    } catch (UnavailableException e) {
        ......
    }

    // Identify if the request is Comet related now that the servlet has been allocated
    // 確定在分配servlet之後,請求是否與Comet相關
    boolean comet = false;
    if (servlet instanceof CometProcessor && Boolean.TRUE.equals(request.getAttribute(
            Globals.COMET_SUPPORTED_ATTR))) {
        comet = true;
        request.setComet(true);
    }
......
    // Create the filter chain for this request 
    ApplicationFilterFactory factory =
        ApplicationFilterFactory.getInstance();
     //這裏↓↓↓↓會構造一個過濾器鏈( filterChain )用於執行這一次請求所經過的相應 Filter
    ApplicationFilterChain filterChain =
        factory.createFilterChain(request, wrapper, servlet);

    // Reset comet flag value after creating the filter chain
    request.setComet(false);

    // Call the filter chain for this request
    // NOTE: This also calls the servlet's service() method
    try {
        if ((servlet != null) && (filterChain != null)) {
            // Swallow output if needed
            if (context.getSwallowOutput()) {
                try {
                    SystemLogHandler.startCapture();
                    if (request.isAsyncDispatching()) {
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else if (comet) {
                        filterChain.doFilterEvent(request.getEvent());
                        request.setComet(true);
                    } else {   
                        //********注意一下***********↓↓↓↓↓
                        filterChain.doFilter(request.getRequest(),
                                response.getResponse());
                    }
                } finally {.......}
            } else {
                if (request.isAsyncDispatching()) {
                    request.getAsyncContextInternal().doInternalDispatch();
                } else if (comet) {
                    request.setComet(true);
                    filterChain.doFilterEvent(request.getEvent());
                } else {
                    //********注意一下***********↓↓↓↓↓
                    filterChain.doFilter    
                        (request.getRequest(), response.getResponse());
                }
            }
        }
    } catch (ClientAbortException e) {......}

    // Release the filter chain (if any) for this request
    if (filterChain != null) {
        if (request.isComet()) {
            // If this is a Comet request, then the same chain will be used for the
            // processing of all subsequent events.
            filterChain.reuse();
        } else {
            filterChain.release();
        }
    }

    // Deallocate the allocated servlet instance 取消分配已分配的servlet實例
    try {
        if (servlet != null) {
            wrapper.deallocate(servlet);
        }
    } catch (Throwable e) {......}

    // If this servlet has been marked permanently unavailable,
    // unload it and release this instance
    try {
        if ((servlet != null) &&
            (wrapper.getAvailable() == Long.MAX_VALUE)) {
            wrapper.unload();
        }
    } catch (Throwable e) {......}
    long t2=System.currentTimeMillis();
    long time=t2-t1;
    processingTime += time;
    if( time > maxTime) maxTime=time;
    if( time < minTime) minTime=time;
}

在以上的代碼中,需要注意的是第30行:

 servlet = wrapper.allocate(); 

這個StandardWrapper .allocate()的方法,在方法中會調用instance = loadServlet();這個方法,就是前面構建Servlet所調用的方法。

然後,在之後會構建filterChain-過濾器鏈,用於執行一次請求所經過的相應Filter。接着會鏈式調用filterChain 的doFilter方法。 來看一下doFilter的具體實現:

//final class ApplicationFilterChain implements FilterChain, CometFilterChain {
public void doFilter(ServletRequest request, ServletResponse response)
    throws IOException, ServletException {

    if( Globals.IS_SECURITY_ENABLED ) {
        final ServletRequest req = request;
        final ServletResponse res = response;
        try {
            java.security.AccessController.doPrivileged(
                new java.security.PrivilegedExceptionAction<Void>() {
                    @Override
                    public Void run() 
                        throws ServletException, IOException {
                        internalDoFilter(req,res);  //********
                        return null;
                    }
                }
            );
        } catch( PrivilegedActionException pe) {......}
    } else {
        internalDoFilter(request,response);//********
    }
}
//這裏會調用到internalDoFilter方法  就在下面
private void internalDoFilter(ServletRequest request, 
                                  ServletResponse response)
        throws IOException, ServletException {

        // Call the next filter if there is one
        if (pos < n) {  //n表示:表示過濾器鏈中所有的過濾器 pos:表示當前要執行的過濾器
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = null;
            try {
                filter = filterConfig.getFilter();
                support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
                                          filter, request, response);
                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                            Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal = 
                        ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege
                        ("doFilter", filter, classType, args, principal);
                } else {  
                    filter.doFilter(request, response, this);  //***就是這個***
                }

                support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
                                          filter, request, response);
            } catch (IOException e) {......}
            return;
        }
		// 我們脫離了鏈的末端——  ****調用servlet實例****
        // We fell off the end of the chain -- call the servlet instance
        try {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(request);
                lastServicedResponse.set(response);
            }

            support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,
                                      servlet, request, response);
            if (request.isAsyncSupported()
                    && !support.getWrapper().isAsyncSupported()) {
                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                        Boolean.FALSE);
            }
            // Use potentially wrapped request from this point
            if ((request instanceof HttpServletRequest) &&
                (response instanceof HttpServletResponse)) {
                    
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal = 
                        ((HttpServletRequest) req).getUserPrincipal();
                    Object[] args = new Object[]{req, res};
                    SecurityUtil.doAsPrivilege("service",
                                               servlet,
                                               classTypeUsedInService, 
                                               args,
                                               principal);   
                } else {  
                    servlet.service(request, response); //這裏就執行具體方法了
                }
            } else {
                servlet.service(request, response);//這裏就執行具體方法了
            }
            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
                                      servlet, request, response);
        } catch (IOException e) {......} finally {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(null);
                lastServicedResponse.set(null);
            }
        }
    }

這裏的internalDoFilter方法,在52行的地方會執行:

 filter.doFilter(request, response, this);

並且在一般的過濾器的使用中,最後都會有這一句:

FilterChain.doFilter(request, response);

這裏就回到了filterChain 的 doFilter 方法,就形成了遞歸調用。

然後:filterChain 對象內部的 pos 是不斷加的,所以假如過濾器鏈中的各個 Filter 的 doFilter 方法都執行完之後,就會執行到調用servlet的service 方法的地方 【第60行第地方開始】

結束

到這裏,請求就在經過ListenerStart,Filter 調用到了具體的Servlet方法 ,就是web應用裏面的Controller層的業務了。

終於瞭解到請求走到了自己寫代碼的地方了 _


2019-05-13 小杭


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