分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
前言
这里分析一个实际的请求是如何在Tomcat中被处理的,以及最后是怎么样找到要处理的Servlet的?当我们在浏览器中输入http://hostname:port/contextPath/servletPath
,前面的hostname与port用于建立tcp连接,由于Http也是基于Tcp协议的,所以这里涉及TCP连接的三次握手。后面的contextPath与servletPath则是与服务器进行请求的信息,contextPath指明了与服务器中哪个Context容器进行交互,服务器会根据这个URL与对应的Context容器建立连接,那么这个过程是如何实现的呢?
在Tomcat7(本文也是基于Tomcat7)中主要通过一个映射来完成的,这个映射的工作交给org.apache.tomcat.util.http.mapper.Mapper类来完成的,这个类保存了Container容器所有子容器的信息,在请求从Connector交给Container容器之前,Mapper会根据hostname和port将host容器与context容器设置到Request的mappingData属性中,这样在Connector的请求进入Container容器之前就知道了交给哪个容器了。
这段代码如下:
代码清单5-4:
// Virtual host mapping if (mappingData.host == null) { Host[] hosts = this.hosts; int pos = findIgnoreCase(hosts, host); if ((pos != -1) && (host.equalsIgnoreCase(hosts[pos].name))) { mappingData.host = hosts[pos].object; contexts = hosts[pos].contextList.contexts; nesting = hosts[pos].contextList.nesting; } else { if (defaultHostName == null) { return; } pos = find(hosts, defaultHostName); if ((pos != -1) && (defaultHostName.equals(hosts[pos].name))) { mappingData.host = hosts[pos].object; contexts = hosts[pos].contextList.contexts; nesting = hosts[pos].contextList.nesting; } else { return; } } } // Context mapping if (mappingData.context == null) { int pos = find(contexts, uri); if (pos == -1) { return; } int lastSlash = -1; int uriEnd = uri.getEnd(); int length = -1; boolean found = false; while (pos >= 0) { if (uri.startsWith(contexts[pos].name)) { length = contexts[pos].name.length(); if (uri.getLength() == length) { found = true; break; } else if (uri.startsWithIgnoreCase("/", length)) { found = true; break; } } if (lastSlash == -1) { lastSlash = nthSlash(uri, nesting + 1); } else { lastSlash = lastSlash(uri); } uri.setEnd(lastSlash); pos = find(contexts, uri); } uri.setEnd(uriEnd); if (!found) { if (contexts[0].name.equals("")) { context = contexts[0]; } } else { context = contexts[pos]; } if (context != null) { mappingData.contextPath.setString(context.name); } } if (context != null) { ContextVersion[] contextVersions = context.versions; int versionCount = contextVersions.length; if (versionCount > 1) { Object[] contextObjects = new Object[contextVersions.length]; for (int i = 0; i < contextObjects.length; i++) { contextObjects[i] = contextVersions[i].object; } mappingData.contexts = contextObjects; } if (version == null) { // Return the latest version contextVersion = contextVersions[versionCount - 1]; } else { int pos = find(contextVersions, version); if (pos < 0 || !contextVersions[pos].name.equals(version)) { // Return the latest version contextVersion = contextVersions[versionCount - 1]; } else { contextVersion = contextVersions[pos]; } } mappingData.context = contextVersion.object; mappingData.contextSlashCount = contextVersion.slashCount; } // Wrapper mapping if ((contextVersion != null) && (mappingData.wrapper == null)) { internalMapWrapper(contextVersion, uri, mappingData); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
下面启动服务器,在浏览器中输入http://localhost:8080/examples/jsp/jsp2/el/composite.jsp,断点调试可以mappingData.host属性为localhost
,mappingData.contextPath.setString(context.name)中context.name为examples
,mappingData.wrapperPath为/jsp/jsp2/el/composite.jsp
,这验证了mappingData属性的有效性,那么mappingData属性是如何设置到Request对象的属性中的呢?
通过org.apache.catalina.connector.Request的源码可以知道,其是通过setContextPath方法与setHost方法设置进去的,其源码如下:
代码清单5-5:
public void setHost(Host host) { mappingData.host = host; } public void setContextPath(String path) { if (path == null) { mappingData.contextPath.setString(""); } else { mappingData.contextPath.setString(path); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
由于请求是从Connector传过来的,而CoyoteAdapter是Connector中处理请求的最后一个类,那么设置这两个属性的代码肯定在CoyoteAdapter类中,果不其然:
代码清单5-6:
// This will map the the latest version by default connector.getMapper().map(serverName, decodedURI, version, request.getMappingData()); request.setContext((Context) request.getMappingData().context); request.setWrapper((Wrapper) request.getMappingData().wrapper); //Mapper的map方法 public void map(MessageBytes host, MessageBytes uri, String version, MappingData mappingData) throws Exception { if (host.isNull()) { host.getCharChunk().append(defaultHostName); } host.toChars(); uri.toChars(); internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
intenalMap方法执行就是代码清单5-4的内容,这样就把从Connector传入请求,并设置Request对象的mappingData属性的整个流程就打通了。还有一个疑问是为什么Mapper类中可以拥有Container所有子容器的信息呢?答案需要回到Tomcat启动过程图的第21步的startIntenal方法了:
代码清单5-7:
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 engine = (Engine) connector.getService().getContainer(); addListeners(engine); Container[] conHosts = engine.findChildren(); for (Container conHost : conHosts) { Host host = (Host) conHost; if (!LifecycleState.NEW.equals(host.getState())) { // Registering the host will register the context and wrappers registerHost(host); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
这段代码就是将MapperListener作为一个监听者加到整个Container容器的每一个子容器中,这样任何一个子容器发生变化,MapperListener都将被通知,响应的mappingData属性也会改变。最后可以总结访问请求地址为http://localhost:8080/examples/composite.jsp的处理过程:
- 在端口8080启动Server,并通知Service完成启动,Service通知Connector完成初始化和启动的过程
- Connector首先收到这个请求,会调用ProtocolHandler完成http协议的解析,然后交给SocketProcessor处理,解析请求头,再交给CoyoteAdapter解析请求行和请求体,并把解析信息封装到Request和Response对象中
- 把请求(此时应该是Request对象,这里的Request对象已经封装了Http请求的信息)交给Container容器
- Container容器交给其子容器——Engine容器,并等待Engine容器的处理结果
- Engine容器匹配其所有的虚拟主机,这里匹配到Host
- 请求被移交给hostname为localhost的Host容器,host匹配其所有子容器Context,这里找到contextPath为/examples的Context容器。如果匹配不到就把该请求交给路径名为”“的Context去处理
- 请求再次被移交给Context容器,Context继续匹配其子容器Wrapper,由Wrapper容器加载composite.jsp对应的servlet,这里编译的servlet是basic_002dcomparisons_jsp.class文件
- Context容器根据后缀匹配原则*.jsp找到composite.jsp编译的java类的class文件
- Connector构建一个org.apache.catalina.connector.Request以及org.apache.catalina.connector.Response对象,使用反射调用Servelt的service方法
- Context容器把封装了响应消息的Response对象返回给Host容器
- Host容器把Response返回给Engine容器
- Engine容器返回给Connector
- Connetor容器把Response返回给浏览器
- 浏览器解析Response报文
- 显示资源内容
根据前面的内容,其中的映射关系是由MapperListener类完成的。