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 Servlet, ServletConfig, Serializable{
// 里面比较长,这里就列一个service方法.
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
}
复制代码
因此,我们继续往下走,看一下继承了GenericServlet的HttpServlet。
我们发现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
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要登场啦 !!!
思路:
编写一个BaseServlet继承HttpServlet,并且覆写其中的service方法。
利用反射机制,完成单Servlet的多用。
让我们正常的业务Servlet去继承BaseServlet
正常使用(下面会介绍)
接下来我们用代码说话,会简单理解一些。
3.0 先介绍如何使用
这边先简单介绍如何使用
首先,将@WebSerlvet写成/xxxx/`的形式,这个`/代表匹配所有
编写自己的方法
前端发送url写成类似(省略)/UserServlet/xx即可,这个xx是指自己编写的方法名称。
哈哈,是不是使用起来非常简单快捷!
而且,最重要的是,我们由原本每个Servlet只能写一个方法,变成了可以写多个方法,这实现了我们功能模块化的目的!
不过,不用急,接下来就进入最重要的BaseServlet的编写
3.1 BaseServlet的编写
一句话:BaseServlet主要是利用了反射的机制完成了上述功能。
我们想理下具体操作的思路,最好再上代码。
首先,我们需要知道前端发送过来的请求,具体希望我们调用什么方法,比如是 登录还是注册呢?
答:由@WebServlet注解中的/xx/*,我们可以规定好,如果需要调用登录方法,就写成/xx/login,这个login(也就是方法名称)就是我们用来识别调用什么方法的关键字。
得到调用的方法名称,那么我们如果调用这个方法呢?
答:通过反射去获取该方法,并调用。
完整代码及解析:
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,且applicationContext是lemonfish),按下回车键。
我们来看下控制台的输出结果是什么呢?
当当当!!!
这里成功输出了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