一步一步实现一个Web Server-04

8 篇文章 0 订阅

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹏鹏超人

如果觉得不错,请我喝杯茶如何?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值