- 浏览: 74841 次
- 性别:
- 来自: 合肥
-
文章分类
最新评论
本文以 Tomcat 5 为基础,也兼顾最新的 Tomcat 6 和 Tomcat 4。Tomcat 的基本设计思路和架构是具有一定连续性的。
Tomcat 的结构很复杂,但是 Tomcat 也非常的模块化,找到了 Tomcat 最核心的模块,您就抓住了 Tomcat 的“七寸”。下面是 Tomcat 的总体结构图:
图 1.Tomcat 的总体结构

从上图中可以看出 Tomcat 的心脏是两个组件:Connector 和 Container,关于这两个组件将在后面详细介绍。Connector 组件是可以被替换,这样可以提供给服务器设计者更多的选择,因为这个组件是如此重要,不仅跟服务器的设计的本身,而且和不同的应用场景也十分相关,所以一个 Container 可以选择对应多个 Connector。多个 Connector 和一个 Container 就形成了一个 Service,Service 的概念大家都很熟悉了,有了 Service 就可以对外提供服务了,但是 Service 还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非 Server 莫属了。所以整个 Tomcat 的生命周期由 Server 控制。
我们将 Tomcat 中 Connector、Container 作为一个整体比作一对情侣的话,Connector 主要负责对外交流,可以比作为 Boy,Container 主要处理 Connector 接受的请求,主要是处理内部事务,可以比作为 Girl。那么这个 Service 就是连接这对男女的结婚证了。是 Service 将它们连接在一起,共同组成一个家庭。当然要组成一个家庭还要很多其它的元素。
说白了,Service 只是在 Connector 和 Container 外面多包一层,把它们组装在一起,向外面提供服务,一个 Service 可以设置多个 Connector,但是只能有一个 Container 容器。这个 Service 接口的方法列表如下:
图 2. Service 接口

从 Service 接口中定义的方法中可以看出,它主要是为了关联 Connector 和 Container,同时会初始化它下面的其它组件,注意接口中它并没有规定一定要控制它下面的组件的生命周期。所有组件的生命周期在一个 Lifecycle 的接口中控制,这里用到了一个重要的设计模式,关于这个接口将在后面介绍。
Tomcat 中 Service 接口的标准实现类是 StandardService 它不仅实现了 Service 借口同时还实现了 Lifecycle 接口,这样它就可以控制它下面的组件的生命周期了。StandardService 类结构图如下:
图 3. StandardService 的类结构图

