1. 请求的模型映射
Mapper组件主要的职责是负责Tomcat的请求路由,每个客户端的请求到达Tomcat后,都将由Mapper路由到对应的处理逻辑(Servlet)上。
在Tomcat的结构中有两部分会包含Mapper组件,一个是Connector组件,称为全局路由Mapper;另外一个是Context组件,称为局部路由Mapper。本章将深入探讨Tomcat的路由模块Mapper组件。
从浏览器输入一个地址发送请求给Tomcat服务器,是否可以从请求链接分析出对应的层级?很明显是可以的。
对应配置如下
<Host name="www.baidu.com" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Context path="/web1" docBase="/xxx"/>
....
2. Mapper的实现
Mapper组件的核心功能是提供请求路径的路由映射,根据某个请求路径,通过计算得到相应的Servlet(Wrapper)。
以Mapper作为映射的入口,按照容器等级,首先Mapper组件会包含了N个Host容器的引用,然后每个Host会有N个Context容器的引用,最后每个Context容器包含N个Wrapper容器的引用。例如,如果使用Mapper组件查找tomcat.apache. org/tomcat-7.0-doc/search,它首先会匹配名为tomcat.apache.org的Host,然后从中继续匹配名为tomcat-7.0-doc的Context,最后匹配名为search的Wrapper(Servlet)。
public final class Mapper {
// ----------------------------------------------------- Instance Variables
/**
包含虚拟主机定义的数组。
*/
//包私有,以方便测试
volatile MappedHost[] hosts = new MappedHost[0];
/**
*默认主机名。
*/
private String defaultHostName = null;
private volatile MappedHost defaultHost = null;
/**
*支持从上下文对象到上下文版本的映射
* RequestDispatcher映射。
*/
private final Map<Context, ContextVersion> contextObjectToContextVersionMap =
new ConcurrentHashMap<>();
其中MapElement提供一个基础的键-值对模型,name为容器的名称,object为具体的容器。
protected abstract static class MapElement<T> {
public final String name;
public final T object;
public MapElement(String name, T object) {
this.name = name;
this.object = object;
}
}
MappedHost 提供Host映射模型,它继承MapElement,且包含若干Context映射。
protected static final class MappedHost extends MapElement<Host> {
public volatile ContextList contextList;
/**
* Link to the "real" MappedHost, shared by all aliases.
*/
private final MappedHost realHost;
/**
* Links to all registered aliases, for easy enumeration. This field
* is available only in the "real" MappedHost. In an alias this field
* is <code>null</code>.
*/
private final List<MappedHost> aliases;
/**
* Constructor used for the primary Host
*
* @param name The name of the virtual host
* @param host The host
*/
public MappedHost(String name, Host host) {
super(name, host);
realHost = this;
contextList = new ContextList();
aliases = new CopyOnWriteArrayList<>();
}
/**
* Constructor used for an Alias
*
* @param alias The alias of the virtual host
* @param realHost The host the alias points to
*/
public MappedHost(String alias, MappedHost realHost) {
super(alias, realHost.object);
this.realHost = realHost;
this.contextList = realHost.contextList;
this.aliases = null;
}
public boolean isAlias() {
return realHost != this;
}
public MappedHost getRealHost() {
return realHost;
}
public String getRealHostName() {
return realHost.name;
}
public Collection<MappedHost> getAliases() {
return aliases;
}
public void addAlias(MappedHost alias) {
aliases.add(alias);
}
public void addAliases(Collection<? extends MappedHost> c) {
aliases.addAll(c);
}
public void removeAlias(MappedHost alias) {
aliases.remove(alias);
}
}
MappedContext 提供Context映射模型,它继承MapElement,且包含不同类型的Wrapper(Servlet):默认Servlet、精确匹配Servlet、通配符Servlet和扩展Servlet。除此之外,还有欢迎页资源和path。
protected static final class MappedContext extends MapElement<Void> {
public volatile ContextVersion[] versions;
public MappedContext(String name, ContextVersion firstVersion) {
super(name, null);
this.versions = new ContextVersion[] { firstVersion };
}
}
protected static final class ContextVersion extends MapElement<Context> {
public final String path;
public final int slashCount;
public final WebResourceRoot resources;
public String[] welcomeResources;
public MappedWrapper defaultWrapper = null;
public MappedWrapper[] exactWrappers = new MappedWrapper[0];
public MappedWrapper[] wildcardWrappers = new MappedWrapper[0];
public MappedWrapper[] extensionWrappers = new MappedWrapper[0];
public int nesting = 0;
private volatile boolean paused;
public ContextVersion(String version, String path, int slashCount,
Context context, WebResourceRoot resources,
String[] welcomeResources) {
super(version, context);
this.path = path;
this.slashCount = slashCount;
this.resources = resources;
this.welcomeResources = welcomeResources;
}
public boolean isPaused() {
return paused;
}
public void markPaused() {
paused = true;
}
}
MappedWrapper 提供Wrapper映射模型,它继承MapElement。
protected static class MappedWrapper extends MapElement<Wrapper> {
public final boolean jspWildCard;
public final boolean resourceOnly;
public MappedWrapper(String name, Wrapper wrapper, boolean jspWildCard,
boolean resourceOnly) {
super(name, wrapper);
this.jspWildCard = jspWildCard;
this.resourceOnly = resourceOnly;
}
}
3. 路由Mapper
- 局部路由Mapper
局部路由Mapper是指提供了Context容器内部路由导航功能的组件。它只存在于Context容器中,用于记录访问资源与Wrapper之间的映射,每个Web应用都存在自己的局部路由Mapper组件。
局部路由Mapper只能在同一个Web应用内进行转发路由,而不能实现跨Web应用的路由。如果要实现跨Web应用,需要用到重定向功能,让客户端重定向到其他主机或其他Web应用上。而对于从客户端到服务端的请求,则需要全局路由Mapper组件的参与。 - 全局路由Mapper
位于Tomcat的Connector组件中。通过它能对Host、Context、Wrapper等路由,即对于一个完整的请求地址,它能定位到指定的Host容器、Context容器以及Wrapper容器。所以全局路由Mapper拥有Tomcat容器完整的路由映射,负责完整的请求地址路由功能。
4. JNDI简介
JNDI即Java命名和目录接口(Java Naming and Directory Interface)。
其实一直对JNDI很疑惑。因为它说的是它提供了一组接口、类和关于命名空间的概念。接口,类,命名空间都知道是个啥,组合其实就不说人话了。
JNDI是基于提供商技术,它暴露一个API和一个服务供应接口(SPI)。它将名称和对象联系起来,使我们可以用名称访问对象。我们可以把JNDI简单地看成里面封装了一个名称到实体对象的映射,通过字符串可以方便得到想要的对象资源。
那么什么又是SPI?
初次理解SPI是在SpringBoot的自动配置上,每个starter的MATE-INF下有一个spring.properties的文件,而SpringBoot每次启动就是将这些文件内的全限定类名对应的类加载到Spring容器中去。而SPI就是做的这样的事。
从架构上看,JNDI包含了一个API层及SPI层,SPI层提供了服务的具体实现,再通过JNDI的API暴露给Java应用程序使用,这就将各种服务复杂的细节屏蔽了,提供统一的接口供应用程序使用。
一言以蔽之,JNDI使得用户可以用字符串去访问组件、资源或服务,提供一个上下文环境。
JNDI的主要工作就是维护两个对象:命名上下文和命名对象。
在JNDI中存在两种命名对象形态:
①直接存在内存中的命名对象;
②使用时再根据指定类及属性信息创建的命名对象。
JNDI运行机制是围绕着NamingManager的这些类和接口是JNDI能正常运行的基础,所有的上下文都要实现Context接口。
javax.naming.Context:
总结一下就是用一个Context类将资源与名字绑定在一起,那么我可以在全局的任意地方拿到这个对象资源的引用。其他的就是一些底层与细节的处理,就不继续深究了,感兴趣的可以把书找过来读一读。
5. 在Tomcat中使用JNDI
在Tomcat中可以通过字符串标识获取某个资源,而这个资源包括很多种,例如数据库数据源、JMS、EJB等。其中,全局命名资源无法从Web应用中直接获取,但它可以通过资源连接间接获得;
通过两行简单的代码就可以查找到一个数据源对象,里面包含了所有数据库连接对象,同时也封装了对这些连接的维护,以充当连接池。由于使用的是URL模式,因此在构造InitialContext时并不需要传入任何参数。
在Tomcat中以JNDI形式配置某Web应用使用的数据源(局部):
1)将数据源相应的驱动包复制到Tomcat安装目录的子目录lib下面。
2)修改Tomcat安装目录的子目录conf下面的server.xml配置文件。
服务器的全局配置方式:
1)将相应的驱动包复制到Tomcat安装目录的子目录lib下面。2)修改Tomcat安装目录的子目录conf下面的server.xml配置文件。这次server.xml文件将有两个地方需要配置,一个是添加GlobalNamingResources节点,另一个是添加ResourceLink节点。
看上面的使用,就知道了这是JNDI的作用及好处。