这一章和第一章的区别就是对servlet的支持。我们看下是怎么做的。
1)首先Response和Request这两个类分别实现了ServletResponse和ServletRequest接口,这两个接口和后面用到的Servlet接口都在javax.servlet这个package下面,需要添加servlet-api这个第三方依赖到classpath才能访问,这里用的是servlet-api-2.5。所有新加的方法除了Response.getWriter() 都没有任何处理,因为暂时没有用到。
Response.getWriter():
@Override public PrintWriter getWriter() throws IOException { writer = new PrintWriter(output, true); return writer; }
实例化了一个PrintWriter对象,第一个参数就是request的OutputStream,第二个参数表示是否自动flush。
2)对于静态资源请求和servlet请求分别创建了StaticResourceProcessor和ServletProcessor1。
StaticResourceProcessor:
import java.io.IOException; public class StaticResourceProcessor { public void process(Request request, Response response) { try { response.sendStaticResource(); } catch (IOException e) { e.printStackTrace(); } } }
这里比较简单,就是条用response的sendStaticResource这个方法。
ServletProcessor1:
import java.io.File; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandler; import javax.servlet.Servlet; 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 { URL[] urls = new URL[1]; URLStreamHandler streamHandler = null; File classPath = new File(Constants.WEB_ROOT); String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString(); 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(request, response); } catch (Throwable e) { System.out.println(e.toString()); } } }
这段代码的主要目的就是通过Request的uri找到对应的Servlet类,通过URLClassLoader从文件加载这个类并通过反射实例化一个Servlet对象,然后通过Servlet对象的service方法处理请求。servlet文件暂时都是放在webroot这个文件夹。所以很明显,目前只能根据uri是否是以/servlet/开头来判断一个请求是否是servlet请求。而且对于每个请求,都必须创建一个Servlet实例。
3)然后修改HttpServer这个类,对于Servlet和静态资源请求使用不同的processor:
public void await() { ServerSocket serverSocket = null; int port = 8080; try { serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); } catch (IOException e) { e.printStackTrace(); System.exit(1); } // Loop waiting for a request while (!shutdown) { Socket socket = null; InputStream input = null; OutputStream output = null; try { socket = serverSocket.accept(); input = socket.getInputStream(); output = socket.getOutputStream(); // create chapter1.Request object and parse Request request = new Request(input); request.parse(); // create chapter1.Response object Response response = new Response(output); response.setRequest(request); if (request.getUri().startsWith("/servlet/")) { ServletProcessor1 processor = new ServletProcessor1(); processor.process(request, response); } else { StaticResourceProcessor processor = new StaticResourceProcessor(); processor.process(request, response); } // Close the socket socket.close(); //check if the previous URI is a shutdown command shutdown = request.getUri().equals(SHUTDOWN_COMMAND); } catch (Exception e) { e.printStackTrace(); continue; } } }
4)在webroot下创建测试用的PrimitiveServlet:
import java.io.IOException; import java.io.PrintWriter; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class PrimitiveServlet implements Servlet { @Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println("init"); } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("from service"); PrintWriter out = servletResponse.getWriter(); out.println("Hello, Roses are red"); out.print("Violets are blue"); } @Override public String getServletInfo() { return null; } @Override public void destroy() { System.out.println("destroy"); } }
启动HttpServelt访问http://localhost:8080/servlet/PrimitiveServlet 会发现(火狐)浏览器打印出了:
Hello, Roses are red
我们会发现下面这一行:
out.print("Violets are blue");
并没有打印出来,这个问题会在后面解决。
5)使用门面模式隐藏request和response的方法
这部分是讲,我们在调用servelt的时候:
Servlet servlet = null; try { servlet = (Servlet) myClass.newInstance(); servlet.service(request, response); } catch (Throwable e) { System.out.println(e.toString()); }
直接传入了request和response,那么request和response里面一些public但是又不应该被servlet访问的方法可以被访问到。虽然Servlet.service的签名是:
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
但是在PrimitiveServlet里面还是可以通过向下转型得到一个Request类型的对象:
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("from service"); PrintWriter out = servletResponse.getWriter(); out.println("Hello, Roses are red"); out.print("Violets are blue"); Request request = (Request) servletRequest; request.xxx(); // others }
这样Request内部不希望被Servlet访问但是又必须是public的方法就暴露了。所以作者在这里使用门面模式把request和response做了封装,以request为例,新增了RequestFacade:
public class RequestFacade implements ServletRequest { private ServletRequest request; public RequestFacade(ServletRequest request) { this.request = request; } @Override public Object getAttribute(String s) { return request.getAttribute(s); } @Override public Enumeration getAttributeNames() { return request.getAttributeNames(); } //..... }
同样实现了ServletRequest接口,同时内部保留一个Request类型的引用,所有的方法都转发给真正的request。Response也是类似的处理。重构之后的ServletProcessor1:
Servlet servlet = null; RequestFacade requestFacade = new RequestFacade(request); ResponseFacade responseFacade = new ResponseFacade(response); try { servlet = (Servlet) myClass.newInstance(); servlet.service(requestFacade, responseFacade); } catch (Throwable e) { System.out.println(e.toString()); }
这里传给Servlet.service()的就是Facade了,不必再担心Servlet里面会对request和respone向下转型。