Wrapper接口在Catalina中的标准实现StandardWrapper类的详细实现过程。
一共有四种容器:engine(引擎),host(主机),context(上下文)和wrapper(包装器)
一个上下文一般包括一个或者多个包装器,每一个包装器表示一个servlet
方法调用序列Sequence of Methods Invocation
对于每一个连接,连接器都会调用关联容器的invoke方法。接下来容器调用它的所有子容器的invoke方法
如果一个连接器跟一个StadardContext实例相关联,那么连接器会调用StandardContext实例的invoke方法,
该方法会调用所有它的子容器的invoke方法
过程
1,连接器创建请求和响应对象
2,连接器调用StandardContext的invoke方法
3,StandardContext的invoke方法必须调用该上下文容器的流水线的invoke方法,
所以StandardContext的流水线会调用StandardContextValve的invoke方法
4,StandardContextValve的invoke方法得到合适的包装器来对请求进行服务并调用包装器的invoke方法
5,StandardWrapper是包装器的标准实现,StandardWrapper对象的invoke方法调用流水线的invoke方法。
6,StandardWrapper流水线的基本阀门时StandardWrapperValve。因此StandardWrapperValve的invoke方法会被调用。
StandardWrapperValve的invoke方法会调用包装器的allocate方法获得一个servlet的实例。
7,当一个servlet需要被加载的时候,方法allocate调用方法load来加载一个servlet
8,方法load会调用servlet的init方法
我们主要关注的是一个servlet被调用的时候发生的细节。因此我们需要自习看StandardWrapper和StandarWrapperValve类
javax.servlet.SingleThreadModel
一个servlet可以实现javax.servlet.SingleThreadModel接口,实现此接口的一个servlet通俗称为SingleThreadModel(STM)的程序组件。
根据Servlet规范,实现此接口的目的是保证servlet一次只能有一个请求。
StandardWrapper
一个StandardWrapper对象的主要职责是:加载它表示的servlet并分配它的一个实例。该StandardWrapper不会调用servlet的service方法
这个任务留给StandardWrapperValve对象,在StandardWrapper实例的基本阀门管道。
StandardWrapperValve对象通过调用StandardWrapper的allocate方法获得Servlet实例。
在获得Servlet实例之后的StandardWrapperValve调用servlet的service方法
在servlet第一次被请求的时候,StandardWrapper加载servlet类。它是动态的加载servlet,所以需要知道servlet类的完全限定名称。
通过StandardWrapper类的setServletClass方法将servlet的类名传递给StandardWrapper。
必须考虑一个servlet是否实现了SingleThreadModel接口。 如果一个servlet没有实现SingleThreadModel接口,
StandardWrapper加载该servlet一次,对于以后的请求返回相同的实例即可。
对于一个STM servlet,情况就有所不同了。StandardWrapper必须保证不能同时有两个线程提交STM servlet的service方法。
Servlet instance = <get an instance of the servlet>;
if ((servlet implementing SingleThreadModel>) {
synchronized (instance) {
instance.service(request, response);
}
}else{
instance.service(request, response);
}
Allocating the Servlet
StandardWrapperValve的invoke方法调用了包装器的allocate方法来获得一个请求servlet的实例
因此StandardWrapper类必须实现该接口
public javax.servlet.Servlet allocate() throws ServletException;
由于要支持STM servlet,这使得该方法更复杂了一点。实际上,该方法有两部分组成,
一部分负责非STM servlet的工作,另一部分负责STM servlet。
第一部分的结构如下
if (!singleThreadModel) {
// returns a non-STM servlet instance
}
布尔变量singleThreadModel负责标志一个servlet是否是STM servlet。
它的初始值是false,loadServlet方法会检测加载的servlet是否是STM的,如果是则将它的值该为true
第二部分处理singleThreadModel为true的情况
synchronized (instancepool) {
// returns an instance of the servlet from the pool
}
对于非STM servlet,StandardWrapper定义一个java.servlet.Servlet类型的实例
private Servlet instance = null;
方法allocate检查该实例是否为null,如果是调用loadServlet方法来加载servlet。然后增加contAllocated整型并返回该实例。
if (!singleThreadModel) {
if (instance == null) {
synchronized (this) {
if (instance == null) {
try {
instance = loadServlet();
}catch (ServletException e) {
throw e;
}
}
}
}
if (!singleThreadModel) {
countAllocated++;
return (instance);
}
Loading the Servlet
StandardWrapper实现了Wrapper接口的load方法,load方法调用loadServlet方法来加载一个servlet类,
并调用该servlet的init方法,传递一个javax.servlet.ServletConfig实例
方法loadServlet首先检查StandardWrapper是否表示一个STM servlet。
如果不是并且该实例不是null(即以前已经加载过),直接返回该实例:
if (!singleThreadModel && (instance != null))
return instance;
如果该实例是null或者是一个STM servlet,继续该方法的其它部分
ServletConfig对象
StandardWrapper的loadServlet方法在加载了loaded方法之后调用的发送者的init方法
init方法传递一个javax.servlet.ServletConfig实例
StandardWrapper实现javax.servlet.ServletConfig接口和Wrapper接口。
ServletConfig接口有以下四个方法getServletContext, getServletName, getInitParameter, 和getInitParameterNames
getServletContext
public ServletContext getServletContext()
一个StandardWrapper实例必须是一个StandardContext容器的子容器。也就是说,StandardWrapper的父容器是StandardContext
StandardWrapper中方法getServletContext的实现
public ServletContext getServletContext() {
if (parent == null)
return (null);
else if (!(parent instanceof Context))
return (null);
else
return (((Context) parent).getServletContext());
}
现在你知道不能单独部署一个包装器来表示一个Servlet,包装器必须从属于一个上下文容器,
这样才能使用ServletConfig对象使用getServletContext方法获得一个ServletContext实例。
getServletName
该方法返回Servlet的名字
public java.lang.String getServletName()
getServletName方法在StandardWrapper类中实现
public String getServletName() {
return (getName());
}
它简单的调用StandardWrapper 的父类ContainerBase类的getName方法
在ContainerBase中如下实现
public String getName() {
return (name);
}
使用setName方法来设置name的值。回忆是如何调用StandardWrapper实例的setName方法来传递Servlet的name的
getInitParameter
该方法返回指定参数的值
public java.lang.String getInitParameter(java.lang.String name)
在StandardWrapper中,初始化参数被存放在一个名为parameters的HashMap中
private HashMap parameters = new HashMap();
StandardWrapper类的addInitParameter方法来填充parameters。传递参数的名字和值。
public void addInitParameter(String name, String value) {
synchronized (parameters) {
parameters.put(name, value);
}
fireContainerEvent("addInitParameter", name);
}
StandardWrapper对getInitParameter的实现:
public String getInitParameter(String name) {
return (findInitParameter(name));
}
getInitParameterNames
该方法返回所有初始化参数名字的枚举(Enumeration),
public java.util.Enumeration getInitParameterNames()
StandardWrapper类中getInitParameterNames的实现:
public Enumeration getInitParameterNames() {
synchronized (parameters) {
return (new Enumerator(parameters.keyset()));
}
}
Parent and Children
一个包装器表示一个独立Servlet的容器。这样,包装器就不能再有子容器,因此不可以调用它的addChild方法,
如果调用了会得到一个java.langIllegalStateException
一个包装器的父容器只能是一个上下文容器。如果传递的参数不是一个上下文容器,
它的setParent方法会抛出java.lang.IllegalArgumentException
public void setParent(Container container) {
if ((container != null) && !(container instanceof Context))
throw new IllegalArgumentException (
sm.getString("standardWrapper.notContext"));
super.setParent(container);
}
StandardWrapperFacade
StandardWrapper调用它价值的Servlet的init方法。该方法需要一个javax.servlet.ServletConfig的参数,
而StandardWrapper类自己就实现了ServletConfig接口。所以,理论上StandardWrapper可以将它自己作为参数传递给init方法。
但是StandardWrapper需要对Servlet隐藏他的大多数public方法。
为了实现这一点,StandardWraper将它自己包装的一个StandardWrapperFacade实例中
StandardWrapperValve
StandardWrapperValve是StandardWrapper实例上的基本阀门,主要工作是
1,提交Servlet的所有相关过滤器
2,调用发送者的service方法
要实现这些内容,下面是StandardWrapperValve在他的invoke方法要实现的
· 调用StandardWrapper的allocate的方法来获得一个servlet实例
· 调用它的private createFilterChain方法获得过滤链
· 调用过滤器链的doFilter方法。包括调用servlet的service方法
· 释放过滤器链
· 调用包装器的deallocate方法
· 如果Servlet无法使用了,调用包装器的unload方法
接下来是invoke方法
try {
if (!unavailable) {
servlet = wrapper.allocate();
}
}
.........
try {
response.sendAcknowledgement();
}
........
ApplicationFilterChain filterChain = createFilterChain(request, servlet);
try {
String jspFile = wrapper.getJspFile();
if (jspFile != null)
sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
else
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
if ((servlet != null) && (filterChain != null)) {
filterChain.doFilter(sreq, sres);
}
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
....
}
...........
try {
if (filterChain != null)
filterChain.release();
}
...
try {
if (servlet != null) {
wrapper.deallocate(servlet);
}
}
........
try {
if ((servlet != null) && (wrapper.getAvailable() == Long.MAX_VALUE)) {
wrapper.unload();
}
}
最重要的方法是createFilterChain方法并调用过滤器链的doFilter方法。
方法createFilterChain创建了一个ApplicationFilterChain实例,并将所有的过滤器添加到上面
FilterDef
org.apache.catalina.deploy.FilterDef表示一个过滤器定义,就像是在部署文件中定义一个过滤器元素那样
public final class FilterDef {
/** * The description of this filter. */
private String description = null;
private String displayName = null;
private String filterClass = null;
private String filterName = null;
private String largeIcon = null;
private String smallIcon = null;
private Map parameters = new HashMap();
public String toString() {
StringBuffer sb = new StringBuffer("FilterDef[");
sb.append("filterName=");
sb.append(this.filterName);
sb.append(", filterClass=");
sb.append(this.filterClass);
sb.append("]");
return (sb.toString());
}
public void addInitParameter(String name, String value) {
parameters.put(name, value);
}
}
FilterDef类中的每一个属性都代表一个可以在过滤器中出现的子元素。该类包括一个Map类型的变量表示一个包含所有初始参数的Map。
方法addInitParameer添加一个name/value对到该Map。
ApplicationFilterConfig
ApplicationFilterConfig实现了javax.servlet.FilterConfig接口。
ApplicationFilterConfig负责管理web应用程序启动的时候创建的过滤器实例。
传递一个org.apache.catalina.Context对象和FilterDef对象给ApplicationFilterConfig的构造来创建一个ApplicationFilterConfig实例:
public ApplicationFilterConfig(Context context, FilterDef filterDef)
throws ClassCastException, ClassNotFoundException, IllegalAccessException, InstantiationException, ServletException
Context对象表示一个一个web应用而FilterDef表示一个过滤器定义。
Filter getFilter()
throws ClassCastException, ClassNotFoundException, IllegalAccessException, InstantiationException, ServletException {
if (this.filter != null)
return (this.filter);
String filterClass = filterDef.getFilterClass();
ClassLoader classLoader = null;
if (filterClass.startsWith("org.apache.catalina."))
classLoader = this.getClass().getClassLoader();
else
classLoader = context.getLoader().getClassLoader();
ClassLoader oldCtxClassLoader = Thread.currentthread().getContextClassLoader();
Class clazz = classLoader.loadClass(filterClass);
this.filter = (Filter) clazz.newInstance();
filter.init(this);
return (this.filter);
}
ApplicationFilterChain
ApplicationFilterChain类是实现了javax.servlet.FilterChain接口。
StandardWrapperValve类中的invoke方法创建一个该类的实例并且调用它的doFilter方法。
ApplicationFilterChain类的doFilter的调用该链中第一个过滤器的doFilter方法。
Filter接口中doFilter方法的签名如下:
public void doFilter(ServletRaquest request, ServletResponse response, FilterChain chain)
在他的doFilter方法中,一个过滤器可以调用另一个过滤器链的doFilter来唤醒另一个过来出去
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// do something here
...
chain.doFilter(request, response);
}
在doFilter方法最好一行,它调用过滤链的doFilter方法。如果该过滤器是过滤链的最后一个过滤器,
它叫调用请求的Servlet的service方法。如果过滤器没有调用chain.doFilter,下一个过滤器就不会被调用。