Servlet功能模块化——封装BaseServlet

·  阅读 1954

1、问题引入

在我们刚接触servlet的时候,我们开发是这个样子的:

  • 为了写一个用户登录功能,我们新建一个LoginServlet
  • 为了写一个用户注册功能,我们新建一个SignUpServlet
  • 为了写一个用户更新功能,我们新建一个UserUpdateServlet
  • 为了写一个用户删除功能,我们新建一个UserDeleteServlet
  • 为了……

哈哈,这还只是一个简单的User,但是很明显,正常的项目上两位数的实体类是很正常的。

难道我们对每个实体类的操作都要像上面那样建立 N 个 Servlet才行吗?那岂不是得好几十个Servlet才能满足功能的开发。。。

接下来,这篇文章将为你们解开这个疑惑OwO


2、Servlet是如何响应我们发出的请求?

在讨论如何解决上述问题前,我们先来一起看一下Servlet是如何响应我们发出的请求的。

下图是一个标准的Servlet,我们通过发起get或者post请求访问servlet

但是,大家有没有想过为什么我们的get/post请求,就可以调用相应的doGet/doPost方法呢?


那么,我们就来一探究竟吧!

首先我们来看下我们刚刚建立的LoginServlet的结构图

从图中可以看见servlet的继承关系,servlet接口在最上面一层,而GenericServlet实现了servlet接口。

我们先来看下最上面的servlet接口里面有什么,大家重点看service方法就行

// servlet接口
public interface Servlet {
    // servlet被创建时进行的初始化方法
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    // Called by the servlet container to allow the servlet to respond to a request.
    // 翻译下就是:由Servlet容器调用,以允许Servlet响应请求。
    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}
复制代码

因此,我们得知了servlet接口中的service方法是用来响应请求的。

不过,接口只是定义了一套规范,我们得看下,具体实现是什么。

因此,我们继续往下走,看一下实现了servlet接口的GenericServlet

但是,我们点进GenericServlet的源码时,发现该类是一个抽象类,在servlet的基础上,丰富了一些方法。

同时,里面的service方法是一个抽象方法,我们猜测可能是由它的子类HttpServlet实现了service方法

public abstract class GenericServlet implements ServletServletConfigSerializable {
    // 里面比较长,这里就列一个service方法.
    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
}
复制代码


因此,我们继续往下走,看一下继承了GenericServletHttpServlet

我们发现service方法在这里被具体实现了!


哈哈,是不是有种离答案越来越近的味道了。

那我们赶紧来看看吧。

我们可以看到里面有非常多的if判断。

if (method.equals("GET"))的下面调用了doGet方法,Post也是同理。

// HttpServlet
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // 获取请求的类型,比如Post、Get等等
    String method = req.getMethod();
    long lastModified;
    // 如果为 GET
    if (method.equals("GET")) {
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
            // 调用doGet
            this.doGet(req, resp);
        } else {
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader("If-Modified-Since");
            } catch (IllegalArgumentException var9) {
                ifModifiedSince = -1L;
            }

            if (ifModifiedSince < lastModified / 1000L * 1000L) {
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
                resp.setStatus(304);
            }
        }
    } else if (method.equals("HEAD")) {
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
        // 如果为 Post
    } else if (method.equals("POST")) {
        // 调用 doPost
        this.doPost(req, resp);
    } else if (method.equals("PUT")) {
        this.doPut(req, resp);
    } else if (method.equals("DELETE")) {
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
        this.doTrace(req, resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }

}
复制代码


到这里,我们就大概明白了,Servlet是如何响应我们发出的请求了。

总结:如果我们的servlet没有覆写service方法的话,那么servlet就会调用httpServlet实现的service方法去响应请求。根据请求的类型,调用具体不同的方法。


3、 BaseServlet的编写

接下来,就是我们本篇文章的核心BaseServlet要登场啦 !!!

思路:

  1. 编写一个BaseServlet继承HttpServlet,并且覆写其中的service方法。
  2. 利用反射机制,完成单Servlet的多用。
  3. 让我们正常的业务Servlet去继承BaseServlet
  4. 正常使用(下面会介绍)

接下来我们用代码说话,会简单理解一些。


