以前只知道继承HttpServlet,然后Servlet容器就会自动调用doGet/doPost方法来提供service服务, 却不知道为什么,知道HttpServlet有生命周期init==>service==>destroy,生命周期由Servlet容器管理,却不知道怎么管理的,闲来无事就选择Tomcat作为研究对象。
其实以我做Java十几年的经验, 看源码似乎已成习惯:
1. Servlet init.
其实并不是所有的Servlet都是Servlet容器初始化就加载的,而是Servlet被第一次调用的时候加载.
web 2.4 configration
<servlet>
<display-name>InitServlet</display-name>
<servlet-name>InitServlet</servlet-name>
<servlet-class>com.InitServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
以上代码是配置Servlet load on startup, 可能有些人会对这里的数字1感兴趣,我配置成2行不行.
org.apache.catalina.core.StandardContext, 在容器启动的时候会调用startInternal方法:
// Load and initialize all "load on startup" servlets
if (ok) {
if (!loadOnStartup(findChildren())){
log.error("Error loadOnStartup");
ok = false;
}
}
/**
* Load and initialize all servlets marked "load on startup" in the
* web application deployment descriptor.
*
* @param children Array of wrappers for all currently defined
* servlets (including those not declared load on startup)
*/
public boolean loadOnStartup(Container children[]) {
// Collect "load on startup" servlets that need to be initialized
TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
for (int i = 0; i < children.length; i++) {
Wrapper wrapper = (Wrapper) children[i];
int loadOnStartup = wrapper.getLoadOnStartup();
if (loadOnStartup < 0)
continue;
Integer key = Integer.valueOf(loadOnStartup);
ArrayList<Wrapper> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
list.add(wrapper);
}
// Load the collected "load on startup" servlets
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
wrapper.load();
} catch (ServletException e) {
getLogger().error(sm.getString("standardWrapper.loadException",
getName()), StandardWrapper.getRootCause(e));
// NOTE: load errors (including a servlet that throws
// UnavailableException from tht init() method) are NOT
// fatal to application startup, excepted if failDeploymentIfServletLoadedOnStartupFails is specified
if(getComputedFailCtxIfServletStartFails()) {
return false;
}
}
}
}
return true;
}
/**
* Load and initialize an instance of this servlet, if there is not already
* at least one initialized instance. This can be used, for example, to
* load servlets that are marked in the deployment descriptor to be loaded
* at server startup time.
*/
public synchronized Servlet loadServlet() throws ServletException {
if (unloading) {
throw new ServletException(
sm.getString("standardWrapper.unloading", getName()));
}
// Nothing to do if we already have an instance or an instance pool
if (!singleThreadModel && (instance != null))
return instance;
PrintStream out = System.out;
if (swallowOutput) {
SystemLogHandler.startCapture();
}
Servlet servlet;
try {
long t1=System.currentTimeMillis();
// Complain if no servlet class has been specified
if (servletClass == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.notClass", getName()));
}
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
unavailable(null);
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.notServlet", servletClass), e);
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
unavailable(null);
// Added extra log statement for Bugzilla 36630:
// http://issues.apache.org/bugzilla/show_bug.cgi?id=36630
if(log.isDebugEnabled()) {
log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
}
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.instantiate", servletClass), e);
}
if (multipartConfigElement == null) {
MultipartConfig annotation =
servlet.getClass().getAnnotation(MultipartConfig.class);
if (annotation != null) {
multipartConfigElement =
new MultipartConfigElement(annotation);
}
}
processServletSecurityAnnotation(servlet.getClass());
// Special handling for ContainerServlet instances
if ((servlet instanceof ContainerServlet) &&
(isContainerProvidedServlet(servletClass) ||
((Context) getParent()).getPrivileged() )) {
((ContainerServlet) servlet).setWrapper(this);
}
classLoadTime=(int) (System.currentTimeMillis() -t1);
if (servlet instanceof SingleThreadModel) {
if (instancePool == null) {
instancePool = new Stack<>();
}
singleThreadModel = true;
}
initServlet(servlet);
fireContainerEvent("load", this);
loadTime=System.currentTimeMillis() -t1;
} finally {
if (swallowOutput) {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
if (getServletContext() != null) {
getServletContext().log(log);
} else {
out.println(log);
}
}
}
}
return servlet;
}
private synchronized void initServlet(Servlet servlet)
throws ServletException {
if (instanceInitialized && !singleThreadModel) return;
// Call the initialization method of this servlet
try {
instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,
servlet);
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(facade);
}
instanceInitialized = true;
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) {
ExceptionUtils.handleThrowable(f);
getServletContext().log("StandardWrapper.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);
}
}
我想不需要我解释,大家也知道这个数字(1)的意义了吧,服务器内部也只是用treeMap来排序罢了,但是不管你配置了多少,只要是数字并且大于0,那么都回在服务器启动的时候初始化.
其实这种例子可是很多的哦,servlet继承struts1.x的时候就是这么配置的:
servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>3</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>3</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
struts中的ActionServlet必须在容器初始化的时候实例化,这样就能在Servlet提供service服务前执行.
2. Servlet service.
经过以上实例化的Servlet就能提供service服务了.
org.apache.catalina.core.StandardWrapperValve
servlet = null;
Context context = (Context) wrapper.getParent();
// Check for the application being marked unavailable
if (!context.getState().isAvailable()) {
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardContext.isUnavailable"));
unavailable = true;
}
// Check for the servlet being marked unavailable
if (!unavailable && wrapper.isUnavailable()) {
container.getLogger().info(sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
long available = wrapper.getAvailable();
if ((available > 0L) && (available < Long.MAX_VALUE)) {
response.setDateHeader("Retry-After", available);
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
} else if (available == Long.MAX_VALUE) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
sm.getString("standardWrapper.notFound",
wrapper.getName()));
}
unavailable = true;
}
// Allocate a servlet instance to process this request
try {
if (!unavailable) {
servlet = wrapper.allocate();
}
} catch (UnavailableException e) {
container.getLogger().error(
sm.getString("standardWrapper.allocateException",
wrapper.getName()), e);
long available = wrapper.getAvailable();
if ((available > 0L) && (available < Long.MAX_VALUE)) {
response.setDateHeader("Retry-After", available);
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
} else if (available == Long.MAX_VALUE) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
sm.getString("standardWrapper.notFound",
wrapper.getName()));
}
} catch (ServletException e) {
container.getLogger().error(sm.getString("standardWrapper.allocateException",
wrapper.getName()), StandardWrapper.getRootCause(e));
throwable = e;
exception(request, response, e);
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString("standardWrapper.allocateException",
wrapper.getName()), e);
throwable = e;
exception(request, response, e);
servlet = null;
}
// Identify if the request is Comet related now that the servlet has been allocated
boolean comet = false;
if (servlet instanceof CometProcessor && request.getAttribute(
Globals.COMET_SUPPORTED_ATTR) == Boolean.TRUE) {
comet = true;
request.setComet(true);
}
MessageBytes requestPathMB = request.getRequestPathMB();
DispatcherType dispatcherType = DispatcherType.REQUEST;
if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC;
request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType);
request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
requestPathMB);
// Create the filter chain for this request
ApplicationFilterFactory factory =
ApplicationFilterFactory.getInstance();
ApplicationFilterChain filterChain =
factory.createFilterChain(request, wrapper, servlet);
// Reset comet flag value after creating the filter chain
request.setComet(false);
// Call the filter chain for this request
// NOTE: This also calls the servlet's service() method
try {
if ((servlet != null) && (filterChain != null)) {
// Swallow output if needed
if (context.getSwallowOutput()) {
try {
SystemLogHandler.startCapture();
if (request.isAsyncDispatching()) {
((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch();
} else if (comet) {
filterChain.doFilterEvent(request.getEvent());
request.setComet(true);
} else {
filterChain.doFilter(request.getRequest(),
response.getResponse());
}
} finally {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
context.getLogger().info(log);
}
}
} else {
if (request.isAsyncDispatching()) {
((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch();
} else if (comet) {
request.setComet(true);
filterChain.doFilterEvent(request.getEvent());
} else {
filterChain.doFilter
(request.getRequest(), response.getResponse());
}
}
}
} catch (ClientAbortException e) {
throwable = e;
exception(request, response, e);
} catch (IOException e) {
container.getLogger().error(sm.getString(
"standardWrapper.serviceException", wrapper.getName(),
context.getName()), e);
throwable = e;
exception(request, response, e);
} catch (UnavailableException e) {
container.getLogger().error(sm.getString(
"standardWrapper.serviceException", wrapper.getName(),
context.getName()), e);
// throwable = e;
// exception(request, response, e);
wrapper.unavailable(e);
long available = wrapper.getAvailable();
if ((available > 0L) && (available < Long.MAX_VALUE)) {
response.setDateHeader("Retry-After", available);
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
} else if (available == Long.MAX_VALUE) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
sm.getString("standardWrapper.notFound",
wrapper.getName()));
}
// Do not save exception in 'throwable', because we
// do not want to do exception(request, response, e) processing
} catch (ServletException e) {
Throwable rootCause = StandardWrapper.getRootCause(e);
if (!(rootCause instanceof ClientAbortException)) {
container.getLogger().error(sm.getString(
"standardWrapper.serviceExceptionRoot",
wrapper.getName(), context.getName(), e.getMessage()),
rootCause);
}
throwable = e;
exception(request, response, e);
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString(
"standardWrapper.serviceException", wrapper.getName(),
context.getName()), e);
throwable = e;
exception(request, response, e);
}
// Release the filter chain (if any) for this request
if (filterChain != null) {
if (request.isComet()) {
// If this is a Comet request, then the same chain will be used for the
// processing of all subsequent events.
filterChain.reuse();
} else {
filterChain.release();
}
}
// Deallocate the allocated servlet instance
try {
if (servlet != null) {
wrapper.deallocate(servlet);
}
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(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) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString("standardWrapper.unloadException",
wrapper.getName()), e);
if (throwable == null) {
throwable = e;
exception(request, response, e);
}
}
long t2=System.currentTimeMillis();
long time=t2-t1;
processingTime += time;
if( time > maxTime) maxTime=time;
if( time < minTime) minTime=time;
}
如果Servlet没有在容器初始化的时候实例化,那么会执行初始化。使用完servlet之后把当前Servlet实例放回到对象池中.
/**
* Return this previously allocated servlet to the pool of available
* instances. If this servlet class does not implement SingleThreadModel,
* no action is actually required.
*
* @param servlet The servlet to be returned
*
* @exception ServletException if a deallocation error occurs
*/
@Override
public void deallocate(Servlet servlet) throws ServletException {
// If not SingleThreadModel, no action is required
if (!singleThreadModel) {
countAllocated.decrementAndGet();
return;
}
// Unlock and free this instance
synchronized (instancePool) {
countAllocated.decrementAndGet();
instancePool.push(servlet);
instancePool.notify();
}
}
3. Servlet destroy.
当容器停止,或Servlet到期时,容器会调用servlet.destroy().
/**
* Stop this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void stopInternal() throws LifecycleException {
setAvailable(Long.MAX_VALUE);
// Send j2ee.state.stopping notification
if (this.getObjectName() != null) {
Notification notification =
new Notification("j2ee.state.stopping", this.getObjectName(),
sequenceNumber++);
broadcaster.sendNotification(notification);
}
// Shut down our servlet instance (if it has been initialized)
try {
unload();
} catch (ServletException e) {
getServletContext().log(sm.getString
("standardWrapper.unloadException", getName()), e);
}
// Shut down this component
super.stopInternal();
// Send j2ee.state.stoppped notification
if (this.getObjectName() != null) {
Notification notification =
new Notification("j2ee.state.stopped", this.getObjectName(),
sequenceNumber++);
broadcaster.sendNotification(notification);
}
// Send j2ee.object.deleted notification
Notification notification =
new Notification("j2ee.object.deleted", this.getObjectName(),
sequenceNumber++);
broadcaster.sendNotification(notification);
}
/**
* Unload all initialized instances of this servlet, after calling the
* <code>destroy()</code> method for each instance. This can be used,
* for example, prior to shutting down the entire servlet engine, or
* prior to reloading all of the classes from the Loader associated with
* our Loader's repository.
*
* @exception ServletException if an exception is thrown by the
* destroy() method
*/
@Override
public synchronized void unload() throws ServletException {
// Nothing to do if we have never loaded the instance
if (!singleThreadModel && (instance == null))
return;
unloading = true;
// Loaf a while if the current instance is allocated
// (possibly more than once if non-STM)
if (countAllocated.get() > 0) {
int nRetries = 0;
long delay = unloadDelay / 20;
while ((nRetries < 21) && (countAllocated.get() > 0)) {
if ((nRetries % 10) == 0) {
log.info(sm.getString("standardWrapper.waiting",
countAllocated.toString(),
getName()));
}
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
// Ignore
}
nRetries++;
}
}
if (instanceInitialized) {
PrintStream out = System.out;
if (swallowOutput) {
SystemLogHandler.startCapture();
}
// Call the servlet destroy() method
try {
instanceSupport.fireInstanceEvent
(InstanceEvent.BEFORE_DESTROY_EVENT, instance);
if( Globals.IS_SECURITY_ENABLED) {
try {
SecurityUtil.doAsPrivilege("destroy",
instance);
} finally {
SecurityUtil.remove(instance);
}
} else {
instance.destroy();
}
instanceSupport.fireInstanceEvent
(InstanceEvent.AFTER_DESTROY_EVENT, instance);
// Annotation processing
if (!((Context) getParent()).getIgnoreAnnotations()) {
((StandardContext)getParent()).getInstanceManager().destroyInstance(instance);
}
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
instanceSupport.fireInstanceEvent
(InstanceEvent.AFTER_DESTROY_EVENT, instance, t);
instance = null;
instancePool = null;
nInstances = 0;
fireContainerEvent("unload", this);
unloading = false;
throw new ServletException
(sm.getString("standardWrapper.destroyException", getName()),
t);
} finally {
// Write captured output
if (swallowOutput) {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
if (getServletContext() != null) {
getServletContext().log(log);
} else {
out.println(log);
}
}
}
}
}
// Deregister the destroyed instance
instance = null;
if (isJspServlet && jspMonitorON != null ) {
Registry.getRegistry(null, null).unregisterComponent(jspMonitorON);
}
if (singleThreadModel && (instancePool != null)) {
try {
while (!instancePool.isEmpty()) {
Servlet s = instancePool.pop();
if (Globals.IS_SECURITY_ENABLED) {
try {
SecurityUtil.doAsPrivilege("destroy", s);
} finally {
SecurityUtil.remove(s);
}
} else {
s.destroy();
}
// Annotation processing
if (!((Context) getParent()).getIgnoreAnnotations()) {
((StandardContext)getParent()).getInstanceManager().destroyInstance(s);
}
}
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
instancePool = null;
nInstances = 0;
unloading = false;
fireContainerEvent("unload", this);
throw new ServletException
(sm.getString("standardWrapper.destroyException",
getName()), t);
}
instancePool = null;
nInstances = 0;
}
singleThreadModel = false;
unloading = false;
fireContainerEvent("unload", this);
}
这就是HttpServlet的生命周期,init方法可以放一些资源的初始化,destroy方法可以放一些资源的释放.