本章呢是讲解如何开发一个简单的servlet容器,首先所有的servlet必须要实现javax.servlet.Servlet接口,如下:
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
在这五个方法中,init(),service(),destroy()是生命周期方法,当servlet类被装载初始化之后,会调用init()方法一次,在收到请求之前,init()方法必须完成。每当对此servlet发起请求的时候,都会调用service()方法,并传入ServletRequest与ServletResponse实例,前者包括了Http请求信息,后者封装了servlet应答信息。
简而言之,一个完整的servlet容器会对每个Http请求做如下工作:
- 当servlet被第一次请求时,加载servlet类,并调用init()方法(仅此一次)
- 为每次请求构建ServletRequest与ServletResponse实例
- 调用service方法,并传入前面那两个实例
当关闭servlet类时,调用destroy方法,释放资源
下面来说一下图中的这些类,HttpServer1和第一章差不多,只不过多了一个判断:
...
//如果uri是/servlet开头,就调用ServletProcecessor1的process方法,然后调用servlet的service方法,如果不是就直接访问静态资源
if (request.getUri().startsWith("/servlet/")) {
ServletProcessor1 processor = new ServletProcessor1();
processor.process(request, response);
}
else {
StaticResourceProcessor processor = new StaticResourceProcessor();
processor.process(request, response);
}
...
接下来是ServletProcecessor1的process方法
public void process(Request request, Response response) {
String uri = request.getUri();
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
URLClassLoader loader = null;
try {
// create a URLClassLoader
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
// the forming of repository is taken from the createClassLoader method in
// org.apache.catalina.startup.ClassLoaderFactory
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
// the code for forming the URL is taken from the addRepository method in
// org.apache.catalina.loader.StandardClassLoader class.
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
}
catch (IOException e) {
System.out.println(e.toString() );
}
Class myClass = null;
try {
//根据serlet的名字获取其Class信息
myClass = loader.loadClass(servletName);
}
catch (ClassNotFoundException e) {
System.out.println(e.toString());
}
Servlet servlet = null;
try {
//实例化该类,并且调用其service方法
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) request, (ServletResponse) response);
}
catch (Exception e) {
System.out.println(e.toString());
}
catch (Throwable e) {
System.out.println(e.toString());
}
}
该方法首先根据包的路径创建一个URLClassLoader,然后调用loadClass方法获取该类的Class信息(根据从uri获取的类名),再实例化该类,最后调用service方法,传入request,response实例。
仔细观察后会发现,这上面的代码有很严重的安全隐患:
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) request, (ServletResponse) response);
}
在这里,request与response都被强制向上转换成了ServletRequest与ServletResponse,并传入service方法,这也就意味着,在servlet内部,你能通过向下转换调用Request的process方法与Response的sendStaticResource方法(通过request与Response实例),在这两个方法是public的前提下。但是这两个方法又不能设置为private方法,因为外部需要调用这两个方法。怎么解决呢?
使用facade类(门面设计模式)
创建一个门面类,往构造器里传入Request实例,并向上转型为ServletRequest
public class RequestFacade implements ServletRequest {
private ServletRequest request = null;
public RequestFacade(Request request) {
this.request = request;
}
...
}
然后ServletProcess中的process方法就写成这样:
RequestFacade requestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new ResponseFacade(response);
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) requestFacade, (ServletResponse) responseFacade);
}
这样的话我们就构造了一个requestFacade实例传入service方法,那么Request中的parse方法就安全了(Response也一样)。