前面的文章分析了在tomcat中的container与pipeline的设计。。。我们知道Server是Service对象的容器,
而Service可以有多个connector对象,但是只能有一个container(一般就是engine对象)对象。。
所以分析各个不同的container对象的入口就在于service对象。。。而在tomcat中一般都是用StandardService这个类型。。。
先来看看一个初略的继承结构吧:
这里其实StandardService算是比较简单的了,首先它继承了LifecycleMBeanBase,这说明这个对象在启动之后将会被注册到JMX上去。。。同时它实现了service接口。。。这里就先来看看这个接口的定义吧:
package org.apache.catalina; import org.apache.catalina.connector.Connector; import org.apache.catalina.mapper.Mapper; public interface Service extends Lifecycle { public Container getContainer(); //获取拥有的container,service对象只拥有一个container public void setContainer(Container container); //设置拥有的container public String getName(); //service的名字 public void setName(String name); public Server getServer(); //返回所属的server对象 public void setServer(Server server);
public ClassLoader getParentClassLoader(); //获取parentClassLoader public void setParentClassLoader(ClassLoader parent); public String getDomain(); //在jmx注册在哪个domain下面 public void addConnector(Connector connector); //添加一个connector public Connector[] findConnectors(); //返回所有的connector public void removeConnector(Connector connector); // 移除一个connecter public void addExecutor(Executor ex); //添加一个executor public Executor[] findExecutors(); //返回所有的executor public Executor getExecutor(String name); //根据名字获取executor public void removeExecutor(Executor ex); //移除一个executor Mapper getMapper(); //用于请求的map }
上面接口的定义其实也还挺简单的,无非首先拥有的container对象的管理,另外这里会管理容纳多个connector对象,同时还有executor对象,在以前分析connector部分的时候,我们知道在connector的创建的时候可以指定executor对象,这里指定的executor就是定义在servie对象里面的,当然如果没有指定的话,那么将会创建默认的executor、。、、、
同时这里还有一个非常重要的值得注意的东西,那就是mapper,他将会具体的负责请求的路由。。。
好啦,那么接下来来看看StandardService类型的属性的定义吧:
private String name = null; private static final StringManager sm = StringManager.getManager(Constants.Package); private Server server = null; //所属的server对象 protected final PropertyChangeSupport support = new PropertyChangeSupport(this); //用于支持监听属性的修改 protected Connector connectors[] = new Connector[0]; //connector的数组 private final Object connectorsLock = new Object(); //当修改connector时候用到的锁 protected final ArrayList<Executor> executors = new ArrayList<>(); //所有定义的executor,connector可以用这个里面定义的executor protected Container container = null; //关联的container private ClassLoader parentClassLoader = null; //classLoader protected final Mapper mapper = new Mapper(); //用于请求的map,host的map,warpper,context的map啥的
这里具体每个属性是干嘛用的注释应该说的蛮清楚的吧。。。接下来来看看StandardService启动吧:
//启动 protected void startInternal() throws LifecycleException { if(log.isInfoEnabled()) log.info(sm.getString("standardService.start.name", this.name)); setState(LifecycleState.STARTING); // Start our defined Container first if (container != null) { synchronized (container) { container.start(); //先启动container } } synchronized (executors) { for (Executor executor: executors) { executor.start(); //启动所有的executor } } mapperListener.start(); //启动mapperListener // Start our defined Connectors second synchronized (connectorsLock) { for (Connector connector: connectors) { try { // If it has already failed, don‘t try and start it if (connector.getState() != LifecycleState.FAILED) { connector.start(); //启动所有的connector } } catch (Exception e) { log.error(sm.getString( "standardService.connector.startFailed", connector), e); } } } }
这里代码应该也挺简单的吧,首先设置当前组件的状态,然后启动拥有的container对象,其实一般都是engine,然后启动所有定义的executor对象,接着启动mapperListener,这个非常重要,待会分析
接下来就是启动connector,这样service就算是启动起来了。。。
嗯,其实service这部分真的很简单。。没啥说的。。。
还啦。。。接下来就来看看mapperListener的start干了啥吧。。。
先来看看MapperListener的比较初略的继承体系吧:
这里LifecycleMBeanBase的继承,说明对象的启动会被注册到jmx上面去。。。另外这里比较有意思的是实现了ContainerListener和LifecycleListener,说明既可以响应container的事件,例如添加child,添加valve啥的,同时还可以响应组件的状态事件。。。。
这里先来看看它的属性的定义吧:
private final Mapper mapper; //service的mapper,用于对请求进行路由 private final Service service; //所属的service对象 private static final StringManager sm = StringManager.getManager(Constants.Package); private final String domain = null; //注册在jmx哪个域名下面 public MapperListener(Mapper mapper, Service service) { this.mapper = mapper; this.service = service; }
这里属性不多吧,具体的干啥用的都在注释后面贴了出来。。。
接下来来看看它的启动吧:
public void startInternal() throws LifecycleException { setState(LifecycleState.STARTING); // Find any components that have already been initialized since the // MBean listener won‘t be notified as those components will have // already registered their MBeans findDefaultHost(); //获取当前engine默认的host Engine engine = (Engine) service.getContainer(); //获取当前service的container,其实也就是engine addListeners(engine); //为engine添加listener,将listener都设置为当前 Container[] conHosts = engine.findChildren(); //获取这个engine所有的host for (Container conHost : conHosts) { //遍历engine下面的所有的host定义 Host host = (Host) conHost; if (!LifecycleState.NEW.equals(host.getState())) { // Registering the host will register the context and wrappers registerHost(host); //登记host对象 } } }
这个首先设置当前组件的状态,接着获取engine对象定义的默认的host的名字,看一段配置文件就知道了:
<Engine name="Catalina" defaultHost="localhost">
每一个engine都可以指定默认使用的host对象,后面的就是设置为默认的host对象的名字。。
这里获取service对象拥有的container对象,其实就是engine对象,然后调用addListeners方法为其添加监听,最后再遍历当前engine对象的所有的host对象,调用registerHost方法注册他们。。
这里先来看看这个addListeners方法吧:
//为container对象添加lister,都设置为当前 private void addListeners(Container container) { container.addContainerListener(this); //将这个container对象的container事件的监听设置为当前对象 container.addLifecycleListener(this); //设置当前对象来监听状态事件 for (Container child : container.findChildren()) { //遍历这个对象的所有的子container对象 addListeners(child); //同时将所有的子container的监听设置为当前对象 } }
这里其实递归的来设置container的监听,不光设置当前container对象,还要讲当前container对象的子container的监听都设置了。。这样就保证了当前service整个container的体系都设置了当前MapperListener为监听。。。
好啦,既然要设置监听,那么我们就来看看对于事件他将会做啥吧,首先来看看如何来响应container的事件吧:
//也就是有child加入,或者valve加入的时候会激活 的事件,这里会将child的listener也设置为当前 public void containerEvent(ContainerEvent event) { if (Container.ADD_CHILD_EVENT.equals(event.getType())) { //如果是添加child的事件 Container child = (Container) event.getData(); addListeners(child); // 为这个child添加listener,将其的lifecycle和containerlistenre都指定为当前对象 // If child is started then it is too late for life-cycle listener // to register the child so register it here if (child.getState().isAvailable()) { if (child instanceof Host) { registerHost((Host) child); //如果这个对象是host,那么需要注册host } else if (child instanceof Context) { registerContext((Context) child); //如果是context那么需要登记 } else if (child instanceof Wrapper) { //如果是warpper,登记 registerWrapper((Wrapper) child); } } } else if (Container.REMOVE_CHILD_EVENT.equals(event.getType())) { Container child = (Container) event.getData(); removeListeners(child); // No need to unregister - life-cycle listener will handle this when // the child stops } else if (Host.ADD_ALIAS_EVENT.equals(event.getType())) { // Handle dynamically adding host aliases mapper.addHostAlias(((Host) event.getSource()).getName(), event.getData().toString()); } else if (Host.REMOVE_ALIAS_EVENT.equals(event.getType())) { // Handle dynamically removing host aliases mapper.removeHostAlias(event.getData().toString()); } else if (Wrapper.ADD_MAPPING_EVENT.equals(event.getType())) { //warper的mapping事件 // Handle dynamically adding wrappers Wrapper wrapper = (Wrapper) event.getSource(); //获取当前warpper对象 Context context = (Context) wrapper.getParent(); //获取当前warpper所属的context String contextPath = context.getPath(); // 获取context的path if ("/".equals(contextPath)) { //如果是根 contextPath = ""; } String version = ((Context) wrapper.getParent()).getWebappVersion(); String hostName = context.getParent().getName(); //获取host的名字 String wrapperName = wrapper.getName(); //获取warpper对象的名字 String mapping = (String) event.getData(); //要map的路径 boolean jspWildCard = ("jsp".equals(wrapperName) && mapping.endsWith("/*")); mapper.addWrapper(hostName, contextPath, version, mapping, wrapper, //在engine的mapper上面注册 jspWildCard, context.isResourceOnlyServlet(wrapperName)); } else if (Wrapper.REMOVE_MAPPING_EVENT.equals(event.getType())) { // Handle dynamically removing wrappers Wrapper wrapper = (Wrapper) event.getSource(); String contextPath = ((Context) wrapper.getParent()).getPath(); if ("/".equals(contextPath)) { contextPath = ""; } String version = ((Context) wrapper.getParent()).getWebappVersion(); String hostName = wrapper.getParent().getParent().getName(); String mapping = (String) event.getData(); mapper.removeWrapper(hostName, contextPath, version, mapping); } else if (Context.ADD_WELCOME_FILE_EVENT.equals(event.getType())) { // Handle dynamically adding welcome files Context context = (Context) event.getSource(); String hostName = context.getParent().getName(); String contextPath = context.getPath(); if ("/".equals(contextPath)) { contextPath = ""; } String welcomeFile = (String) event.getData(); mapper.addWelcomeFile(hostName, contextPath, context.getWebappVersion(), welcomeFile); } else if (Context.REMOVE_WELCOME_FILE_EVENT.equals(event.getType())) { // Handle dynamically removing welcome files Context context = (Context) event.getSource(); String hostName = context.getParent().getName(); String contextPath = context.getPath(); if ("/".equals(contextPath)) { contextPath = ""; } String welcomeFile = (String) event.getData(); mapper.removeWelcomeFile(hostName, contextPath, context.getWebappVersion(), welcomeFile); } else if (Context.CLEAR_WELCOME_FILES_EVENT.equals(event.getType())) { // Handle dynamically clearing welcome files Context context = (Context) event.getSource(); String hostName = context.getParent().getName(); String contextPath = context.getPath(); if ("/".equals(contextPath)) { contextPath = ""; } mapper.clearWelcomeFiles(hostName, contextPath, context.getWebappVersion()); } }
这里很重要吧,首先是对于ADD_CHILD_EVENT事件,如果这里就设对这个加入的子container对象的监听,然后根据不同的类型,进行相应的登记 。。。。。
其实到这里就对整个MapperListener的运行就算比较了解了,通过对container的监控,来进行整个请求的map处理。。。这里就来看看registerHost方法做了啥吧:
//登记host对象 private void registerHost(Host host) { String[] aliases = host.findAliases(); //获取当前host的别名 mapper.addHost(host.getName(), aliases, host); // 将host与其名字对应起来 for (Container container : host.findChildren()) { //登记这个host下面的所有的context if (container.getState().isAvailable()) { registerContext((Context) container); } } if(log.isDebugEnabled()) { log.debug(sm.getString("mapperListener.registerHost", host.getName(), domain, service)); } }
这个其实就是在service对象中的mapper中建立host的名字,别名与host对象的对象。。这样通过名字就能很快的索引到host了。。。。它用于干啥呢?嗯,其实就是对http请求的host字段进行路由。。。
好啦,接下来来看看registerContext方法,怎么登记context对象吧(其实一个context对象就对应着一个web应用程序)。。
//登记context对象,在里面要接着登记warrper private void registerContext(Context context) { String contextPath = context.getPath(); //获取context的path if ("/".equals(contextPath)) { contextPath = ""; } Host host = (Host)context.getParent(); //这里获取的其实是host WebResourceRoot resources = context.getResources(); //获取root String[] welcomeFiles = context.findWelcomeFiles(); //获取welcomeFile mapper.addContextVersion(host.getName(), host, contextPath, context.getWebappVersion(), context, welcomeFiles, resources); //对添加context的map for (Container container : context.findChildren()) { registerWrapper((Wrapper) container); //登记所有的warpper对象 } if(log.isDebugEnabled()) { log.debug(sm.getString("mapperListener.registerContext", contextPath, service)); } }
这里首先获获取了当前context的path,然后获取这个context所属的host对象,接着获取webroot的引用,以及首页啥的,然后将这些属性一起注册到service的mapper上面去,这样就能够很快的对http请求的context的进行定位了。。。最后解释登记Wrapper(这里可以先简单的理解为servlet的一层包装)了:
//登记warpper对象 private void registerWrapper(Wrapper wrapper) { String wrapperName = wrapper.getName(); //获取名字 Context context = (Context) wrapper.getParent(); //获取所属的context对象 String contextPath = context.getPath(); //获取context的path if ("/".equals(contextPath)) { contextPath = ""; } String version = ((Context) wrapper.getParent()).getWebappVersion(); //webapp的版本 String hostName = context.getParent().getName(); // String[] mappings = wrapper.findMappings(); //获取这个warpper所有的mapping信息,毕竟一个servlet可能可以处理多个请求路径嘛 for (String mapping : mappings) { //根据mapping的信息,在service的mapper里面进行mapper boolean jspWildCard = (wrapperName.equals("jsp") && mapping.endsWith("/*")); mapper.addWrapper(hostName, contextPath, version, mapping, wrapper, jspWildCard, context.isResourceOnlyServlet(wrapperName)); } if(log.isDebugEnabled()) { log.debug(sm.getString("mapperListener.registerWrapper", wrapperName, contextPath, service)); } }
这里其实跟上面的差不多,只不过注册的时候多了一些信息,到这里位置,host的索引,context索引,直到warpper(servlet)的索引就算都搞定了。。。
那么是不是整个http请求的路由也就差不多啦。。。也就差分析Mapper对象了,这个以后再说吧。。
这里整个MapperListener的工作也都差不多啦,当然也还有一些状态事件的相应,例如启动啥的,其实要做的事情也都差不多,无非是登记,或者基础mapper的登记。。。。
这样对于tomcat是如何维护请求的map信息也就比较的清楚了。。。。