老铁们,又见面了。上一期中,我们封装了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再走吧!