Tomcat和设计模式
tomcat的启动:
windows下的exe文件看的话太麻烦,就找tomcat的sh脚本:
start.sh就做了一件事,启动catalina.sh脚本。
catalina.sh的执行流程如下:
1. 执行设置环境变量脚本。
2. 获取一些环境路径。
3. . "$CATALINA_HOME"/bin/setclasspath.sh (这也是一个脚本,这里不介绍)目的是设置jar和java_HOME。
4. 设置输出参数路径和配置;classpath路径,等一些java管理类的引入。
5. org.apache.catalina.startup.Bootstrap "$@" start 。
catalina.sh执行的最终目的是执行第5条步骤。下面是bootstrap类的结构图:
Bootstrap的start和stop就是tomcat生命周期的开始和结束,也就是tomcat的LifyCycle接口的功能。
1 public void start() throws Exception {#start启动函数 2 if (this.catalinaDaemon == null) { 3 this.init(); 4 } 5 6 Method method = this.catalinaDaemon.getClass().getMethod("start", (Class[])null); 7 method.invoke(this.catalinaDaemon, (Object[])null); 8 }
1 public void init() throws Exception {#初始化Catalina对象 2 this.initClassLoaders(); 3 Thread.currentThread().setContextClassLoader(this.catalinaLoader); 4 SecurityClassLoad.securityClassLoad(this.catalinaLoader); 5 if (log.isDebugEnabled()) { 6 log.debug("Loading startup class"); 7 } 8 9 Class<?> startupClass = this.catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); 10 Object startupInstance = startupClass.getConstructor().newInstance(); 11 if (log.isDebugEnabled()) { 12 log.debug("Setting startup class properties"); 13 } 14 15 String methodName = "setParentClassLoader"; 16 Class<?>[] paramTypes = new Class[]{Class.forName("java.lang.ClassLoader")}; 17 Object[] paramValues = new Object[]{this.sharedLoader}; 18 Method method = startupInstance.getClass().getMethod(methodName, paramTypes); 19 method.invoke(startupInstance, paramValues);#Catalina对象的setParentClassLoader方法,将下面初始化的classloader设置为父类clasloader
20 this.catalinaDaemon = startupInstance;
21 }
1 private void initClassLoaders() {#这个方法初始化了classloader 属性文件在下面的catalina.properties文件中 2 try { 3 this.commonLoader = this.createClassLoader("common", (ClassLoader)null); 4 if (this.commonLoader == null) { 5 this.commonLoader = this.getClass().getClassLoader(); 6 } 7 8 this.catalinaLoader = this.createClassLoader("server", this.commonLoader); 9 this.sharedLoader = this.createClassLoader("shared", this.commonLoader); 10 } catch (Throwable var2) { 11 handleThrowable(var2); 12 log.error("Class loader creation threw exception", var2); 13 System.exit(1); 14 } 15 16 }
1 # Licensed to the Apache Software Foundation (ASF) under one or more 2 # contributor license agreements. See the NOTICE file distributed with 3 # this work for additional information regarding copyright ownership. 4 # The ASF licenses this file to You under the Apache License, Version 2.0 5 # (the "License"); you may not use this file except in compliance with 6 # the License. You may obtain a copy of the License at 7 # 8 # http://www.apache.org/licenses/LICENSE-2.0 9 # 10 # Unless required by applicable law or agreed to in writing, software 11 # distributed under the License is distributed on an "AS IS" BASIS, 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # See the License for the specific language governing permissions and 14 # limitations under the License. 15 16 # 17 # List of comma-separated packages that start with or equal this string 18 # will cause a security exception to be thrown when 19 # passed to checkPackageAccess unless the 20 # corresponding RuntimePermission ("accessClassInPackage."+package) has 21 # been granted. 22 package.access=sun.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.tomcat. 23 # 24 # List of comma-separated packages that start with or equal this string 25 # will cause a security exception to be thrown when 26 # passed to checkPackageDefinition unless the 27 # corresponding RuntimePermission ("defineClassInPackage."+package) has 28 # been granted. 29 # 30 # by default, no packages are restricted for definition, and none of 31 # the class loaders supplied with the JDK call checkPackageDefinition. 32 # 33 package.definition=sun.,java.,org.apache.catalina.,org.apache.coyote.,\ 34 org.apache.jasper.,org.apache.naming.,org.apache.tomcat. 35 36 # 37 # 38 # List of comma-separated paths defining the contents of the "common" 39 # classloader. Prefixes should be used to define what is the repository type. 40 # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute. 41 # If left as blank,the JVM system loader will be used as Catalina's "common" 42 # loader. 43 # Examples: 44 # "foo": Add this folder as a class repository 45 # "foo/*.jar": Add all the JARs of the specified folder as class 46 # repositories 47 # "foo/bar.jar": Add bar.jar as a class repository 48 # 49 # Note: Values are enclosed in double quotes ("...") in case either the 50 # ${catalina.base} path or the ${catalina.home} path contains a comma. 51 # Because double quotes are used for quoting, the double quote character 52 # may not appear in a path. 53 common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar" 54 55 # 56 # List of comma-separated paths defining the contents of the "server" 57 # classloader. Prefixes should be used to define what is the repository type. 58 # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute. 59 # If left as blank, the "common" loader will be used as Catalina's "server" 60 # loader. 61 # Examples: 62 # "foo": Add this folder as a class repository 63 # "foo/*.jar": Add all the JARs of the specified folder as class 64 # repositories 65 # "foo/bar.jar": Add bar.jar as a class repository 66 # 67 # Note: Values may be enclosed in double quotes ("...") in case either the 68 # ${catalina.base} path or the ${catalina.home} path contains a comma. 69 # Because double quotes are used for quoting, the double quote character 70 # may not appear in a path. 71 server.loader= 72 73 # 74 # List of comma-separated paths defining the contents of the "shared" 75 # classloader. Prefixes should be used to define what is the repository type. 76 # Path may be relative to the CATALINA_BASE path or absolute. If left as blank, 77 # the "common" loader will be used as Catalina's "shared" loader. 78 # Examples: 79 # "foo": Add this folder as a class repository 80 # "foo/*.jar": Add all the JARs of the specified folder as class 81 # repositories 82 # "foo/bar.jar": Add bar.jar as a class repository 83 # Please note that for single jars, e.g. bar.jar, you need the URL form 84 # starting with file:. 85 # 86 # Note: Values may be enclosed in double quotes ("...") in case either the 87 # ${catalina.base} path or the ${catalina.home} path contains a comma. 88 # Because double quotes are used for quoting, the double quote character 89 # may not appear in a path. 90 shared.loader= 91 92 # Default list of JAR files that should not be scanned using the JarScanner 93 # functionality. This is typically used to scan JARs for configuration 94 # information. JARs that do not contain such information may be excluded from 95 # the scan to speed up the scanning process. This is the default list. JARs on 96 # this list are excluded from all scans. The list must be a comma separated list 97 # of JAR file names. 98 # The list of JARs to skip may be over-ridden at a Context level for individual 99 # scan types by configuring a JarScanner with a nested JarScanFilter. 100 # The JARs listed below include: 101 # - Tomcat Bootstrap JARs 102 # - Tomcat API JARs 103 # - Catalina JARs 104 # - Jasper JARs 105 # - Tomcat JARs 106 # - Common non-Tomcat JARs 107 # - Test JARs (JUnit, Cobertura and dependencies) 108 tomcat.util.scan.StandardJarScanFilter.jarsToSkip=\ 109 annotations-api.jar,\ 110 ant-junit*.jar,\ 111 ant-launcher.jar,\ 112 ant.jar,\ 113 asm-*.jar,\ 114 aspectj*.jar,\ 115 bootstrap.jar,\ 116 catalina-ant.jar,\ 117 catalina-ha.jar,\ 118 catalina-ssi.jar,\ 119 catalina-storeconfig.jar,\ 120 catalina-tribes.jar,\ 121 catalina.jar,\ 122 cglib-*.jar,\ 123 cobertura-*.jar,\ 124 commons-beanutils*.jar,\ 125 commons-codec*.jar,\ 126 commons-collections*.jar,\ 127 commons-daemon.jar,\ 128 commons-dbcp*.jar,\ 129 commons-digester*.jar,\ 130 commons-fileupload*.jar,\ 131 commons-httpclient*.jar,\ 132 commons-io*.jar,\ 133 commons-lang*.jar,\ 134 commons-logging*.jar,\ 135 commons-math*.jar,\ 136 commons-pool*.jar,\ 137 dom4j-*.jar,\ 138 easymock-*.jar,\ 139 ecj-*.jar,\ 140 el-api.jar,\ 141 geronimo-spec-jaxrpc*.jar,\ 142 h2*.jar,\ 143 hamcrest-*.jar,\ 144 hibernate*.jar,\ 145 httpclient*.jar,\ 146 icu4j-*.jar,\ 147 jasper-el.jar,\ 148 jasper.jar,\ 149 jaspic-api.jar,\ 150 jaxb-*.jar,\ 151 jaxen-*.jar,\ 152 jdom-*.jar,\ 153 jetty-*.jar,\ 154 jmx-tools.jar,\ 155 jmx.jar,\ 156 jsp-api.jar,\ 157 jstl.jar,\ 158 jta*.jar,\ 159 junit-*.jar,\ 160 junit.jar,\ 161 log4j*.jar,\ 162 mail*.jar,\ 163 objenesis-*.jar,\ 164 oraclepki.jar,\ 165 oro-*.jar,\ 166 servlet-api-*.jar,\ 167 servlet-api.jar,\ 168 slf4j*.jar,\ 169 taglibs-standard-spec-*.jar,\ 170 tagsoup-*.jar,\ 171 tomcat-api.jar,\ 172 tomcat-coyote.jar,\ 173 tomcat-dbcp.jar,\ 174 tomcat-i18n-*.jar,\ 175 tomcat-jdbc.jar,\ 176 tomcat-jni.jar,\ 177 tomcat-juli-adapters.jar,\ 178 tomcat-juli.jar,\ 179 tomcat-util-scan.jar,\ 180 tomcat-util.jar,\ 181 tomcat-websocket.jar,\ 182 tools.jar,\ 183 websocket-api.jar,\ 184 wsdl4j*.jar,\ 185 xercesImpl.jar,\ 186 xml-apis.jar,\ 187 xmlParserAPIs-*.jar,\ 188 xmlParserAPIs.jar,\ 189 xom-*.jar 190 191 # Default list of JAR files that should be scanned that overrides the default 192 # jarsToSkip list above. This is typically used to include a specific JAR that 193 # has been excluded by a broad file name pattern in the jarsToSkip list. 194 # The list of JARs to scan may be over-ridden at a Context level for individual 195 # scan types by configuring a JarScanner with a nested JarScanFilter. 196 tomcat.util.scan.StandardJarScanFilter.jarsToScan=\ 197 log4j-taglib*.jar,\ 198 log4j-web*.jar,\ 199 log4javascript*.jar,\ 200 slf4j-taglib*.jar 201 202 # String cache configuration. 203 tomcat.util.buf.StringCache.byte.enabled=true 204 #tomcat.util.buf.StringCache.char.enabled=true 205 #tomcat.util.buf.StringCache.trainThreshold=500000 206 #tomcat.util.buf.StringCache.cacheSize=5000
可以看出它设置了一些属性值以及一些需要的路径。
1 Method method = this.catalinaDaemon.getClass().getMethod("start", (Class[])null); 2 method.invoke(this.catalinaDaemon, (Object[])null);
上面的代码开始启动Catalina.start。
1 public void start() { 2 if (this.getServer() == null) { 3 this.load(); 4 } 5 6 if (this.getServer() == null) { 7 log.fatal(sm.getString("catalina.noServer")); 8 } else { 9 long t1 = System.nanoTime(); 10 11 try { 12 this.getServer().start(); 13 } catch (LifecycleException var7) { 14 log.fatal(sm.getString("catalina.serverStartFail"), var7); 15 16 try { 17 this.getServer().destroy(); 18 } catch (LifecycleException var6) { 19 log.debug("destroy() failed for failed Server ", var6); 20 } 21 return; 22 } 23 24 long t2 = System.nanoTime(); 25 if (log.isInfoEnabled()) { 26 log.info(sm.getString("catalina.startup", new Object[]{(t2 - t1) / 1000000L})); 27 } 28 29 if (this.useShutdownHook) { 30 if (this.shutdownHook == null) { 31 this.shutdownHook = new Catalina.CatalinaShutdownHook(); 32 } 33 34 Runtime.getRuntime().addShutdownHook(this.shutdownHook); 35 LogManager logManager = LogManager.getLogManager(); 36 if (logManager instanceof ClassLoaderLogManager) { 37 ((ClassLoaderLogManager)logManager).setUseShutdownHook(false); 38 } 39 } 40 41 if (this.await) { 42 this.await(); 43 this.stop(); 44 } 45 } 46 }
上面第二行调用load方法,开始初始化server,步骤太长就不贴了,大致流程就是获取server.xml导入配置,调用server.init()。
然后在第12行启动server,至此catlina.start()结束即server.start()结束。开始新的生命周期启动流程。
让我们回到server.init()方法中:
1 public final synchronized void init() throws LifecycleException { 2 if (!this.state.equals(LifecycleState.NEW)) { 3 this.invalidTransition("before_init"); 4 } 5 6 try { 7 this.setStateInternal(LifecycleState.INITIALIZING, (Object)null, false); 8 this.initInternal(); 9 this.setStateInternal(LifecycleState.INITIALIZED, (Object)null, false); 10 } catch (Throwable var2) { 11 this.handleSubClassException(var2, "lifecycleBase.initFail", this.toString()); 12 } 13 14 }
这一步主要做的事情就是启动server自动定义的步骤:this.initInternal();这个没啥好说的,tomcat以standardServer类来实现这个方法。然后判断LifecycleState是否正常,如果不正常就抛出异常。如果正常就设置LifecycleState的值,这个值是个枚举,代表了当前server的状态。
让我们会到server.start()方法,这个方法也是再次重复的判断了下server现在的状态,即那个枚举值是不是已经启动,如果没有启动就重新调一边init,如果启动就打印已经启动提示语,这个方法没有什么新动作。
上面的start,init方法是lifycycle接口的方法,抽象类LifecycleBase实现了这两个方法,并且定义了自己的抽象方法initinternal方法,这三个方法在这个部分比较重要。我们知道抽象类都是定义的一些公共方法,所以这些方法会被所有实现这个功能的的组件使用。
谈到这里就不得不谈一下tomcat的设计模式了,tomcat作为一个经典之作,其内使用一定量的设计模式肯定是必须的,而且会是tomcat的灵魂,tomcat的设计模式主要包含了以下几个设计模式:
门面模式:
在tomcat中因为很多组件需要交互,所有组件之间需要获取其他组件的信息都是通过门面类来获取的,所以门面模式在tomcat中应用的也很多,比如applicaitoncontext类和它的门面类ApplicationContextFacade,他们都实现了servletContext接口,这个接口是servletContext的功能接口,applicaitoncontext持有ApplicationContextFacade,并将自己传给ApplicationContextFacade,由ApplicationContextFacade代替自己暴露自己需要提供的服务。(这就是门面模式,把自己的核心服务交给自己信得过的人(兄弟)让他完成服务提供。)
观察者设计模式:
在tomcat的实现中,观察者模式是最重要的一环,它负责初始化并启动整个tomcat的所有组件的生命周期:包括两部分:完成最重要的lifecycle接口功能以及监听事件的功能,lifecycle接口的功能表由子类去实现,这是它存在的意义。同时子类在实现这些方法的同时需要将自己事件的LifecycleState事件告诉所有注册在自己的lifecycleListeners上的监听者,让他们知道自己正在做什么,由此他们可以对自己感兴趣的事做出反应。
命令模式:
tomcat中命令模式的实现是Connector和Container。这是两个接口。Connector是抽象命令请求者,Container是抽象命令接收者,server是这一切的缘由,HttpProcessor是抽象命令。
这就对应上了命令模式的五个模块:client:创建一个命令,并决定接收者。command:命令接口。ConcreteCommand:具体命令。Invoker:请求者。Receiver:命令接受则。
在tomcat中的实现形式是:server需要Connector来接受来自外接的Http请求,然后Connector接受到请求,并创建了命令HttpProcessor,然后server将这个命令交给了Container接收者。
责任链模式:
在tomcat中最容易发现的就是责任链模式,这个模式也是Container容器设计的基础,整个容器就是通过一个链连接在一起的,这个链一直将请求传递给最终处理请求的那个Servlet。
它的原理:特务的工作方式就是责任链模式的原理。蒋委员长给特务部门下命令,特务部门给特务头下命令,特务头给地方特务潜伏点下任务,地方特务潜伏点给具体的特务下命令,特务给自己的小弟下命令。
这里每个环节都只能是上司对下司直接下命令,没有第三方赚差价,任务会一级一级的往下传,任务是继续下发还是被节点自己消化解决由每个节点自己决定,上司只听它直属下司的回报。
在tomcat中这个模式被强化了,tomcat中允许第三方赚差价,它是通过PipeLine和Value来实现的,我们知道tomcat一共有四个容器,分别是Host,Engine,Context,Wrapper。这些容器的实例在Connector封装HttpServletRequest的时候就已经封装到Request中了,而standardPinple定义在抽象类ContainerBase中,所以每个容器都有自己的standardPinple:
标准的Pinple的属性如下:
而在标准的容器中都会给自己的pipeline设置basic,举例Engine容器:
这个basic就是每个容器都会对应一个的Value,这个Value很重要。所以pipeline一共有三个属性已经被定了两个,那么最后的first是什么?查看server.xml,在Host容器下有这么一段配置:
这个配置就是在配置这个first,就是你想在传到Context容器之前对request做的操作。
好了现在讲下tomcat责任链怎么设计的,首先每个容器都有一个pipeline责任链,这个链就是数据流需要经过的地方,因为tomcat对所有组件的实现都是standard***,所以后续的***就代表了standard***。每个container持有一个pipeline,且这个容器在初始化的时候就给这个pipiline设置basic(标准Value,必须是它且不能改变当然你也改变不了),first(xml中定义的),container(this),xml中定义的first需要实现Value接口,Value接口给了默认的抽象类,只需要继承即可简单的实现自己Value。
tomcat就是通过这个pipeline来链接各个容器,每个容器的pipeline都必会有一个叫做standard**Value的Value,这个是将数据传到下个容器的Value,如下面的代码:
1 final class StandardEngineValve extends ValveBase { 2 public StandardEngineValve() { 3 super(true); 4 } 5 6 public final void invoke(Request request, Response response) throws IOException, ServletException { 7 Host host = request.getHost(); 8 if (host != null) { 9 if (request.isAsyncSupported()) { 10 request.setAsyncSupported(host.getPipeline().isAsyncSupported()); 11 } 12 13 host.getPipeline().getFirst().invoke(request, response); 14 } 15 } 16 }
前面已经讲过,一个请求的容器在request中封装,这里取出然后调用它的pipeline.invoke方法执行它的pipeline链就转到了下个容器中了。
这里讲下Value接口的抽象类:
1 public abstract class ValveBase extends LifecycleMBeanBase implements Contained, Valve { 2 protected static final StringManager sm = StringManager.getManager(ValveBase.class); 3 protected boolean asyncSupported; 4 protected Container container; 5 protected Log containerLog; 6 protected Valve next;
看到next了吗,这是个链表,所以pipeline链指的不是pipeline是个链表,而是pipeline的first指向的是个链表,所以***Value中的会只调用一次pipeline.getFirst.invoke,后续会继续调用下一个Value,比如xml中提到的AccessLogValve,它的抽象类就会调用下一个的invoke:
1 public abstract class AbstractAccessLogValve extends ValveBase implements AccessLog 2 3 public void invoke(Request request, Response response) throws IOException, ServletException { 4 if (this.tlsAttributeRequired) { 5 request.getAttribute("javax.servlet.request.X509Certificate"); 6 } 7 8 AbstractAccessLogValve.CachedElement[] var3 = this.cachedElements; 9 int var4 = var3.length; 10 11 for(int var5 = 0; var5 < var4; ++var5) { 12 AbstractAccessLogValve.CachedElement element = var3[var5]; 13 element.cache(request); 14 } 15 16 this.getNext().invoke(request, response); 17 }
在最开始提到了Standard***Value很重要,就在这里,因为Standard***Value就是链接各个容器的链接口,能不能到下个容器就靠这个Standard***Value对象。而这个对象总实会被加到各个容器的pipeline的最后:
1 public void addValve(Valve valve) { 2 if (valve instanceof Contained) { 3 ((Contained)valve).setContainer(this.container); 4 } 5 6 if (this.getState().isAvailable() && valve instanceof Lifecycle) { 7 try { 8 ((Lifecycle)valve).start(); 9 } catch (LifecycleException var3) { 10 log.error(sm.getString("standardPipeline.valve.start"), var3); 11 } 12 } 13 14 if (this.first == null) { 15 this.first = valve; 16 valve.setNext(this.basic); 17 } else { 18 for(Valve current = this.first; current != null; current = current.getNext()) { 19 if (current.getNext() == this.basic) { 20 current.setNext(valve); 21 valve.setNext(this.basic); 22 break; 23 } 24 } 25 } 26 27 this.container.fireContainerEvent("addValve", valve); 28 }
这是因为每个Value在添加它的下个Value的时候都会将Standard***Value加到最后面,以保证它会将数据留到正确的容器,而不是流没了。
参考文章 https://blog.csdn.net/fcc7619666/article/details/52022007