3.0 先介绍如何使用

这边先简单介绍如何使用

  1. 首先,将@WebSerlvet写成/xxxx/`的形式,这个`/代表匹配所有
  2. 编写自己的方法
  3. 前端发送url写成类似(省略)/UserServlet/xx即可,这个xx是指自己编写的方法名称。

哈哈,是不是使用起来非常简单快捷!

而且,最重要的是,我们由原本每个Servlet只能写一个方法,变成了可以写多个方法,这实现了我们功能模块化的目的!


不过,不用急,接下来就进入最重要的BaseServlet的编写


3.1 BaseServlet的编写

一句话:BaseServlet主要是利用了反射的机制完成了上述功能。


我们想理下具体操作的思路,最好再上代码。

  1. 首先,我们需要知道前端发送过来的请求,具体希望我们调用什么方法,比如是 登录还是注册呢?

    答:由@WebServlet注解中的/xx/*,我们可以规定好,如果需要调用登录方法,就写成/xx/login,这个login(也就是方法名称)就是我们用来识别调用什么方法的关键字。

  2. 得到调用的方法名称,那么我们如果调用这个方法呢?

    答:通过反射去获取该方法,并调用。


完整代码及解析:

public class BaseServlet extends HttpServlet {
    // 覆写service方法
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 1. 这里获取URL或者URI都可以,URI: /lemonfish/UserServlet/login    URL: http://localhost/lemonfish/UserServlet/login
        String requestURI = req.getRequestURI();
        // 2. 获取最后`/`的索引
        int beginIndex = requestURI.lastIndexOf("/");
        // 3. 使用substring,获取方法名称
        String methodName = requestURI.substring(beginIndex + 1);
        try {
            /***
             * 记住谁调用了“service”方法,this就是谁,
             * 因为我们自己编写的UserServlet继承了BaseServlet
             * 因此,这个service方法也是属于UserServlet的
             * 而前端访问的是UserServlet
             * 因此 this 是 UserServlet的一个对象
             * 
             * 4. 这里 根据 方法名称 和 方法参数的class类型 ,利用反射获取UserServlet的该方法。
             * 
             * 如果大家这两行代码看不太懂,可以先去复习下 反射 的知识。
             ***/

            Method method = this.getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);

            // 5. 使用this调用该方法。
            method.invoke(this, req, resp);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
复制代码


4、BaseServlet的实例演示

4.1 写一个BaseServlet

如上


4.2 写一个UserServlet,并继承BaseServlet

这个UserServlet里面有两个方法,一个是 login 登录,一个是signUp注册


4.3 启动Tomcat,访问UserServlet

我们启动tomcat,并在地址栏输入http://localhost/lemonfish/UserServlet/login(这里根据自己的端口和路径 进行调整,我这里端口是80,且applicationContextlemonfish),按下回车键。

我们来看下控制台的输出结果是什么呢?

当当当!!!

这里成功输出了Log In,说明我们成功一半了。

不过别急,我们再试试signUp方法

哈哈,这下我们可以放心啦!

我们成功通过BaseServlet完成了访问一个Servlet的同时,可以使用多个方法。

这样,我们在开发的时候就可以根据自己的业务需求编写对应的xxxServlet即可。


4.4 处理转发的小坑

在写转发路径的时候要记住加/喔,这样会直接追加在根路径下

如果写成hello.jsp,那么就会追加到当前路径(也就是UserServlet下面),

这样service方法就会去UserServlet下面找名字为hello.jsp的方法,就会报错!

request.getRequestDispatcher("/hello.jsp").forward(request, response);
复制代码


5. DEMO源码地址

为了大家方便,我决定把这个demo的源码直接上传到 Github

这样方便大家进行阅读学习,如果觉得不错,还请赏颗 Star(●'◡'●)


虽然之前用码云用的比较多 - -,但是最近开始应该会主要用Github了。


6. 写在最后

封装一个BaseServlet对我们开发效率有很大的提高,特别是在还没有接触框架之前。

希望大家看完能有所收获~~(●'◡'●)

如果大家觉得有所帮助的话,能点个👍b( ̄▽ ̄)d 是再好不过的事情啦hhh

分类:
后端
标签:
分类:
后端
标签: