【问题】
Servlet
规范中规定Servlet
生命周期只调用一次init()
和distory()
方法,每次请求来只调用service()
方法,那么Tomcat
是如何管理Servlet
生命周期的?
- 高并发请求中,如果保证只初始化一个
Servlet
? - 如和实现
loadOnStartup
参数的功能?
【猜想】
按照Servlet
的规范,一定需要缓存Servlet
对象,如果是我来做的话,应该会使用Map
缓存,每次请求判断一下Servlet
是否在Map
中,若不存在,就load() servlet
然后存进Map
。
在高并发的情况下,肯定得用线程安全的ConcurrentHashMap
,
对于loadOnStartup
参数,可以使用TreeSet
排序初始化
【Tomcat】
Tomcat
的实现并不是如此,在Tomcat
中,每个Servlet
都有一个对应的StandardWrapper
,而在前面我们分析过在Tomcat
启动的时候,会调用Server
,Connector
,以及各个容器的strat()
方法,其中在Context
的start()
方法中,就会首先加载所有的StandardWrapper
,这个StandardWrapper
中包含了Servlet
对象,只是当在调用对应的Servlet
方法后,StandardWrapper
会检查Servlet
是否被加载过,如果没有加载,则加载,如果加载过了,则直接使用。
前一篇关于分析[Tomcat如何解析
Url找到对应的
Servlet](<https://service-h580ssv7-1257812551.bj.apigw.tencentcs.com/?p=1065>)的文章中说过,
Serlvet的匹配其实算是一个树形结构,每个
Host会缓存多个
Context,每个
Context会缓存多个
StandardWrapper,因此,
StandardWrapper在系统启动的时候就已经被加载了,只是
Servlet是通过
LoadOnStartup`参数配置的。
对于Servlet
只初始化一个的问题,Tomcat
使用的是典型的单例类中的双重检验完成的。
对于loadOnStartup
,Tomcat
则使用的是TreeMap
实现,这一点我确实没相当,刚开始我说TreeSet
,是因为我直接将Servelt
重写Comparable
接口,使用loadOnStartup
属性作为对比的对象,但是这样实现远没有TreeMap
中将loadOnStartup
作为key
简洁。
整体说下流程:
- 加载
Context
在启动的时候,并不会自己去寻找Child
,也就是StandardWrapper
,而是在初始化之前,有一个ContextConfig
会监听StandardContext
的所有时间,当Context
触发了Start
事件的时候,ContextConfig
便会遍历CATLINA_HOME
下的所有目录,去读取所有的war
以及各个文件夹,对于war
包,会先解压War
包,然后将信息生成StandardWrapper
加入到StandardContext
中。
那么loadOnStartup
这个参数是怎么实现的呢?
当加入到Child
之后,StardandContext
的start()
方法会遍历所有的Child
,当发现loadOnStartup
大于0之后,会获取loadOnStartUp
配置文件的值,然后将它的值作为key
加入到TreeMap中,后续再遍历这个map
依次loadServlet
即可.
- 使用
Servlet
使用就非常简单了,当一个请求到来了,Servlet
的Mapper
会通过规则查找到对应的SerlvetWarpper
,而StandardWrapper
便会在真正调用Servlet
的时候判断Serlvet
是否已经加载过,如果没有,则先加载。
- 卸载
Serlvet
卸载就比较简单了,在系统关闭的时候,会触发StandardWrapper
,StandardWrapper
会调用Stop()
方法,从而调用SerlvetWarpper
的unload()
方法,而unload()
方法中就会调用Servlet
的destory()
方法
【代码】
这里代码比较散,而且还包含其他业务,因此便不全部贴出来.
- 加载1
ContextConfig##configureContext()
//读取webxml中servlet节点配置
for (ServletDef servlet : webxml.getServlets().values()) {
Wrapper wrapper = context.createWrapper();
//设置LoadOnStartup 属性
if (servlet.getLoadOnStartup() != null) {
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
//设置Servlet是否有效
if (servlet.getEnabled() != null) {
wrapper.setEnabled(servlet.getEnabled().booleanValue());
}
// 设置name
wrapper.setName(servlet.getServletName());
// 设置初始化参数
Map<String,String> params = servlet.getParameterMap();
for (Entry<String, String> entry : params.entrySet()) {
wrapper.addInitParameter(entry.getKey(), entry.getValue());
}
wrapper.setRunAs(servlet.getRunAs());
Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
for (SecurityRoleRef roleRef : roleRefs) {
wrapper.addSecurityReference(
roleRef.getName(), roleRef.getLink());
}
//设置servlet映射的类
wrapper.setServletClass(servlet.getServletClass());
MultipartDef multipartdef = servlet.getMultipartDef();
if (multipartdef != null) {
if (multipartdef.getMaxFileSize() != null &&
multipartdef.getMaxRequestSize()!= null &&
multipartdef.getFileSizeThreshold() != null) {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation(),
Long.parseLong(multipartdef.getMaxFileSize()),
Long.parseLong(multipartdef.getMaxRequestSize()),
Integer.parseInt(
multipartdef.getFileSizeThreshold())));
} else {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation()));
}
}
if (servlet.getAsyncSupported() != null) {
wrapper.setAsyncSupported(
servlet.getAsyncSupported().booleanValue());
}
wrapper.setOverridable(servlet.isOverridable());
//将配置好的servlet对象作为Child加入到context中
context.addChild(wrapper);
}
- 加载2
StandardContext##startInternal()
// Load and initialize all "load on startup" servlets
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
*/
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();
//如果loadOnStartup值小于0,则跳过
if (loadOnStartup < 0)
continue;
//否则将它对应的值作为key加入到TreeMap中,这样值越小,就越会先遍历到
//也就是loadOnStartup值越小,就越先加载
Integer key = Integer.valueOf(loadOnStartup);
ArrayList<Wrapper> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
list.add(wrapper);
}
//加载servlet
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
wrapper.load();
} catch (ServletException e) {
getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
// NOTE: load errors (including a servlet that throws
// UnavailableException from the init() method) are NOT
// fatal to application startup
// unless failCtxIfServletStartFails="true" is specified
if(getComputedFailCtxIfServletStartFails()) {
return false;
}
}
}
}
return true;
}
- 使用
这里使用就是典型的双重检验实现单例了
if (instance == null || !instanceInitialized) {
synchronized (this) {
if (instance == null) {
try {
instance = loadServlet();
newInstance = true;
}
if (!instanceInitialized) {
initServlet(instance);
}
}
}
这里再复习一下双重校验单例:
instance
必须为volatile
,为了防止JVM
优化而改变代码顺序因为
new
不是一个原子操作,new
一个对象分为以下三步:
- 创建对象,分配内存
- 执行构造方法初始化内存
将
instance
指向分配的内存地址,(此时Instance变不为空了)而上面的执行顺序由于
JVM
的指令重排的优化,可能改为了1->3->2
,在单线程看来是没有毛病的,但是多线程中很可能第二个线程在判断是否为null的时候,instance
仅仅被分配了内存,却没有执行构造方法,这个时候第二个线程拿到的可能就是一个不完整的instance
- 销毁
销毁就没什么好说了了
StandardWrapper##stop()
@Override
protected synchronized void stopInternal() throws LifecycleException {
unload();
}
@Override
public synchronized void unload() throws ServletException {
if (instanceInitialized) {
instance.destroy();
}
}