我们已经知道,在tomcat中有四种类型的servlet容器,分别为Engine、Host、Context 和Wrapper,本文接下来对tomcat中Wrapper接口的标准实现进行说明。
对于每个引入的HTTP请求,连接器都会调用与其关联的servlet容器的invoke()方法;然后,servlet容器会调用所有子容器的invoke()方法
这里面的流程通常是servlet容器调用其管道对象的invoke()方法,其管道对象的invoke()方法最后调用其基础阀,管道对象的基础阀里面会调用子容器的invoke()方法;子容器的invoke()方法的调用序列与之相同。
在tomcat中,servlet类可以实现javax.servlet.SingleThreadModel接口,这样的servlet类也称为SingleThreadModel(STM)servlet类;根据Servlet规范,实现此接口的目的是保证servlet实例一次只处理一个请求。
StandardWrapper对象的主要任务是载入它所代表的servlet类,并进行实例化;不过StandardWrapper类并不调用servlet的service方法,该任务由StandardWrapperValve对象(StandardWrapper实例的管道对象中的基础阀)完成;StandardWrapperValve对象通过调用StandardWrapper实例的allocate()方法获取servlet实例,在获取实例后,StandardWrapperValve实例就会调用servlet实例的service()方法
对于STM 类型的servlet类与非STM 类型的servlet类,StandardWrapper实例的载入方式是不一样的;对于非STM 类型的servlet类,StandardWrapper实例只会载入一次,对于随后的请求都会返回servlet的同一个实例,它假设该servlet类的service()方法在多线程环境中是线程安全的。
而对于STM 类型的servlet类,StandardWrapper实例必须保证每一时刻只能有一个线程在执行STM servlet类的service()方法;StandardWrapper实例通过将STM 类型的servlet实例保存在一个java.util.Stack类型的栈中
public
Servlet allocate()
throws
ServletException {
if
(debug >= 1
)
log(
"Allocating an instance"
);
//
If we are currently unloading this servlet, throw an exception
if
(unloading)
throw
new
ServletException
(sm.getString(
"standardWrapper.unloading"
, getName()));
//
If not SingleThreadedModel, return the same instance every time
if
(!
singleThreadModel) {
//
Load and initialize our instance if necessary
if
(instance ==
null
) {
synchronized
(
this
) {
if
(instance ==
null
) {
try
{
instance
=
loadServlet();
}
catch
(ServletException e) {
throw
e;
}
catch
(Throwable e) {
throw
new
ServletException
(sm.getString(
"standardWrapper.allocate"
), e);
}
}
}
}
if
(!
singleThreadModel) {
if
(debug >= 2
)
log(
" Returning non-STM instance"
);
countAllocated
++
;
return
(instance);
}
}
synchronized
(instancePool) {
while
(countAllocated >=
nInstances) {
//
Allocate a new instance if possible, or else wait
if
(nInstances <
maxInstances) {
try
{
instancePool.push(loadServlet());
nInstances
++
;
}
catch
(ServletException e) {
throw
e;
}
catch
(Throwable e) {
throw
new
ServletException
(sm.getString(
"standardWrapper.allocate"
), e);
}
}
else
{
try
{
instancePool.wait();
}
catch
(InterruptedException e) {
;
}
}
}
if
(debug >= 2
)
log(
" Returning allocated STM instance"
);
countAllocated
++
;
return
(Servlet) instancePool.pop();
}
}
而在loadServlet()方法中,首先获取要加载的servlet的完整类名及类加载器,然后通过类加载器加载该servlet类,然后实例化该servlet类,最后调用该servlet实例的init()方法初始化(传入javax.servlet.ServletConfig对象)
public
synchronized
Servlet loadServlet()
throws
ServletException {
//
Nothing to do if we already have an instance or an instance pool
if
(!singleThreadModel && (instance !=
null
))
return
instance;
PrintStream out
=
System.out;
SystemLogHandler.startCapture();
Servlet servlet
=
null
;
try
{
//
If this "servlet" is really a JSP file, get the right class.
//
HOLD YOUR NOSE - this is a kludge that avoids having to do special
//
case Catalina-specific code in Jasper - it also requires that the
//
servlet path be replaced by the <jsp-file> element content in
//
order to be completely effective
String actualClass =
servletClass;
if
((actualClass ==
null
) && (jspFile !=
null
)) {
Wrapper jspWrapper
=
(Wrapper)
((Context) getParent()).findChild(Constants.JSP_SERVLET_NAME);
if
(jspWrapper !=
null
)
actualClass
=
jspWrapper.getServletClass();
}
//
Complain if no servlet class has been specified
if
(actualClass ==
null
) {
unavailable(
null
);
throw
new
ServletException
(sm.getString(
"standardWrapper.notClass"
, getName()));
}
//
Acquire an instance of the class loader to be used
Loader loader =
getLoader();
if
(loader ==
null
) {
unavailable(
null
);
throw
new
ServletException
(sm.getString(
"standardWrapper.missingLoader"
, getName()));
}
ClassLoader classLoader
=
loader.getClassLoader();
//
Special case class loader for a container provided servlet
if
(isContainerProvidedServlet(actualClass)) {
classLoader
=
this
.getClass().getClassLoader();
log(sm.getString
(
"standardWrapper.containerServlet"
, getName()));
}
//
Load the specified servlet class from the appropriate class loader
Class classClass =
null
;
try
{
if
(classLoader !=
null
) {
System.out.println(
"Using classLoader.loadClass"
);
classClass
=
classLoader.loadClass(actualClass);
}
else
{
System.out.println(
"Using forName"
);
classClass
=
Class.forName(actualClass);
}
}
catch
(ClassNotFoundException e) {
unavailable(
null
);
throw
new
ServletException
(sm.getString(
"standardWrapper.missingClass"
, actualClass),
e);
}
if
(classClass ==
null
) {
unavailable(
null
);
throw
new
ServletException
(sm.getString(
"standardWrapper.missingClass"
, actualClass));
}
//
Instantiate and initialize an instance of the servlet class itself
try
{
servlet
=
(Servlet) classClass.newInstance();
}
catch
(ClassCastException e) {
unavailable(
null
);
//
Restore the context ClassLoader
throw
new
ServletException
(sm.getString(
"standardWrapper.notServlet"
, actualClass), e);
}
catch
(Throwable e) {
unavailable(
null
);
//
Restore the context ClassLoader
throw
new
ServletException
(sm.getString(
"standardWrapper.instantiate"
, actualClass), e);
}
//
Check if loading the servlet in this web application should be
//
allowed
if
(!
isServletAllowed(servlet)) {
throw
new
SecurityException
(sm.getString(
"standardWrapper.privilegedServlet"
,
actualClass));
}
//
Special handling for ContainerServlet instances
if
((servlet
instanceof
ContainerServlet) &&
isContainerProvidedServlet(actualClass)) {
System.out.println(
"calling setWrapper"
);
((ContainerServlet) servlet).setWrapper(
this
);
System.out.println(
"after calling setWrapper"
);
}
//
Call the initialization method of this servlet
try
{
instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,
servlet);
servlet.init(facade);
//
Invoke jspInit on JSP pages
if
((loadOnStartup > 0) && (jspFile !=
null
)) {
//
Invoking jspInit
HttpRequestBase req =
new
HttpRequestBase();
HttpResponseBase res
=
new
HttpResponseBase();
req.setServletPath(jspFile);
req.setQueryString(
"jsp_precompile=true"
);
servlet.service(req, res);
}
instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
servlet);
}
catch
(UnavailableException f) {
instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
servlet, f);
unavailable(f);
throw
f;
}
catch
(ServletException f) {
instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
servlet, f);
//
If the servlet wanted to be unavailable it would have
//
said so, so do not call unavailable(null).
throw
f;
}
catch
(Throwable f) {
instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
servlet, 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);
}
//
Register our newly initialized instance
singleThreadModel = servlet
instanceof
SingleThreadModel;
if
(singleThreadModel) {
if
(instancePool ==
null
)
instancePool
=
new
Stack();
}
fireContainerEvent(
"load",
this
);
}
finally
{
String log
=
SystemLogHandler.stopCapture();
if
(log !=
null
&& log.length() > 0
) {
if
(getServletContext() !=
null
) {
getServletContext().log(log);
}
else
{
out.println(log);
}
}
}
return
servlet;
}
StandardWrapper类的loadServlet()方法在载入servlet类后,会调用该servlet实例的init()方法,该方法需要传入一个javax.servlet.ServletConfig实例作为参数;那么, StandardWrapper对象是如何获取ServletConfig对象的呢
答案就在StandardWrapper类本身,该类不仅实现了Wrapper接口,还实现了javax.servlet.ServletConfig接口,下面是相关方法实现
/**
* Return the initialization parameter value for the specified name,
* if any; otherwise return <code>null</code>.
*
*
@param
name Name of the initialization parameter to retrieve
*/
public
String getInitParameter(String name) {
return
(findInitParameter(name));
}
/**
* Return the set of initialization parameter names defined for this
* servlet. If none are defined, an empty Enumeration is returned.
*/
public
Enumeration getInitParameterNames() {
synchronized
(parameters) {
return
(
new
Enumerator(parameters.keySet()));
}
}
/**
* Return the servlet context with which this servlet is associated.
*/
public
ServletContext getServletContext() {
if
(parent ==
null
)
return
(
null
);
else
if
(!(parent
instanceof
Context))
return
(
null
);
else
return
(((Context) parent).getServletContext());
}
/**
* Return the name of this servlet.
*/
public
String getServletName() {
return
(getName());
}
StandardWrapper实例在它的loadServlet()方法里面调用它所载入的servlet类的实例的init()方法,该方法需要一个javax.servlet.ServletConfig类型参数,理论上StandardWrapper对象可以将自身传入init()方法,不过为了类型安全,StandardWrapper类将自身实例包装成StandardWrapperFacade类的一个实例,然后再传给init()方法
public
final
class
StandardWrapperFacade
implements
ServletConfig {
/**
* Create a new facede around a StandardWrapper.
*/
public
StandardWrapperFacade(StandardWrapper config) {
super
();
this
.config =
(ServletConfig) config;
}
/**
* Wrapped config.
*/
private
ServletConfig config =
null
;
public
String getServletName() {
return
config.getServletName();
}
public
ServletContext getServletContext() {
ServletContext theContext
=
config.getServletContext();
if
((theContext !=
null
) &&
(theContext
instanceof
ApplicationContext))
theContext
=
((ApplicationContext) theContext).getFacade();
return
(theContext);
}
public
String getInitParameter(String name) {
return
config.getInitParameter(name);
}
public
Enumeration getInitParameterNames() {
return
config.getInitParameterNames();
}
}
StandardWrapperValve类是StandardWrapper实例中的基础阀,要完成两个操作
(1)执行与该servlet相关联的全部过滤器
(2)调用servlet实例的service()方法
public
void
invoke(Request request, Response response,
ValveContext valveContext)
throws
IOException, ServletException {
//
Initialize local variables we may need
boolean
unavailable =
false
;
Throwable throwable
=
null
;
StandardWrapper wrapper
=
(StandardWrapper) getContainer();
ServletRequest sreq
=
request.getRequest();
ServletResponse sres
=
response.getResponse();
Servlet servlet
=
null
;
HttpServletRequest hreq
=
null
;
if
(sreq
instanceof
HttpServletRequest)
hreq
=
(HttpServletRequest) sreq;
HttpServletResponse hres
=
null
;
if
(sres
instanceof
HttpServletResponse)
hres
=
(HttpServletResponse) sres;
//
Check for the application being marked unavailable
if
(!
((Context) wrapper.getParent()).getAvailable()) {
hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString(
"standardContext.isUnavailable"
));
unavailable
=
true
;
}
//
Check for the servlet being marked unavailable
if
(!unavailable &&
wrapper.isUnavailable()) {
log(sm.getString(
"standardWrapper.isUnavailable"
,
wrapper.getName()));
if
(hres ==
null
) {
;
//
NOTE - Not much we can do generically
}
else
{
long
available =
wrapper.getAvailable();
if
((available > 0L) && (available <
Long.MAX_VALUE))
hres.setDateHeader(
"Retry-After"
, available);
hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString(
"standardWrapper.isUnavailable"
,
wrapper.getName()));
}
unavailable
=
true
;
}
//
Allocate a servlet instance to process this request
try
{
if
(!
unavailable) {
servlet
=
wrapper.allocate();
}
}
catch
(ServletException e) {
log(sm.getString(
"standardWrapper.allocateException"
,
wrapper.getName()), e);
throwable
=
e;
exception(request, response, e);
servlet
=
null
;
}
catch
(Throwable e) {
log(sm.getString(
"standardWrapper.allocateException"
,
wrapper.getName()), e);
throwable
=
e;
exception(request, response, e);
servlet
=
null
;
}
//
Acknowlege the request
try
{
response.sendAcknowledgement();
}
catch
(IOException e) {
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
log(sm.getString(
"standardWrapper.acknowledgeException"
,
wrapper.getName()), e);
throwable
=
e;
exception(request, response, e);
}
catch
(Throwable e) {
log(sm.getString(
"standardWrapper.acknowledgeException"
,
wrapper.getName()), e);
throwable
=
e;
exception(request, response, e);
servlet
=
null
;
}
//
Create the filter chain for this request
ApplicationFilterChain filterChain =
createFilterChain(request, servlet);
//
Call the filter chain for this request
//
NOTE: This also calls the servlet's service() method
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);
}
catch
(IOException e) {
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
log(sm.getString(
"standardWrapper.serviceException"
,
wrapper.getName()), e);
throwable
=
e;
exception(request, response, e);
}
catch
(UnavailableException e) {
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
log(sm.getString(
"standardWrapper.serviceException"
,
wrapper.getName()), e);
//
throwable = e;
//
exception(request, response, e);
wrapper.unavailable(e);
long
available =
wrapper.getAvailable();
if
((available > 0L) && (available <
Long.MAX_VALUE))
hres.setDateHeader(
"Retry-After"
, available);
hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString(
"standardWrapper.isUnavailable"
,
wrapper.getName()));
//
Do not save exception in 'throwable', because we
//
do not want to do exception(request, response, e) processing
}
catch
(ServletException e) {
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
log(sm.getString(
"standardWrapper.serviceException"
,
wrapper.getName()), e);
throwable
=
e;
exception(request, response, e);
}
catch
(Throwable e) {
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
log(sm.getString(
"standardWrapper.serviceException"
,
wrapper.getName()), e);
throwable
=
e;
exception(request, response, e);
}
//
Release the filter chain (if any) for this request
try
{
if
(filterChain !=
null
)
filterChain.release();
}
catch
(Throwable e) {
log(sm.getString(
"standardWrapper.releaseFilters"
,
wrapper.getName()), e);
if
(throwable ==
null
) {
throwable
=
e;
exception(request, response, e);
}
}
//
Deallocate the allocated servlet instance
try
{
if
(servlet !=
null
) {
wrapper.deallocate(servlet);
}
}
catch
(Throwable e) {
log(sm.getString(
"standardWrapper.deallocateException"
,
wrapper.getName()), e);
if
(throwable ==
null
) {
throwable
=
e;
exception(request, response, e);
}
}
//
If this servlet has been marked permanently unavailable,
//
unload it and release this instance
try
{
if
((servlet !=
null
) &&
(wrapper.getAvailable()
==
Long.MAX_VALUE)) {
wrapper.unload();
}
}
catch
(Throwable e) {
log(sm.getString(
"standardWrapper.unloadException"
,
wrapper.getName()), e);
if
(throwable ==
null
) {
throwable
=
e;
exception(request, response, e);
}
}
}
上述创建过滤器链的过程本人后来作了补充: How Tomcat Works(十四)补充
---------------------------------------------------------------------------
本系列How Tomcat Works系本人原创
转载请注明出处 博客园 刺猬的温驯
本人邮箱: chenying998179 # 163.com ( #改为@ )

