(一)门面设计模式
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
}