一个简单servlet容器
2.1 javax.servlet.Servlet接口
- Servlet编程需要使用javax.servlet和javax.servlet.http两个包下的接口和类
- 在所有的类中javax.servlet.Servlet接口是最重要的。所有的servlet程序都必须实现该接口或继承实现了该接口的类
- tomcat8中该接口如下:
-
package javax.servlet; import java.io.IOException; public interface Servlet { public void init(ServletConfig config) throws ServletException; public ServletConfig getServletConfig(); public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public String getServletInfo(); public void destroy(); }
- 在Servlet接口中,init()、service()和destroy()方法是和servlet生命周期相关的方法。当实例化某个servlet类后,servlet容器就会调用其init()方法进行初始化。servlet容器只会调用该方法一次,调用后则可以执行service()方法了。
- 在servlet接收任何请求之前,必须是经过初始化的。该方法可以进行覆盖,自定义初始化。
- 当servlet的一个客户端请求到达后,servlet容器就调用相应的servlet的service方法,并将 javax.servlet.ServletRequest对象和javax.servlet.ServletResponse对象作为参数传入。
- ServletRequest对象包含客户端的HTTP请求信息,ServletResponse对象则封装servlet的响应信息。
- 在整个servlet周期内,service会被多次调用。
- 在将servlet容器从服务中移除之前,servlet容器会调用servlet实例的destory方法。
- 使用PrimitiveServlet来测试servlet容器应用程序
-
import javax.servlet.*; import java.io.IOException; import java.io.PrintWriter; public class PrimitiveServlet implements Servlet { public void init(ServletConfig config) throws ServletException { System.out.println("init"); } public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { System.out.println("from service"); PrintWriter out = response.getWriter(); out.println("Hello. Roses are red."); out.print("Violets are blue."); } public void destroy() { System.out.println("destroy"); } public String getServletInfo() { return null; } public ServletConfig getServletConfig() { return null; } }
-
2.2应用程序1
- 对一个Servlet的每个HTTP请求,一个功能齐全的servlet容器需要做到以下几点:
- 当第一次调用某个servlet时,要载入该servlet类,并调用其init方法
- 针对每一个request请求,创建一个javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例
- 调用该servlet的service方法,将ServletRequest对象和ServletResponse对象作为参数传入
- 当关闭servlet类时,调用destory方法
- 接下来建立一个Servlet容器,功能如下:
- 等待HTTP请求
- 创建一个ServletRequest对象和一个ServletResponse对象
- 若请求静态资源,则调用StaticResourceProcessor对象的process方法,传入上面的两个对象
- 若请求servlet,则载入相应的servlet类,调用其service方法。
- 类关系UML图如下:
- 基于Java的Servlet容器实现,需要调用Java提供的有关接口,比如javax.servlet.ServletRequest和javax.servlet.ServletResponse。
- 任何一个Servlet都需要实现Servlet接口或者继承实现该接口的类。
- 具体的请求过程以及类型如图:
- 代码示例:
-
if (request.getUri().startsWith("/servlet/")) { ServletProcessor1 processor = new ServletProcessor1(); processor.process(request, response); } else { StaticResourceProcessor processor = new StaticResourceProcessor(); processor.process(request, response); }
-
在上面代码中可以看出对于静态资源使用StaticResourceProcessor容器,对于动态Servlet资源使用ServletProcessor1容器
-
package ex02.pyrmont; public class ServletProcessor1 { 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 { myClass = loader.loadClass(servletName); } catch (ClassNotFoundException e) { System.out.println(e.toString()); } Servlet servlet = null; try { 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()); } } }
-
上面代码通过newInstance创建了一个Servlet的实例,并调用了service方法,并传入参数request和response。但是该参数是向上转型的。
-
这通常是不安全的,因为外部人员可以将其向下转型为Request的对象,就可以调用其方法.解决方法是创建Request和Response的外观类(外观类和原类实现同一个接口,在外观类中创建私有接口对象用原类进行赋值即可)。而在调用接口对象时使用外观类就不会导致原类的方法泄露
-
示例如下:
-
package ex02.pyrmont; public class RequestFacade implements ServletRequest { private ServletRequest request = null; public RequestFacade(Request request) { this.request = request; } /* implementation of the ServletRequest*/ ... } package ex02.pyrmont; public class Request implements ServletRequest { private InputStream input; private String uri; public Request(InputStream input) { this.input = input; } public String getUri() { return uri; } private String parseUri(String requestString) { int index1, index2; index1 = requestString.indexOf(' '); if (index1 != -1) { index2 = requestString.indexOf(' ', index1 + 1); if (index2 > index1) return requestString.substring(index1 + 1, index2); } return null; } public void parse() { // Read a set of characters from the socket StringBuffer request = new StringBuffer(2048); int i; byte[] buffer = new byte[2048]; try { i = input.read(buffer); } catch (IOException e) { e.printStackTrace(); i = -1; } for (int j=0; j<i; j++) { request.append((char) buffer[j]); } System.out.print(request.toString()); uri = parseUri(request.toString()); } /* implementation of the ServletRequest*/ }
-
这样在上面service方法中使用RequestFacade类的实例向上转型就不会出现问题了。
-
运行结果如下:
-
-