从上图中可以看出除了 Service 接口的方法的实现以及控制组件生命周期的 Lifecycle 接口的实现,还有几个方法是用于在事件监听的方法的实现,不仅是这个 Service 组件,Tomcat 中其它组件也同样有这几个方法,这也是一个典型的设计模式,将在后面介绍。
下面看一下 StandardService 中主要的几个方法实现的代码,下面是 setContainer 和 addConnector 方法的源码:
清单 1. StandardService. SetContainer
public void setContainer(Container container) { Container oldContainer = this.container; if ((oldContainer != null) && (oldContainer instanceof Engine)) ((Engine) oldContainer).setService(null); this.container = container; if ((this.container != null) && (this.container instanceof Engine)) ((Engine) this.container).setService(this); if (started && (this.container != null) && (this.container instanceof Lifecycle)) { try { ((Lifecycle) this.container).start(); } catch (LifecycleException e) { ; } } synchronized (connectors) { for (int i = 0; i < connectors.length; i++) connectors[i].setContainer(this.container); } if (started && (oldContainer != null) && (oldContainer instanceof Lifecycle)) { try { ((Lifecycle) oldContainer).stop(); } catch (LifecycleException e) { ; } } support.firePropertyChange("container", oldContainer, this.container); } |
这段代码很简单,其实就是先判断当前的这个 Service 有没有已经关联了 Container,如果已经关联了,那么去掉这个关联关系—— oldContainer.setService(null)。如果这个 oldContainer 已经被启动了,结束它的生命周期。然后再替换新的关联、再初始化并开始这个新的 Container 的生命周期。最后将这个过程通知感兴趣的事件监听程序。这里值得注意的地方就是,修改 Container 时要将新的 Container 关联到每个 Connector,还好 Container 和 Connector 没有双向关联,不然这个关联关系将会很难维护。
清单 2. StandardService. addConnector
public void addConnector(Connector connector) { synchronized (connectors) { connector.setContainer(this.container); connector.setService(this); Connector results[] = new Connector[connectors.length + 1]; System.arraycopy(connectors, 0, results, 0, connectors.length); results[connectors.length] = connector; connectors = results; if (initialized) { try { connector.initialize(); } catch (LifecycleException e) { e.printStackTrace(System.err); } } if (started && (connector instanceof Lifecycle)) { try { ((Lifecycle) connector).start(); } catch (LifecycleException e) { ; } } support.firePropertyChange("connector", null, connector); } } |
上面是 addConnector 方法,这个方法也很简单,首先是设置关联关系,然后是初始化工作,开始新的生命周期。这里值得一提的是,注意 Connector 用的是数组而不是 List 集合,这个从性能角度考虑可以理解,有趣的是这里用了数组但是并没有向我们平常那样,一开始就分配一个固定大小的数组,它这里的实现机制是:重新创建一个当前大小的数组对象,然后将原来的数组对象 copy 到新的数组中,这种方式实现了类似的动态数组的功能,这种实现方式,值得我们以后拿来借鉴。
最新的 Tomcat6 中 StandardService 也基本没有变化,但是从 Tomcat5 开始 Service、Server 和容器类都继承了 MBeanRegistration 接口,Mbeans 的管理更加合理。
前面说一对情侣因为 Service 而成为一对夫妻,有了能够组成一个家庭的基本条件,但是它们还要有个实体的家,这是它们在社会上生存之本,有了家它们就可以安心的为人民服务了,一起为社会创造财富。
Server 要完成的任务很简单,就是要能够提供一个接口让其它程序能够访问到这个 Service 集合、同时要维护它所包含的所有 Service 的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问的 Service。还有其它的一些次要的任务,如您住在这个地方要向当地政府去登记啊、可能还有要配合当地公安机关日常的安全检查什么的。
Server 的类结构图如下:
图 4. Server 的类结构图

它的标准实现类 StandardServer 实现了上面这些方法,同时也实现了 Lifecycle、MbeanRegistration 两个接口的所有方法,下面主要看一下 StandardServer 重要的一个方法 addService 的实现:
清单 3. StandardServer.addService
public void addService(Service service) { service.setServer(this); synchronized (services) { Service results[] = new Service[services.length + 1]; System.arraycopy(services, 0, results, 0, services.length); results[services.length] = service; services = results; if (initialized) { try { service.initialize(); } catch (LifecycleException e) { e.printStackTrace(System.err); } } if (started && (service instanceof Lifecycle)) { try { ((Lifecycle) service).start(); } catch (LifecycleException e) { ; } } support.firePropertyChange("service", null, service); } } |
从上面第一句就知道了 Service 和 Server 是相互关联的,Server 也是和 Service 管理 Connector 一样管理它,也是将 Service 放在一个数组中,后面部分的代码也是管理这个新加进来的 Service 的生命周期。Tomcat6 中也是没有什么变化的。
前面一直在说 Service 和 Server 管理它下面组件的生命周期,那它们是如何管理的呢?
Tomcat 中组件的生命周期是通过 Lifecycle 接口来控制的,组件只要继承这个接口并实现其中的方法就可以统一被拥有它的组件控制了,这样一层一层的直到一个最高级的组件就可以控制 Tomcat 中所有组件的生命周期,这个最高的组件就是 Server,而控制 Server 的是 Startup,也就是您启动和关闭 Tomcat。
下面是 Lifecycle 接口的类结构图:
图 5. Lifecycle 类结构图

