一步一步實現一個Web Server-04

老鐵們,又見面了。上一期中,我們封裝了Request和Response兩個類,使我們的服務器核心代碼doService()方法變得不那麼臃腫了。但目前還存在一個問題,doService方法裏還是摻雜了太多了業務邏輯在裏邊,按照職責單一原則,我們本期再進行抽象和封裝。現在,我們可以引入Servlet了。

public abstract class Servlet {

    /**Service方法*/
    public void service(Request request, Response response) {}
}

簡單一點,我們就先定義一個抽象類,然後定義service方法。按照上一期的例子,我們分別定義LoginServlet,IndexServlet,ErrorServlet,它們都繼承自Servlet,然後在子類中覆寫service方法。然後我們就可以將本來在Bootstrap的doService()中執行的代碼根據業務邏輯,放在不同的Servlet中的service方法了。

public class LoginServlet extends Servlet {

    @Override
    public void service(Request request, Response response) {
        response.println("<html>");
        response.println("<head>");
        response.println("<meta charset=\"utf-8\">");
        response.println("<title>Lyn Server 登錄頁面</title>");
        response.println("<body>");
        response.println("<input type=\"text\" placeholder=\"輸入用戶名\" />");
        response.println("<input type=\"password\" placeholder=\"輸入密碼\" />");
        response.println("<a href=\"index.html\" target=\"_self\"><button>登 錄</button></a>");
        response.println("</body>");
        response.println("</html>");
        response.send2Client(200);
    }
}

這樣,Bootstrap的doService()就更進一步精簡了

    /**
     * 對外提供服務
     */
    public void doService(Socket client) throws IOException {

       /**創建請求對象*/
        Request request = new Request(client);
        Response response = new Response(client);

        if (request.getRequestUrl().contains("/index")) {
            new IndexServlet().service(request,response);
        }else if (request.getRequestUrl().contains("/login")) {
            new LoginServlet().service(request, response);
        } else {
            new ErrorServlet().service(request, response);
        }
    }

怎麼樣?是不是更加有感覺了?還能精簡麼?答案是:必須能啊!現在我們是通過if else來實現的servlet分派。那如果我們服務的是一個規模稍大的系統,幾十個上百個servlet怎麼辦?相信聰明的你已經想到了——反射。我們可以把這些servlet配置在一個文件裏邊,然後根據請求的url去動態創建出來Servlet的實例,調用service方法。這樣豈不是非常美滋滋麼?OK,繼續。

那我們就定義個xml文件,命名爲web.xml吧。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">

    <!--登錄-->
    <servlet>
        <servlet-name>loginServlet</servlet-name>
        <servlet-class>com.jhhg.nova.servlet.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>loginServlet</servlet-name>
        <url-pattern>/login.do</url-pattern>
    </servlet-mapping>
    <!--首頁-->
    <servlet>
        <servlet-name>indexServlet</servlet-name>
        <servlet-class>com.jhhg.nova.servlet.IndexServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>indexServlet</servlet-name>
        <url-pattern>/index.do</url-pattern>
    </servlet-mapping>
    <!--404頁面-->
    <servlet>
        <servlet-name>errorServlet</servlet-name>
        <servlet-class>com.jhhg.nova.servlet.ErrorServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>errorServlet</servlet-name>
        <url-pattern>/error.do</url-pattern>
    </servlet-mapping>
</web-app>

接下來,就是要解析這個xml,然後拿到url跟servlet的映射關係,利用反射創建實例,調用方法就可以了。我們一步步來。

解析xml有兩種方式,SAX解析和Dom解析,一個按流,一個解析成樹。使用dom的話,需要引入額外的包,我們這裏就選擇使用sax來解析。

public static WebContext parseWebContext() {
        try {
            SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
            SAXParser saxParser = saxParserFactory.newSAXParser();
            WebXmlHandler webXmlHandler = new WebXmlHandler();
            saxParser.parse(Thread.currentThread().getContextClassLoader().
                    getResourceAsStream("com/jhhg/nova/web.xml"), webXmlHandler);
            WebContext webContext = new WebContext(webXmlHandler.getEntities(), webXmlHandler.getMappings());
            return webContext;
        } catch (Exception e) {
            e.printStackTrace();
            return null;

        }
    }

這樣我們就可以拿到webcontext了,顧名思義,我們將servlet的映射關係存放在了這個類中。

public class WebContext {
    /**web.xml中,一個servlet可以對應多個url-pattern,
     * 但url-pattern不允許有重複,而且一個url-pattern只能對應到一個servlet
     * */
    /** key=servlet-name value = servlet-class */
    private Map<String, String> entityMap = new HashMap<>();
    /** key = url-pattern value = servlet-name*/
    private Map<String, String> mappingMap = new HashMap<>();

    public WebContext(List<Entity> entities, List<Mapping> mappings) {

        for (Entity entity:entities){
            entityMap.put(entity.getName(),entity.getClz());
        }

        for (Mapping mapping: mappings) {
            for (String pattern:mapping.getPatterns()){
                mappingMap.put(pattern,mapping.getName());
            }
        }

    }

    public String getEntityMap(String pattern) {
        String servletName = mappingMap.get(pattern);
        return entityMap.get(servletName);
    }
}

這樣,我們就可以根據url-pattern拿到對應的servlet類了,接下來就是通過反射拿到url對應的Servlet實例:

        /**通過反射拿到Servlet的實例*/
        String servletClassName = webContext.getEntityMap(request.getRequestUrl());
        Class clz = Class.forName(servletClassName);
        Servlet servlet = (Servlet) clz.getConstructor().newInstance();

最後就是調用servlet的service方法,就可以了:

        /**調用servlet的service方法*/
        servlet.service(request, response);

OK,這樣就完成了代碼的重構。進行一下測試:

OK,全部沒有問題。通過本期的重構,我們實現了Servlet隨業務邏輯自由添加,web.xml配置。進一步解耦了我們的服務器代碼,怎麼樣,到這裏是不是有一些小小的成就感了?別慌,還剩最後一步。可能聰明的你又發現了——每次請求都要手動啓動一次服務器,這要是拿到生產環境去用,那還不累死了。怎麼搞呢?多線程。這將是下一期的內容了。本期就到這裏,我們下期再見!

本期代碼請移步:https://github.com/justein/lyn-server/tree/master/server-servlet

如果覺得不錯的話,留下一個star再走吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章