【问题】
Tomcat
是如何读取web.xml
并生成ServletConfig
对象的
【猜想】
这个没什么思路,要是按照常规的来,结合Tomcat
读取server.xml
的方式来看,应该同样是是通过Degister
通过规则配置生成相应的属性,然后再生成每个Context
和Servlet
的时候分别初始化即可。
【Tomcat】
答案是和猜想大概差不多,不过中间还设计到其他规则逻辑。
首先需要知道的是,部署Tomcat
服务分为静态部署和动态部署,这里我先只看静态部署:
部署Tomcat
可以配置上下文描述文件,他们通常位于:
$CATALINA_BASE/conf/[enginename]/[hostname]/[webappname].xml
-
$CATALINA_BASE/webapps/[webappname]/META-INF/context.xml
还有一种方式便是我们前面所说的在
server.xml
配置Host
的子节点。但是
Tomcat
官网并不推荐在server.xml
中进行配置,而是在/conf/context.xml
中进行独立的配置。因为server.xml
是不可动态重加载的资源,服务器一旦启动了以后,要修改这个文件,就得重启服务器才能重新加载。而context.xml
文件则不然,Tomcat
服务器会定时去扫描这个文件。一旦发现文件被修改(时间戳改变了),就会自动重新加载这个文件,而不需要重启服务器。
配置样例:
<Context path="/kaka" docBase="kaka" debug="0" reloadbale="true" privileged="true">
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>WEB-INF/kaka.xml</WatchedResource> 监控资源文件,如果web.xml || kaka.xml改变了,则自动重新加载改应用。
<Resource name="jdbc/testSiteds" 表示指定的jndi名称
auth="Container" 表示认证方式,一般为Container
type="javax.sql.DataSource"
maxActive="100" 连接池支持的最大连接数
maxIdle="30" 连接池中最多可空闲maxIdle个连接
maxWait="10000" 连接池中连接用完时,新的请求等待时间,毫秒
username="root" 表示数据库用户名
password="root" 表示数据库用户的密码
driverClassName="com.mysql.jdbc.Driver" 表示JDBC DRIVER
url="jdbc:mysql://localhost:3306/testSite" /> 表示数据库URL地址
</Context>
参考文章:https://blog.csdn.net/nlznlz/article/details/77623379
而部署Tomcat Web
服务具体步骤如下:
- 先部署上下文描述符文件。
- 然后再对没被任何上下文描述符文件引用过的 Web 应用进行部署。 如果在
appBase
中已存在与这种应用有关的.war
文件,而且要比应用文件更新,那么就会将应用的文件夹清除,转而从.war
文件中部署 Web 应用。 - 部署
.war
文件。
因此,Tomcat
也是大概按照这个步骤来进行部署的。
首先我们可以大概看看org.apache.tomcat.util.descriptor.web.WebRuleSet
中关于web.xml
的规则:
digester.addRule(fullPrefix + "/servlet",
new ServletDefCreateRule());
digester.addSetNext(fullPrefix + "/servlet",
"addServlet",
"org.apache.tomcat.util.descriptor.web.ServletDef");
digester.addCallMethod(fullPrefix + "/servlet/init-param",
"addInitParameter", 2);
digester.addCallParam(fullPrefix + "/servlet/init-param/param-name",
0);
digester.addCallParam(fullPrefix + "/servlet/init-param/param-value",
1);
比较多,这里就不多完全粘贴了,可以看到有许多我们熟悉的节点,比如init-param
等等
通过这种规则描述,Tomcat
就能获取到xml
文件的内容,从而生成对应的WebXml
对象。
接下来再看部署:
ConextConfig##configureContext()
private void configureContext(WebXml webxml) {
context.setPublicId(webxml.getPublicId());
// Everything else in order
context.setEffectiveMajorVersion(webxml.getMajorVersion());
context.setEffectiveMinorVersion(webxml.getMinorVersion());
//初始化Context Params
for (Entry<String, String> entry : webxml.getContextParams().entrySet()) {
context.addParameter(entry.getKey(), entry.getValue());
}
context.setDenyUncoveredHttpMethods(
webxml.getDenyUncoveredHttpMethods());
context.setDisplayName(webxml.getDisplayName());
context.setDistributable(webxml.isDistributable());
for (ContextLocalEjb ejbLocalRef : webxml.getEjbLocalRefs().values()) {
context.getNamingResources().addLocalEjb(ejbLocalRef);
}
for (ContextEjb ejbRef : webxml.getEjbRefs().values()) {
context.getNamingResources().addEjb(ejbRef);
}
for (ContextEnvironment environment : webxml.getEnvEntries().values()) {
context.getNamingResources().addEnvironment(environment);
}
//初始化错误页
for (ErrorPage errorPage : webxml.getErrorPages().values()) {
context.addErrorPage(errorPage);
}
//初始化过滤器
for (FilterDef filter : webxml.getFilters().values()) {
if (filter.getAsyncSupported() == null) {
filter.setAsyncSupported("false");
}
context.addFilterDef(filter);
}
//初始化过滤器映射路径
for (FilterMap filterMap : webxml.getFilterMappings()) {
context.addFilterMap(filterMap);
}
context.setJspConfigDescriptor(webxml.getJspConfigDescriptor());
for (String listener : webxml.getListeners()) {
context.addApplicationListener(listener);
}
//初始化字符编码
for (Entry<String, String> entry :
webxml.getLocaleEncodingMappings().entrySet()) {
context.addLocaleEncodingMappingParameter(entry.getKey(),
entry.getValue());
}
// Prevents IAE
if (webxml.getLoginConfig() != null) {
context.setLoginConfig(webxml.getLoginConfig());
}
for (MessageDestinationRef mdr :
webxml.getMessageDestinationRefs().values()) {
context.getNamingResources().addMessageDestinationRef(mdr);
}
// messageDestinations were ignored in Tomcat 6, so ignore here
context.setIgnoreAnnotations(webxml.isMetadataComplete());
for (Entry<String, String> entry :
webxml.getMimeMappings().entrySet()) {
context.addMimeMapping(entry.getKey(), entry.getValue());
}
context.setRequestCharacterEncoding(webxml.getRequestCharacterEncoding());
// Name is just used for ordering
for (ContextResourceEnvRef resource :
webxml.getResourceEnvRefs().values()) {
context.getNamingResources().addResourceEnvRef(resource);
}
for (ContextResource resource : webxml.getResourceRefs().values()) {
context.getNamingResources().addResource(resource);
}
context.setResponseCharacterEncoding(webxml.getResponseCharacterEncoding());
boolean allAuthenticatedUsersIsAppRole =
webxml.getSecurityRoles().contains(
SecurityConstraint.ROLE_ALL_AUTHENTICATED_USERS);
for (SecurityConstraint constraint : webxml.getSecurityConstraints()) {
if (allAuthenticatedUsersIsAppRole) {
constraint.treatAllAuthenticatedUsersAsApplicationRole();
}
context.addConstraint(constraint);
}
for (String role : webxml.getSecurityRoles()) {
context.addSecurityRole(role);
}
for (ContextService service : webxml.getServiceRefs().values()) {
context.getNamingResources().addService(service);
}
//初始化ServletWrapper
for (ServletDef servlet : webxml.getServlets().values()) {
Wrapper wrapper = context.createWrapper();
// Description is ignored
// Display name is ignored
// Icons are ignored
// jsp-file gets passed to the JSP Servlet as an init-param
//LoadOnStartUp属性
if (servlet.getLoadOnStartup() != null) {
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
if (servlet.getEnabled() != null) {
wrapper.setEnabled(servlet.getEnabled().booleanValue());
}
//Servlet 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 Class
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());
context.addChild(wrapper);
}
for (Entry<String, String> entry :
webxml.getServletMappings().entrySet()) {
context.addServletMappingDecoded(entry.getKey(), entry.getValue());
}
//初始化Session配置
SessionConfig sessionConfig = webxml.getSessionConfig();
if (sessionConfig != null) {
if (sessionConfig.getSessionTimeout() != null) {
context.setSessionTimeout(
sessionConfig.getSessionTimeout().intValue());
}
SessionCookieConfig scc =
context.getServletContext().getSessionCookieConfig();
scc.setName(sessionConfig.getCookieName());
scc.setDomain(sessionConfig.getCookieDomain());
scc.setPath(sessionConfig.getCookiePath());
scc.setComment(sessionConfig.getCookieComment());
if (sessionConfig.getCookieHttpOnly() != null) {
scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue());
}
if (sessionConfig.getCookieSecure() != null) {
scc.setSecure(sessionConfig.getCookieSecure().booleanValue());
}
if (sessionConfig.getCookieMaxAge() != null) {
scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue());
}
if (sessionConfig.getSessionTrackingModes().size() > 0) {
context.getServletContext().setSessionTrackingModes(
sessionConfig.getSessionTrackingModes());
}
}
//添加首页文件
for (String welcomeFile : webxml.getWelcomeFiles()) {
/*
* The following will result in a welcome file of "" so don't add
* that to the context
* <welcome-file-list>
* <welcome-file/>
* </welcome-file-list>
*/
if (welcomeFile != null && welcomeFile.length() > 0) {
context.addWelcomeFile(welcomeFile);
}
}
// Do this last as it depends on servlets
for (JspPropertyGroup jspPropertyGroup :
webxml.getJspPropertyGroups()) {
String jspServletName = context.findServletMapping("*.jsp");
if (jspServletName == null) {
jspServletName = "jsp";
}
if (context.findChild(jspServletName) != null) {
for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
context.addServletMappingDecoded(urlPattern, jspServletName, true);
}
} else {
if(log.isDebugEnabled()) {
for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
log.debug("Skipping " + urlPattern + " , no servlet " +
jspServletName);
}
}
}
}
for (Entry<String, String> entry :
webxml.getPostConstructMethods().entrySet()) {
context.addPostConstructMethod(entry.getKey(), entry.getValue());
}
for (Entry<String, String> entry :
webxml.getPreDestroyMethods().entrySet()) {
context.addPreDestroyMethod(entry.getKey(), entry.getValue());
}
}
这里就可以和上一篇文章
Tomcat
如何找到Url对应的Servlet
相连接起来,这里可以看到,在启动的时候,Tomcat
便会根据配置文件生成对应的Context
的Child
—–ServletWrapper
,但是具体什么时候真正的加载Servlet
,再根据参数来定。
而Servlet
如何获取这个初始化信息的呢?
当需要初始化Servlet
之前,会调用servlet.init(facade);
而facade
便是StandardWrapperFacade
,这是典型的外观模式。
在StanardWarapper
中,便包含一个属性:
protected final StandardWrapperFacade facade = new StandardWrapperFacade(this);
因此,当我们需要获取各个属性的时候,其实就是获取的StandardWrapper
的属性,例如:
@Override
public ServletContext getServletContext() {
if (context == null) {
context = config.getServletContext();
if (context instanceof ApplicationContext) {
context = ((ApplicationContext) context).getFacade();
}
}
return context;
}
@Override
public Enumeration<String> getInitParameterNames() {
parametersLock.readLock().lock();
try {
return Collections.enumeration(parameters.keySet());
} finally {
parametersLock.readLock().unlock();
}
}