除了控制生命周期的 Start 和 Stop 方法外还有一个监听机制,在生命周期开始和结束的时候做一些额外的操作。这个机制在其它的框架中也被使用,如在 Spring 中。关于这个设计模式会在后面介绍。
Lifecycle 接口的方法的实现都在其它组件中,就像前面中说的,组件的生命周期由包含它的父组件控制,所以它的 Start 方法自然就是调用它下面的组件的 Start 方法,Stop 方法也是一样。如在 Server 中 Start 方法就会调用 Service 组件的 Start 方法,Server 的 Start 方法代码如下:
清单 4. StandardServer.Start
public void start() throws LifecycleException { if (started) { log.debug(sm.getString("standardServer.start.started")); return; } lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); lifecycle.fireLifecycleEvent(START_EVENT, null); started = true; synchronized (services) { for (int i = 0; i < services.length; i++) { if (services[i] instanceof Lifecycle) ((Lifecycle) services[i]).start(); } } lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); } |
监听的代码会包围 Service 组件的启动过程,就是简单的循环启动所有 Service 组件的 Start 方法,但是所有 Service 必须要实现 Lifecycle 接口,这样做会更加灵活。
Server 的 Stop 方法代码如下:
清单 5. StandardServer.Stop
public void stop() throws LifecycleException { if (!started) return; lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null); lifecycle.fireLifecycleEvent(STOP_EVENT, null); started = false; for (int i = 0; i < services.length; i++) { if (services[i] instanceof Lifecycle) ((Lifecycle) services[i]).stop(); } lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null); } |
它所要做的事情也和 Start 方法差不多。
Connector 组件是 Tomcat 中两个核心组件之一,它的主要任务是负责接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理这个请求的线程,处理这个请求的线程就是 Container 组件要做的事了。
由于这个过程比较复杂,大体的流程可以用下面的顺序图来解释:
图 6. Connector 处理一次请求顺序图

(查看清晰大图)
Tomcat5 中默认的 Connector 是 Coyote,这个 Connector 是可以选择替换的。Connector 最重要的功能就是接收连接请求然后分配线程让 Container 来处理这个请求,所以这必然是多线程的,多线程的处理是 Connector 设计的核心。Tomcat5 将这个过程更加细化,它将 Connector 划分成 Connector、Processor、Protocol, 另外 Coyote 也定义自己的 Request 和 Response 对象。
下面主要看一下 Tomcat 中如何处理多线程的连接请求,先看一下 Connector 的主要类图:
图 7. Connector 的主要类图

