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。