zoukankan      html  css  js  c++  java
  • How Tomcat Works A Simple Servlet Container

    这一章和第一章的区别就是对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向下转型。

  • 相关阅读:
    react使用react-hot-loader实现局部热更新
    React的环境搭建以及脚手架的安装
    Git常用命令及方法大全
    centos7.6
    Spring Boot 如何提升服务吞吐量?
    RabbitMQ 的核心概念,看了必懂!
    Jenkins 自动化部署 Java 项目,厉害~
    Spring Boot 太狠了,一口气发布了 3 个版本!
    牛逼哄哄的 RabbitMQ 到底有啥用?
    为什么 HTTPS 是安全的?图文详解!
  • 原文地址:https://www.cnblogs.com/likely/p/7289721.html
Copyright © 2011-2022 走看看