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 小杭