老鐵們,又見面了。上一期中,我們封裝了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再走吧!