前面一篇文章分析了servlet里init方法,包括init方法本身以及调用的方法源代码,这篇文章继续,按照servlet的生命周期,接下去会调用servlet的service方法:
@Override
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
final HttpServerHelper helper = getServer(request);
if (helper != null) {
helper.handle(createCall(helper.getHelped(), request, response));
} else {
log("[Noelios Restlet Engine] - Unable to get the Restlet HTTP server connector. Status code 500 returned.");
response.sendError(500);
}
}
让我们一步一步的分析下去,首先看getServer方法:
public HttpServerHelper getServer(HttpServletRequest request) {
HttpServerHelper result = this.helper;
if (result == null) {
synchronized (ServerServlet.class) {
if (result == null) {
// Find the attribute name to use to store the server
// reference
final String serverAttributeName = getInitParameter(
NAME_SERVER_ATTRIBUTE,
NAME_SERVER_ATTRIBUTE_DEFAULT);
// Look up the attribute for a target
result = (HttpServerHelper) getServletContext()
.getAttribute(serverAttributeName);
if (result == null) {
result = createServer(request);
getServletContext().setAttribute(serverAttributeName,
result);
}
this.helper = result;
}
}
}
return result;
}
上面这段代码首先还是根据server属性名字,查找servlet context里是否已经有存放的server,如果有就直接取出来,如果没有,就创建一个server,然后把这个新的server放到servlet context里,以便下次的时候不需要重新创建,这个动作之前的component已经重复过。如果第一次请求,肯定是没有server存在,那么接下来,看看是怎么创建一个server的。由于这个方法的代码很长,所以,只贴出部分代码做一个解释:
在这个方法里,首先得到Component,调用的getComponent,之前文章的分析,我们知道,这个时候已经有component放到servlet context。
final Component component = getComponent();
既然有了component,那么接下去就会创建一个server以及一个server helper,需要说明的是restlet代码里有很多helper,如ComponentHelper,ApplicationHelper,HttpServerHelper等等。
final Server server = new Server(component.getContext()
.createChildContext(), (List<Protocol>) null, request
.getLocalAddr(), request.getLocalPort(), component);
result = new HttpServerHelper(server);
然后,获取请求的path,如/restlet/resources/customers/1:
final String uriPattern = request.getContextPath()
+ request.getServletPath();
其中restlet是context path,而/resources是servlet path,而/customers/1则是我们定义的映射的URI。
接下来会判断当前的Component是否是default component(关于default component的判断,在上篇文章中解释过),如果是default compoent就将之前在init方法里创建的application attach到当前component的默认的Host上(虚拟主机)。否则,就会启动另外的一个流程:
1.首先会判断一个参数org.restlet.autoWire是否是true,那么这个参数代表什么呢?看看源码里面的注释:
org.restlet.autoWire是Servlet context的一个参数名字,并包含一个布尔型值,值为true表示所有的applications
将被用Servlet上下文的路径值attach到Component的虚拟主机(virtual hosts)上
代码如下:
final String autoWire = getInitParameter(AUTO_WIRE_KEY,
AUTO_WIRE_KEY_DEFAULT);
if (AUTO_WIRE_KEY_DEFAULT.equalsIgnoreCase(autoWire)) {
根据我在开始时候列出的环境,没有在web.xml中配置一个名为org.restlet.autoWire的参数,所以,这里autoWire会取默认值为true。而常量AUTO_WIRE_KEY_DEFAULT的值也为true。
2.分析映射的URL是否需要增加Context path或者Servlet path
源码里面定义了两个变量:
boolean addContextPath = false;
boolean addFullServletPath = false;
顾名思义,第一个是判断是否需要增加context path,第二个是判断是否需要增加servlet path。下面的代码就是判断的具体逻辑:
for (final Route route : component.getDefaultHost()
.getRoutes()) {
if (route.getTemplate().getPattern() == null) {
addFullServletPath = true;
continue;
}
if (!route.getTemplate().getPattern().startsWith(
uriPattern)) {
if (!route.getTemplate().getPattern()
.startsWith(request.getServletPath())) {
addFullServletPath = true;
} else {
addContextPath = true;
break;
}
}
}
代码比较简单,不多做解释,接下来,如果对于默认主机的定义的所有路由的URL不需要增加Context path,则会继续检查attach到该component的所有的别的虚拟主机是否需要增加Context path和Servlet path:
if (!addContextPath) {
for (final VirtualHost virtualHost : component.getHosts()) {
if (virtualHost.getRoutes().isEmpty()) {
// Case where the default host has a default
// route (with an empty pattern).
addFullServletPath = virtualHost .getDefaultRoute() != null;
} else {
for (final Route route : virtualHost.getRoutes()) {
if (route.getTemplate().getPattern() == null) {
addFullServletPath = true;
continue;
}
if (!route.getTemplate().getPattern().startsWith(uriPattern)) {
if (!route.getTemplate().getPattern().startsWith(request.getServletPath())) {
addFullServletPath = true;
} else {
addContextPath = true;
break;
}
}
}
}
if (addContextPath) {
break;
}
}
}
3. 改变各种路由
经过上面两步,如果需要增加Context path或者Servlet path,则会判断是那种类型:
if (addContextPath) {
offsetPath = request.getContextPath();
} else {
offsetPath = uriPattern;
}
接下来会对所有的路由进行转化,包括默认的虚拟主机的默认路由,默认虚拟主机的路由,组件的别的虚拟主机的默认路由以及组件的别的虚拟主机的路由。这里为了节省篇幅,仅给出一种情况的代码:
// Shift the routes of the default host
for (final Route route : component.getDefaultHost()
.getRoutes()) {
log("[Noelios Restlet Engine] - Attaching restlet: "
+ route.getNext()
+ " to URI: "
+ offsetPath
+ route.getTemplate().getPattern());
route.getTemplate().setPattern(
offsetPath
+ route.getTemplate().getPattern());
}
ok,分析完这几段代码后,结合我之前给出的环境,因为定义url映射时候,没有Context path和servlet path,只是单纯的映射部分,如/customers/1,而不是/restlet/resources/customers/1或者/resources/customers/1.所以,经过上述代码,我们的定义的url会变成/{Context path}/{Servlet path}/{Mapping url}.
至此,一个Server就创建完成,然后系统会根据一个属性名字将它放到一个Servlet Context里。然后返回基于Server的一个Helper。