SpringBoot启动过程(四):SpringBoot内嵌Tomcat启动
之前在提到过createEmbeddedServletContainer创建了内嵌的Servlet容器,我⽤的是默认的Tomcat。
private void createEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = beddedServletContainer;
ServletContext localServletContext = getServletContext();
if (localContainer == null && localServletContext == null) {
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
.getEmbeddedServletContainer(getSelfInitializer());
}
else if (localServletContext != null) {
try {
getSelfInitializer().onStartup(localServletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
getEmbeddedServletContainerFactory⽅法中调⽤了ServerProperties,从ServerProperties的实例⽅法customize可以看出Springboot ⽀持三种内嵌容器的定制化配置:Tomcat、Jetty、Undertow。
这⾥直接说TomcatEmbeddedServletContainerFactory的getEmbeddedServletContainer⽅法了,原因在前⾯那篇⾥说过了。不过⾸先是getSelfInitializer⽅法先执⾏的:
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return new ServletContextInitializer() {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
selfInitialize(servletContext);
}
};
}
将初始化的ServletContextInitializer传给了getEmbeddedServletContainer⽅法。进⼊了getEmbeddedServletContainer⽅法直接就是实例化了⼀个Tomcat:
Tomcat tomcat = new Tomcat();
然后⽣成⼀个临时⽬录,并tomcat.setBaseDir,setBaseDir⽅法的注释说Tomcat需要⼀个⽬录⽤于临时⽂件并且它应该是第⼀个被调⽤的⽅法;如果⽅法没有被调⽤会使⽤默认的⼏个位置system properties - catalina.base, catalina.home - $PWD/tomcat.$PORT,另外/tmp 从安全⾓度来说不建议。
接着:
Connector connector = new Connector(this.protocol);
Connector实例创建好了之后Service().addConnector(connector),getService的getServer中new了⼀个StandardServer,StandardServer的初始化主要是创建了globalNamingResources(globalNamingResources主要⽤于管理明明上下⽂和JDNI上下⽂),并根据catalina.useNaming判断是否注册NamingContextListener给lifecycleListeners。创建Server之后initBaseDir,先读取catalina.home配置Property(Globals.CATALINA_BASE_PROP),如果没取到则使⽤之前⽣成的临时⽬录,这段直接看代码吧:
protected void initBaseDir() {
String catalinaHome = Property(Globals.CATALINA_HOME_PROP);
if (basedir == null) {
basedir = Property(Globals.CATALINA_BASE_PROP);
}
if (basedir == null) {
basedir = catalinaHome;
}
if (basedir == null) {
// Create a temp dir.
basedir = Property("user.dir") +
"/tomcat." + port;
}
File baseFile = new File(basedir);
baseFile.mkdirs();
try {
baseFile = CanonicalFile();
} catch (IOException e) {
baseFile = AbsoluteFile();
}
server.setCatalinaBase(baseFile);
System.setProperty(Globals.CATALINA_BASE_PROP, Path());
basedir = Path();
if (catalinaHome == null) {
server.setCatalinaHome(baseFile);
} else {
File homeFile = new File(catalinaHome);
homeFile.mkdirs();
try {
homeFile = CanonicalFile();
} catch (IOException e) {
homeFile = AbsoluteFile();
}
server.setCatalinaHome(homeFile);
}
System.setProperty(Globals.CATALINA_HOME_PROP,
}
然后⼜实例化了个StandardService,代码并没有什么特别的:
service = new StandardService();
service.setName("Tomcat");
server.addService( service )
server.addService( service )这⾥除了发布了⼀个PropertyChangeEvent事件,也没做什么特别的,最后返回这个server。addConnector的逻辑和上⾯addService没什么区别。然后是customizeConnector,这⾥设置了Connector的端⼝、编码等信息,并
将“bindOnInit”和对应值false写⼊了最开头说的静态代码块中的replacements集合,IntrospectionUtils.setProperty(protocolHandler, repl, value)通过反射的⽅法将protocolHandler实现对象
的setBindOnInit存在的情况下(拼字符串拼出来的)set为前⾯的false,这个⽅法⾥有⼤量的判断⽐如参数类型及setter的参数类型,⽐如返回值类型以及没到还会try a setProperty("name", "value")等,setProperty可以处理⽐如AbstractEndpoint中有个HashMap<String, Object> attributes的属性时会attributes.put(name, value)。如果是ssl还会执⾏customizeSsl⽅法,设置⼀些SSL⽤的属性⽐如协议⽐如秘钥还有可以⽤上秘钥仓库等。如果配置了压缩,这⾥还会给协议的相关setter设置值。tomcat.setConnector(connector)不解释。Host().setAutoDeploy(false),getHost⽅法中创建了StandardHost并设置host名(例如localhost),并getEngine().addChild( host );然后设置host的⾃动部署。Engine()),getEngine中如果engine 为null就初始化标准引擎,设置名字为Tomcat,设置Realm和service.setContainer(engine),不过这⾥engine已经在getHost初始化过了所以直接返回;configureEngine⽅法先设置引擎的后台进程延迟,并将引擎的Value对象注册给引擎的pipeline,此时尚⽆value对象实例。这⾥简单说明⼀下:value对象在Tomcat的各级容器中都有标准类型,并且各级容器都有⼀个pipeline,在请求处理过程中会从各级的第⼀个value 对象开始依次执⾏⼀遍,value⽤于加⼊到对应的各级容器的逻辑,默认有⼀个标注value实现,名字类似StandardHostValue。
Host(), initializers),initializers这⾥是AnnotationConfigEmbeddedWebApplicationContext,Context级的根;准备Context的过程主要设置Ba
se⽬录,new⼀个TomcatEmbeddedContext并在构造中判断了下loadOnStartup⽅法是否被重写;注册⼀个FixContextListener监听,这个监听⽤于设置context的配置状态以及是否加⼊登录验证的逻辑;context.setParentClassLoader;设置各种语⾔的编码映射,我这⾥是en和fr设置为UTF-8,此处可以使⽤配置⽂件org/apache/catalina/util/CharsetMapperDefault .properties;设置是否使⽤相对地址重定向useRelativeRedirects=false,此属性应该是Tomcat 8.0.30版本加上的;接着就是初始化webapploader,这⾥和完整版的Tomcat有点不⼀样,它⽤的是虚拟机的⽅式,会将加载类向上委托loader.setDelegate(true),context.setLoader(loader);之后就开始创建Wapper了,⾄此engine,host,context及wrapper四个层次的容器都创建完了:
private void addDefaultServlet(Context context) {
Wrapper defaultServlet = ateWrapper();
defaultServlet.setName("default");
defaultServlet.setServletClass("org.apache.catalina.servlets.DefaultServlet");
defaultServlet.addInitParameter("debug", "0");
defaultServlet.addInitParameter("listings", "false");
defaultServlet.setLoadOnStartup(1);
// Otherwise the default location of a Spring DispatcherServlet cannot be set
defaultServlet.setOverridable(true);
context.addChild(defaultServlet);
addServletMapping(context, "/", "default");
}
connector从socket接收的数据,解析成HttpServletRequest后就会经过这⼏层容器,有容器各⾃的Value对象链依次处理。
接着是是否注册jspServlet,jasperInitializer和StoreMergedWebXmlListener我这⾥是都没有的。接着的mergeInitializers⽅法:
protected final ServletContextInitializer[] mergeInitializers(
< initializers) {
List<ServletContextInitializer> mergedInitializers = new ArrayList<ServletContextInitializer>();
mergedInitializers.addAll(Arrays.asList(initializers));
mergedInitializers.addAll(this.initializers);
return mergedInitializers
.toArray(new ServletContextInitializer[mergedInitializers.size()]);
}
configureContext(context, initializersToUse)对context做了些设置⼯作,包括TomcatStarter(实例化并set给
context),LifecycleListener,contextValue,errorpage,Mime,session超时持久化等以及⼀些⾃定义⼯作:
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
// Should be true
((TomcatEmbeddedContext) context).setStarter(starter);
}
context.addServletContainerInitializer(starter, NO_CLASSES);
for (LifecycleListener lifecycleListener : tLifecycleListeners) {
context.addLifecycleListener(lifecycleListener);
}
for (Valve valve : tValves) {
}
for (ErrorPage errorPage : getErrorPages()) {
new TomcatErrorPage(errorPage).addToContext(context);
}
for (MimeMappings.Mapping mapping : getMimeMappings()) {
context.Extension(), MimeType());
}
Session如果不需要持久化会注册⼀个DisablePersistSessionListener。其他定制化操作是通过TomcatContextCustomizer的实现类实现的:
context配置完了作为child add给host,add时给context注册了个内存泄漏跟踪的监听MemoryLeakTrackingListener。postProcessContext(context)⽅法是空的,留给⼦类重写⽤的。
getEmbeddedServletContainer⽅法的最后⼀⾏:return getTomcatEmbeddedServletContainer(tomcat)。
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
}
TomcatEmbeddedServletContainer的构造函数:
public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
this.autoStart = autoStart;
initialize();
}
initialize的第⼀个⽅法addInstanceIdToEngineName对全局原⼦变量containerCounter+1,由于初始值是-1,所以addInstanceIdToEngineName⽅法内后续的获取引擎并设置名字的逻辑没有执⾏:
private void addInstanceIdToEngineName() {
int instanceId = containerCounter.incrementAndGet();
if (instanceId > 0) {
Engine engine = Engine();
engine.Name() + "-" + instanceId);
}
}
initialize的第⼆个⽅法removeServiceConnectors,将上⾯new的connection以service(这⾥是StandardService[Tomcat])做key保存到private final Map<Service, Connector[]> serviceConnectors中,并将StandardService中的protected Connector[] connectors与service解绑(connector.setService((Service)null);),解绑后下⾯利⽤LifecycleBase启动容器就不会启动到Connector了。
之后是at.start(),这段⽐较复杂,我单独总结⼀篇吧。
TomcatEmbeddedServletContainer的初始化,接下来是rethrowDeferredStartupExceptions,这个⽅法检查初始化过程中的异常,如果有直接在主线程抛出,检查⽅法是TomcatStarter中的private volatile Exception startUpException,这个值是在Context启动过程中记录的:
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext)
throws ServletException {
try {
for (ServletContextInitializer initializer : this.initializers) {
}
}
catch (Exception ex) {
this.startUpException = ex;
springframework和springboot// Prevent Tomcat from logging and re-throwing when we know we can
// deal with it in the main thread, but log for information here.
if (logger.isErrorEnabled()) {
<("Error starting Tomcat context. Exception: "
+ ex.getClass().getName() + ". Message: " + ex.getMessage());
}
}
}
Context context = findContext():
private Context findContext() {
for (Container child : Host().findChildren()) {
if (child instanceof Context) {
return (Context) child;
}
}
throw new IllegalStateException("The host does not contain a Context");
}
绑定命名的上下⽂和classloader,不成功也⽆所谓:
try {
ContextBindings.bindClassLoader(context, getNamingToken(context),
getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
startDaemonAwaitThread⽅法的注释是:与Jetty不同,Tomcat所有的线程都是守护线程,所以创建⼀个⾮守护线程(例:Thread[container-0,5,main])来避免服务到这就shutdown了:
private void startDaemonAwaitThread() {
Thread awaitThread = new Thread("container-" + (())) {
@Override
public void run() {
at.getServer().await();
}
};
awaitThread.setContextClassLoader(getClass().getClassLoader());
awaitThread.setDaemon(false);
awaitThread.start();
}
这个await每10秒检查⼀次是否关闭了:
try {
awaitThread = Thread.currentThread();
while(!stopAwait) {
try {
Thread.sleep( 10000 );
} catch( InterruptedException ex ) {
// continue and check the flag
}
}
} finally {
awaitThread = null;
}
return;
回到EmbeddedWebApplicationContext,initPropertySources⽅法,⽤初始化好的servletContext完善环境变量:
/**
* {@inheritDoc}
* <p>Replace {@code Servlet}-related property sources.
*/
@Override
protected void initPropertySources() {
ConfigurableEnvironment env = getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, null);
}
}
createEmbeddedServletContainer就结束了,内嵌容器的启动过程⾄此结束。
==========================================================
:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论