(查看清晰大图)
看一下 HttpConnector 的 Start 方法:
清单 6. HttpConnector.Start
public void start() throws LifecycleException { if (started) throw new LifecycleException (sm.getString("httpConnector.alreadyStarted")); threadName = "HttpConnector[" + port + "]"; lifecycle.fireLifecycleEvent(START_EVENT, null); started = true; threadStart(); while (curProcessors < minProcessors) { if ((maxProcessors > 0) && (curProcessors >= maxProcessors)) break; HttpProcessor processor = newProcessor(); recycle(processor); } } |
threadStart() 执行就会进入等待请求的状态,直到一个新的请求到来才会激活它继续执行,这个激活是在 HttpProcessor 的 assign 方法中,这个方法是代码如下 :
清单 7. HttpProcessor.assign
synchronized void assign(Socket socket) { while (available) { try { wait(); } catch (InterruptedException e) { } } this.socket = socket; available = true; notifyAll(); if ((debug >= 1) && (socket != null)) log(" An incoming request is being assigned"); } |
创建 HttpProcessor 对象是会把 available 设为 false,所以当请求到来时不会进入 while 循环,将请求的 socket 赋给当期处理的 socket,并将 available 设为 true,当 available 设为 true 是 HttpProcessor 的 run 方法将被激活,接下去将会处理这次请求。
Run 方法代码如下:
清单 8. HttpProcessor.Run
public void run() { while (!stopped) { Socket socket = await(); if (socket == null) continue; try { process(socket); } catch (Throwable t) { log("process.invoke", t); } connector.recycle(this); } synchronized (threadSync) { threadSync.notifyAll(); } } |
解析 socket 的过程在 process 方法中,process 方法的代码片段如下:
清单 9. HttpProcessor.process
private void process(Socket socket) { boolean ok = true; boolean finishResponse = true; SocketInputStream input = null; OutputStream output = null; try { input = new SocketInputStream(socket.getInputStream(),connector.getBufferSize()); } catch (Exception e) { log("process.create", e); ok = false; } keepAlive = true; while (!stopped && ok && keepAlive) { finishResponse = true; try { request.setStream(input); request.setResponse(response); output = socket.getOutputStream(); response.setStream(output); response.setRequest(request); ((HttpServletResponse) response.getResponse()) .setHeader("Server", SERVER_INFO); } catch (Exception e) { log("process.create", e); ok = false; } try { if (ok) { parseConnection(socket); parseRequest(input, output); if (!request.getRequest().getProtocol().startsWith("HTTP/0")) parseHeaders(input); if (http11) { ackRequest(output); if (connector.isChunkingAllowed()) response.setAllowChunking(true); } } 。。。。。。 try { ((HttpServletResponse) response).setHeader ("Date", FastHttpDateFormat.getCurrentDate()); if (ok) { connector.getContainer().invoke(request, response); } 。。。。。。 } try { shutdownInput(input); socket.close(); } catch (IOException e) { ; } catch (Throwable e) { log("process.invoke", e); } socket = null; } |
当 Connector 将 socket 连接封装成 request 和 response 对象后接下来的事情就交给 Container 来处理了。
Container 是容器的父接口,所有子容器都必须实现这个接口,Container 容器的设计用的是典型的责任链的设计模式,它有四个子容器组件构成,分别是:Engine、Host、Context、Wrapper,这四个组件不是平行的,而是父子关系,Engine 包含 Host,Host 包含 Context,Context 包含 Wrapper。通常一个 Servlet class 对应一个 Wrapper,如果有多个 Servlet 就可以定义多个 Wrapper,如果有多个 Wrapper 就要定义一个更高的 Container 了,如 Context,Context 通常就是对应下面这个配置:
清单 10. Server.xml
<Context path="/library" docBase="D:\projects\library\deploy\target\library.war" reloadable="true" /> |
Context 还可以定义在父容器 Host 中,Host 不是必须的,但是要运行 war 程序,就必须要 Host,因为 war 中必有 web.xml 文件,这个文件的解析就需要 Host 了,如果要有多个 Host 就要定义一个 top 容器 Engine 了。而 Engine 没有父容器了,一个 Engine 代表一个完整的 Servlet 引擎。
那么这些容器是如何协同工作的呢?先看一下它们之间的关系图:
图 8. 四个容器的关系图

(查看清晰大图)
当 Connector 接受到一个连接请求时,将请求交给 Container,Container 是如何处理这个请求的?这四个组件是怎么分工的,怎么把请求传给特定的子容器的呢?又是如何将最终的请求交给 Servlet 处理。下面是这个过程的时序图:
图 9. Engine 和 Host 处理请求的时序图

(查看清晰大图)
这里看到了 Valve 是不是很熟悉,没错 Valve 的设计在其他框架中也有用的,同样 Pipeline 的原理也基本是相似的,它是一个管道,Engine 和 Host 都会执行这个 Pipeline,您可以在这个管道上增加任意的 Valve,Tomcat 会挨个执行这些 Valve,而且四个组件都会有自己的一套 Valve 集合。您怎么才能定义自己的 Valve 呢?在 server.xml 文件中可以添加,如给 Engine 和 Host 增加一个 Valve 如下:
清单 11. Server.xml
<Engine defaultHost="localhost" name="Catalina"> <Valve className="org.apache.catalina.valves.RequestDumperValve"/> ……… <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true" xmlNamespaceAware="false" xmlValidation="false"> <Valve className="org.apache.catalina.valves.FastCommonAccessLogValve" directory="logs" prefix="localhost_access_log." suffix=".txt" pattern="common" resolveHosts="false"/> ………… </Host> </Engine> |
StandardEngineValve 和 StandardHostValve 是 Engine 和 Host 的默认的 Valve,它们是最后一个 Valve 负责将请求传给它们的子容器,以继续往下执行。
前面是 Engine 和 Host 容器的请求过程,下面看 Context 和 Wrapper 容器时如何处理请求的。下面是处理请求的时序图:
图 10. Context 和 wrapper 的处理请求时序图

(查看清晰大图)
从 Tomcat5 开始,子容器的路由放在了 request 中,request 中保存了当前请求正在处理的 Host、Context 和 wrapper。
Engine 容器比较简单,它只定义了一些基本的关联关系,接口类图如下:
图 11. Engine 接口的类结构

它的标准实现类是 StandardEngine,这个类注意一点就是 Engine 没有父容器了,如果调用 setParent 方法时将会报错。添加子容器也只能是 Host 类型的,代码如下:
清单 12. StandardEngine. addChild
public void addChild(Container child) { if (!(child instanceof Host)) throw new IllegalArgumentException (sm.getString("standardEngine.notHost")); super.addChild(child); } public void setParent(Container container) { throw new IllegalArgumentException (sm.getString("standardEngine.notParent")); } |
它的初始化方法也就是初始化和它相关联的组件,以及一些事件的监听。
Host 是 Engine 的字容器,一个 Host 在 Engine 中代表一个虚拟主机,这个虚拟主机的作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是 Context,它除了关联子容器外,还有就是保存一个主机应该有的信息。
下面是和 Host 相关的类关联图:
图 12. Host 相关的类图

(查看清晰大图)
从上图中可以看出除了所有容器都继承的 ContainerBase 外,StandardHost 还实现了 Deployer 接口,上图清楚的列出了这个接口的主要方法,这些方法都是安装、展开、启动和结束每个 web application。
Deployer 接口的实现是 StandardHostDeployer,这个类实现了的最要的几个方法,Host 可以调用这些方法完成应用的部署等。
Context 代表 Servlet 的 Context,它具备了 Servlet 运行的基本环境,理论上只要有 Context 就能运行 Servlet 了。简单的 Tomcat 可以没有 Engine 和 Host。
Context 最重要的功能就是管理它里面的 Servlet 实例,Servlet 实例在 Context 中是以 Wrapper 出现的,还有一点就是 Context 如何才能找到正确的 Servlet 来执行它呢? Tomcat5 以前是通过一个 Mapper 类来管理的,Tomcat5 以后这个功能被移到了 request 中,在前面的时序图中就可以发现获取子容器都是通过 request 来分配的。
Context 准备 Servlet 的运行环境是在 Start 方法开始的,这个方法的代码片段如下:
清单 13. StandardContext.start
public synchronized void start() throws LifecycleException { ……… if( !initialized ) { try { init(); } catch( Exception ex ) { throw new LifecycleException("Error initializaing ", ex); } } ……… lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); setAvailable(false); setConfigured(false); boolean ok = true; File configBase = getConfigBase(); if (configBase != null) { if (getConfigFile() == null) { File file = new File(configBase, getDefaultConfigFile()); setConfigFile(file.getPath()); try { File appBaseFile = new File(getAppBase()); if (!appBaseFile.isAbsolute()) { appBaseFile = new File(engineBase(), getAppBase()); } String appBase = appBaseFile.getCanonicalPath(); String basePath = (new File(getBasePath())).getCanonicalPath(); if (!basePath.startsWith(appBase)) { Server server = ServerFactory.getServer(); ((StandardServer) server).storeContext(this); } } catch (Exception e) { log.warn("Error storing config file", e); } } else { try { String canConfigFile = (new File(getConfigFile())).getCanonicalPath(); if (!canConfigFile.startsWith (configBase.getCanonicalPath())) { File file = new File(configBase, getDefaultConfigFile()); if (copy(new File(canConfigFile), file)) { setConfigFile(file.getPath()); } } } catch (Exception e) { log.warn("Error setting config file", e); } } } ……… Container children[] = findChildren(); for (int i = 0; i < children.length; i++) { if (children[i] instanceof Lifecycle) ((Lifecycle) children[i]).start(); } if (pipeline instanceof Lifecycle) ((Lifecycle) pipeline).start(); ……… } |
它主要是设置各种资源属性和管理组件,还有非常重要的就是启动子容器和 Pipeline。
我们知道 Context 的配置文件中有个 reloadable 属性,如下面配置:
清单 14. Server.xml
<Context path="/library" docBase="D:\projects\library\deploy\target\library.war" reloadable="true" /> |
当这个 reloadable 设为 true 时,war 被修改后 Tomcat 会自动的重新加载这个应用。如何做到这点的呢 ? 这个功能是在 StandardContext 的 backgroundProcess 方法中实现的,这个方法的代码如下:
清单 15. StandardContext. backgroundProcess
public void backgroundProcess() { if (!started) return; count = (count + 1) % managerChecksFrequency; if ((getManager() != null) && (count == 0)) { try { getManager().backgroundProcess(); } catch ( Exception x ) { log.warn("Unable to perform background process on manager",x); } } if (getLoader() != null) { if (reloadable && (getLoader().modified())) { try { Thread.currentThread().setContextClassLoader (StandardContext.class.getClassLoader()); reload(); } finally { if (getLoader() != null) { Thread.currentThread().setContextClassLoader (getLoader().getClassLoader()); } } } if (getLoader() instanceof WebappLoader) { ((WebappLoader) getLoader()).closeJARs(false); } } } |
它会调用 reload 方法,而 reload 方法会先调用 stop 方法然后再调用 Start 方法,完成 Context 的一次重新加载。可以看出执行 reload 方法的条件是 reloadable 为 true 和应用被修改,那么这个 backgroundProcess 方法是怎么被调用的呢?
这个方法是在 ContainerBase 类中定义的内部类 ContainerBackgroundProcessor 被周期调用的,这个类是运行在一个后台线程中,它会周期的执行 run 方法,它的 run 方法会周期调用所有容器的 backgroundProcess 方法,因为所有容器都会继承 ContainerBase 类,所以所有容器都能够在 backgroundProcess 方法中定义周期执行的事件。
Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。
Wrapper 的实现类是 StandardWrapper,StandardWrapper 还实现了拥有一个 Servlet 初始化信息的 ServletConfig,由此看出 StandardWrapper 将直接和 Servlet 的各种信息打交道。
下面看一下非常重要的一个方法 loadServlet,代码片段如下:
清单 16. StandardWrapper.loadServlet
public synchronized Servlet loadServlet() throws ServletException { ……… Servlet servlet; try { ……… ClassLoader classLoader = loader.getClassLoader(); ……… Class classClass = null; ……… servlet = (Servlet) classClass.newInstance(); if ((servlet instanceof ContainerServlet) && (isContainerProvidedServlet(actualClass) || ((Context)getParent()).getPrivileged() )) { ((ContainerServlet) servlet).setWrapper(this); } classLoadTime=(int) (System.currentTimeMillis() -t1); try { instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet); if( System.getSecurityManager() != null) { Class[] classType = new Class[]{ServletConfig.class}; Object[] args = new Object[]{((ServletConfig)facade)}; SecurityUtil.doAsPrivilege("init",servlet,classType,args); } else { servlet.init(facade); } if ((loadOnStartup >= 0) && (jspFile != null)) { ……… if( System.getSecurityManager() != null) { Class[] classType = new Class[]{ServletRequest.class, ServletResponse.class}; Object[] args = new Object[]{req, res}; SecurityUtil.doAsPrivilege("service",servlet,classType,args); } else { servlet.service(req, res); } } instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet); ……… return servlet; } |
它基本上描述了对 Servlet 的操作,当装载了 Servlet 后就会调用 Servlet 的 init 方法,同时会传一个 StandardWrapperFacade 对象给 Servlet,这个对象包装了 StandardWrapper,ServletConfig 与它们的关系图如下:
图 13. ServletConfig 与 StandardWrapperFacade、StandardWrapper 的关系

Servlet 可以获得的信息都在 StandardWrapperFacade 封装,这些信息又是在 StandardWrapper 对象中拿到的。所以 Servlet 可以通过 ServletConfig 拿到有限的容器的信息。
当 Servlet 被初始化完成后,就等着 StandardWrapperValve 去调用它的 service 方法了,调用 service 方法之前要调用 Servlet 所有的 filter。
Tomcat 还有其它重要的组件,如安全组件 security、logger 日志组件、session、mbeans、naming 等其它组件。这些组件共同为 Connector 和 Container 提供必要的服务。
相关推荐
亲测可用
资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。
【项目资源】: 物联网项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
【项目资源】: 单片机项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
【项目资源】: 单片机项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
【项目资源】: 物联网项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
IDE护眼主题套件
内容概要:文章详细介绍了基于Matlab/Simulink构建的增程式电动车仿真模型。该模型由电池、电机、发动机、整车动力学、控制策略和驾驶员模块六大组件构成,重点在于各模块间的能量流动逻辑。文中特别强调了功率跟随控制策略,通过PID闭环控制使发动机功率与电池需求动态匹配,优化了燃油经济性和SOC控制精度。此外,模型采用开放式架构,所有参数通过m脚本集中管理,便于修改和扩展。文章展示了模型在典型工况下的性能表现,并突出了其在科研和工程应用中的灵活性和实用性。; 适合人群:对新能源汽车技术感兴趣的工程师、研究人员以及高校相关专业师生。; 使用场景及目标:①用于研究增程式电动车的能量管理策略;②作为教学案例帮助学生理解复杂系统的建模方法;③为实际工程项目提供可复用的仿真平台。; 阅读建议:读者应重点关注模型的架构设计和关键控制算法实现,同时结合提供的代码片段进行实践操作,以便更好地掌握增程式电动车的工作原理及其优化方法。
51a30-main.zip
内容概要:本文详细介绍了多种类型的数据库索引及其应用场景,包括普通索引、唯一性索引、单个索引、复合索引、聚簇索引、非聚簇索引、主索引、外键索引、全文索引和空间索引。每种索引都有其独特的定义、要点和适用场景,并附有具体的SQL代码示例。此外,文章还对比了InnoDB和MyISAM两种存储引擎的特点,解释了脏读、不可重复读、可重复读和幻读的概念,并讨论了SQL优化的方法以及数据库事务的ACID特性。 适合人群:具备一定数据库基础知识的开发者、数据库管理员以及参与数据库设计和优化的技术人员。 使用场景及目标:①帮助开发者选择合适的索引类型以提高查询效率;②理解不同存储引擎的特点,选择最适合应用场景的存储引擎;③掌握事务隔离级别的概念,避免数据不一致问题;④学习SQL优化技巧,提升数据库性能;⑤理解ACID特性,确保数据库操作的一致性和可靠性。 阅读建议:本文内容较为全面且深入,建议读者结合实际项目需求,重点理解不同类型索引的应用场景,掌握SQL优化的基本原则,并熟悉事务处理的最佳实践。
内容概要:本文详细介绍了MATLAB中优化算法的实现方法,涵盖确定性算法(如梯度下降法)和随机性算法(如遗传算法、粒子群优化)。文章首先讲解了梯度下降法和MATLAB优化工具箱的应用,展示了如何使用fmincon解决约束优化问题。接着,文章深入探讨了线性规划、非线性规划和多目标优化的理论和实践,提供了具体的MATLAB代码示例。此外,文中还介绍了遗传算法、粒子群优化和模拟退火算法的原理及应用,并通过实例展示了这些算法在实际问题中的使用。最后,文章讨论了优化算法在工程、金融和机器学习领域的高级应用,以及调试和优化的常见策略。 适合人群:具备一定编程基础,对优化算法感兴趣的工程师、研究人员和学生。 使用场景及目标:①理解优化算法的基础理论和实现方法;②掌握MATLAB优化工具箱的使用,解决线性、非线性、多目标优化问题;③学习遗传算法、粒子群优化和模拟退火算法的具体应用;④提高优化算法的性能和可靠性,解决实际工程、金融和机器学习问题。 阅读建议:本文内容丰富,涉及多种优化算法及其MATLAB实现,建议读者先掌握基本的优化理论和MATLAB编程基础,再逐步深入学习各类算法的具体应用。在学习过程中,结合提供的代码示例进行实践,并尝试调整参数以优化算法性能。
this is for myself learn coding, change a pc debug.
项目资源包含:可运行源码+sql文件 适用人群:学习不同技术领域的小白或进阶学习者;可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 项目具有较高的学习借鉴价值,也可拿来修改、二次开发。 有任何使用上的问题,欢迎随时与博主沟通,博主看到后会第一时间及时解答。 开发语言:Python 框架:django Python版本:python3.8 数据库:mysql 5.7 数据库工具:Navicat 开发软件:PyCharm 浏览器:谷歌浏览器
【项目资源】: 单片机项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
内容概要:本文深入探讨了MMC型STATCOM/SVG的核心技术和调试技巧,重点讲解了载波移相调制(CPS-PWM)和电压均衡控制两大关键技术。载波移相调制通过为每个子模块设置不同的载波相位差,有效降低谐波含量并优化开关频率。电压均衡则分为桥臂内、桥臂间和相间三个层次,分别采用动态排序、比例控制和零序电压注入等方法,确保系统稳定运行。文章还分享了多个实战经验,如低压调试、红外热像仪检测以及避免参数设置不当引发的问题。; 适合人群:从事电力电子领域,特别是参与STATCOM/SVG项目的设计、开发和调试的技术人员。; 使用场景及目标:①理解MMC型STATCOM/SVG的工作原理和技术细节;②掌握载波移相调制的具体实现方法;③学习电压均衡控制的各种策略及其应用场景;④获取实际调试过程中常见问题的解决方案。; 阅读建议:本文涉及大量技术细节和实战经验,建议读者结合实际项目进行阅读,重点关注载波移相调制和电压均衡控制的具体实现,并参考提供的代码片段进行实践。
liangmmm_finalll.scdoc
内容概要:本文详细介绍了Solidity语言的核心概念和语法特性,涵盖结构体、函数修改器、事件、类型系统、数组、映射、操作符、合约可见性、构造函数、抽象合约、接口、继承、控制结构、异常处理和keccak256哈希函数等内容。通过这些知识点的讲解,帮助开发者理解如何构建高效、安全的智能合约。; 适合人群:对区块链开发感兴趣,尤其是希望深入了解以太坊智能合约开发的初学者及有一定编程基础的研发人员。; 使用场景及目标:①掌握Solidity语言的基本语法和高级特性,如结构体、函数修改器、事件等;②理解合约的可见性、继承、接口等面向对象编程特性;③学会使用keccak256等安全机制保障智能合约的安全性;④能够运用控制结构和异常处理编写健壮的合约逻辑。; 阅读建议:建议读者从基础语法开始逐步深入,结合实际案例进行练习。尤其要注意合约的安全性和性能优化,避免常见的漏洞和错误。在学习过程中,应多参考官方文档和其他优质资料,不断巩固和拓展知识体系。
原型模式课上代码.zip
内容概要:文章介绍了利用Simulink搭建锂电池组主动均衡系统的原理与实现方法。通过双向DC-DC主动均衡电路和模糊控制算法,解决了储能项目中电池包SOC(荷电状态)差异过大的问题。文中详细解释了关键代码逻辑,包括均衡状态切换、模糊控制器规则配置以及动态子系统使能技术的应用。特别提到当SOC最大差值超过设定阈值时,系统会自动启动均衡,并根据差值大小智能调整均衡电流。仿真结果显示,在处理突发状况(如某电池SOC突然下降)时,该系统能在短时间内恢复平衡。此外,文章还分享了调试过程中的一些实用技巧,如设置合理的均衡电流限值。; 适合人群:从事锂电池管理系统研发的技术人员,尤其是有一定MATLAB/Simulink使用经验的工程师。; 使用场景及目标:①解决储能系统中锂电池组SOC不一致的问题;②优化现有均衡策略,提高均衡效率;③学习如何在Simulink中实现复杂控制算法。; 其他说明:本文不仅提供了详细的代码示例和技术细节,还通过生动的比喻帮助读者更好地理解复杂的控制逻辑。建议读者在实践中结合理论知识进行调试和验证。
【项目资源】: 物联网项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。