(一)門面設計模式
1. 應用場景
在一個大的系統中有多個子系統,而每個子系統肯定不能將自己的內部數據過多的暴露給其他系統,否則將失去劃分各個子系統的意義;則此時每個子系統都會設計一個門面,將其他系統經常訪問或者感興趣的數據封裝在這個門面類中,通過這個門面類與其他系統進行數據交互。
2. 示意圖
3. 在Tomcat中的應用示例
如 在Tomcat中當瀏覽器發過來的TCP連接請求通過Request及Response對象進行和Container交流時,那Request及Response對象在一次請求中的變化情況:
另如 StandardWrapper 及 StandardWrapperFacade 都實現了ServletConfig接口,而StandardWrapperFacade作爲StandardWrapper的門面類,所以在Wrapper容器中裝載Servlet時,將StandardWrapperFacade作爲ServletConfig參數傳遞到Servlet保證了從StandardWrapper中拿到ServletConfig所規定的的參數,不會將無關數據也暴露到StandardWrapper傳入Servlet:
private synchronized void initServlet(Servlet servlet)throws ServletException {
if (instanceInitialized && !singleThreadModel) return;
// Call the initialization method of this servlet
try {
if( Globals.IS_SECURITY_ENABLED) {
boolean success = false;
try {
Object[] args = new Object[] { facade };
SecurityUtil.doAsPrivilege("init",servlet,classType,args);
success = true;
} finally {
if (!success) {
// destroy() will not be called, thus clear the reference now
SecurityUtil.remove(servlet);
}
}
} else {
//核心:調用Servlet的init方法,並將StandardWrapper對象的門面類對象StandardWrapperFacade作爲ServletConfig參數傳入
servlet.init(facade);
}
instanceInitialized = true;
} catch (UnavailableException f) {
unavailable(f);
throw f;
} catch (ServletException f) {
// If the servlet wanted to be unavailable it would have
// said so, so do not call unavailable(null).
throw f;
} catch (Throwable f) {
ExceptionUtils.handleThrowable(f);
getServletContext().log("StandardWrapper.Throwable", f );
// If the servlet wanted to be unavailable it would have
// said so, so do not call unavailable(null).
throw new ServletException(sm.getString("standardWrapper.initException",getName()),f);
}
}
另如 ServletContext,在Servlet中能拿到的實際對象也是ApplicationContextFacade對象,同樣保證ServletContext只能從容器中拿到它該拿的數據。
(二)觀察者設計模式
1. 應用場景
也稱發佈-訂閱模式,也就是事件監聽機制;A盯着B做事,如果B做得事A也感興趣,則B做的同時也會觸發讓A做某些操作;但同時A必須向B進行註冊,否則無法進行觸發關聯操作。
Subject抽象主題:管理註冊的所有觀察者A的引用,以及一些事件操作;
ConcreteSubject具體主題:實現Subject抽象主題中的那些事件接口,當發生變化時通知觀察者A;
Observer觀察者:監聽主題B發生變化時觸發的另外一系列事件操作.
2. 示意圖
3. 在Tomcat中的應用示例
LifecycleListener代表抽象觀察者,其中包含一個lifecycleEvent方法:定義了當主題變化時要執行的邏輯;
ServerLifecycleListener代表具體的觀察者實現;
Lifecycle代表抽象主題,定義了管理觀察者的方法及其他操作;
StandardSubject代表具體主題;
同時存在兩個觀察者輔助擴展類:LifecycleSupport、LifecycleEvent.
那麼主題是如何通知觀察者的吶???
那這裏不得不提到目前Servlet中提供的6種兩類事件的觀察者接口:
注意:ServletContextListener 在容器啓動之後就不能再添加新的。
(三)命令設計模式
1. 應用場景
命令模式主要作用:封裝命令,然後將 發出命令 及 執行命令 的責任分開;不同模塊可以對同一命令做出不同的解釋。
Client:將 發出命令 及 執行命令 等過程中使用到的角色進行組裝
Invoker:請求者,負責調用ConcreteCommand來執行請求
Command:命令接口,定義一個抽象命令
ConcreteCommand:具體命令,負責調用接受者的相關操作
Receiver:接受者,負責具體實施和執行一次請求的邏輯
2. 示意圖
代碼示例:
//抽象命令
public interface Command {
//這個方法是一個返回結果爲空的方法
//實際項目中,可以根據需求設計多個不同的方法
void execute();
}
//具體命令實現
public class ConcreteCommand implements Command{
private Receiver receiver; //命令的真正執行者
public ConcreteCommand(Receiver receiver) {
super();
this.receiver = receiver;
}
@Override
public void execute() {
//真正之前或後,執行相關的處理
receiver.action();
}
}
//調用者/發起者
public class Invoker {
public Command command; //也可以通過容器List<Command>容納很多命令對象,進行批處理,數據庫底層事務管理就是類似的構造
public Invoker(Command command) {
super();
this.command = command;
}
//業務方法,用於調用命令類的方法
public void call(){
command.execute();
}
}
//真正的命令執行者
public class Receiver {
public void action(){
System.out.println("Receiver.action()");
}
}
// 客戶端 各個角色組裝
public class Client {
public static void main(String[] args) {
Command c = new ConcreteCommand(new Receiver());
Invoker i = new Invoker(c);
i.call();
}
}
3. 在Tomcat中的應用示例
在以前老的版本中,Connector是利用HttpConnector、HttpProcessor、ContainerBase通過命令設計模式來調用Container的。
Connector:抽象請求者、HttpConnector:具體請求者
HttpProcessor:命令
Container:抽象接受者、ContainerBase:具體接受者
Server組件:客戶端
(四)責任鏈設計模式
1. 應用場景
形成一條由每個對象對其下家的引用而連接成的鏈,請求在這條鏈上傳遞,直到鏈上的 某個對象處理此請求 或 每個對象都可以處理請求,並傳給‘ 下家 ’,直到鏈上每個對象都處理完;此過程中不影響客戶端在鏈上增加任意處理節點。
2. 示例圖
抽象處理者 Handler:定義一個處理請求的接口
具體處理者 ConcreteHandler:具體處理請求的類,或者傳遞給‘ 下家 ’
詳情參考: https://blog.csdn.net/tuzhihai/article/details/75035865
3. 在Tomcat中的應用示例
(1)在Tomcat中的Container設計便利用該設計模式,從 Engine——Host——Context——Wrapper一直將請求正確地傳遞給最終處理請求的那個Servlet。
(2)javax.servlet.FilterConfig 及 javax.servlet.FilterChain 在Tomcat中的實現類分別是ApplicationFilterConfig和ApplicationFilterChain;在doFilter(ServletRequest,ServletResponse,FilterChain)方法中,FilterChain就代表當前整個請求鏈,所以通過調用FilterChain.doFilter可以將請求繼續傳遞下去;如果想攔截這個請求,可以不調用FilterChain.doFilter,則該請求將直接返回,這就是責任鏈設計模式。
Filter類的傳遞還是FilterChain對象,這個對象保存了到最終Servlet對象的所有Filter對象,這些對象都保存在ApplicationFilterChain對象的filters數組中。在FilterChain鏈上每執行一個Filter對象,數組的當前計數都會加1,直到計數等於數組的長度,當FilterChain上多有的Filter對象都執行完後,就會執行最終的Servlet,所以在ApplicationFilterChain對象中會持有Servlet對象的引用;在Tomcat的Wrapper容器中的Filter執行時序圖如下:
那麼一次請求中URL又是如何解析並匹配到最終的Filter的????
在web.xml中<servlet-mapping>和<filter-mapping>都有<url-pattern>配置項:Filter的url-pattern匹配是在創建
ApplicationFilterChain對象時進行的,它會把所有定義的Filter的url-pattern與當前URL匹配,如果匹配成功就將這個Filter
保存到ApplicationFilterChain的filters數組中,但是在匹配之前首先要利用StandardContext中的validateURLPattern方法
檢查url-pattern配置是否符合規則,如果檢查不成功,Context容器啓動會失敗:
private boolean validateURLPattern(String urlPattern) {
if (urlPattern == null)
return false;
if (urlPattern.indexOf('\n') >= 0 || urlPattern.indexOf('\r') >= 0) {
return false;
}
if (urlPattern.equals("")) {
return true;
}
if (urlPattern.startsWith("*.")) {
if (urlPattern.indexOf('/') < 0) {
checkUnusualURLPattern(urlPattern);
return true;
} else
return false;
}
if ( (urlPattern.startsWith("/")) &&
(urlPattern.indexOf("*.") < 0)) {
checkUnusualURLPattern(urlPattern);
return true;
} else
return false;
}
而匹配規則分爲:
精確匹配:如 /foo.html,只會匹配foo.html這個文件URL
路徑匹配:如 /foo/*,匹配以foo爲前綴的URL
後綴匹配:如 *.html,會匹配所有以.html爲後綴的URL
Servlet及Filter匹配順序均爲: 精確匹配——最長路徑匹配——後綴匹配、
區別:Servlet中一次請求只會成功匹配到一個Servlet;
Filter中只要通過ApplicationFilterFactory類中的matchFiltersURL方法匹配成功,在請求鏈上都會被調用:
private static boolean matchFiltersURL(String testPath, String requestPath) {
if (testPath == null)
return false;
// Case 1 - Exact Match
if (testPath.equals(requestPath))
return true;
// Case 2 - Path Match ("/.../*")
if (testPath.equals("/*"))
return true;
if (testPath.endsWith("/*")) {
if (testPath.regionMatches(0, requestPath, 0, testPath.length() - 2)) {
if (requestPath.length() == (testPath.length() - 2)) {
return true;
} else if ('/' == requestPath.charAt(testPath.length() - 2)) {
return true;
}
}
return false;
}
// Case 3 - Extension Match
if (testPath.startsWith("*.")) {
int slash = requestPath.lastIndexOf('/');
int period = requestPath.lastIndexOf('.');
if ((slash >= 0) && (period > slash) && (period != requestPath.length() - 1)
&& ((requestPath.length() - period) == (testPath.length() - 1))) {
return (testPath.regionMatches(2, requestPath, period + 1, testPath.length() - 2));
}
}
// Case 4 - "Default" Match
return false; // NOTE - Not relevant for selecting filters
}