16,tomcat中StandardWrapper实现

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,下一个过滤器就不会被调用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值