前面的文章粗略的看了一下Tomcat提供的servlet-api.jar包中的Servlet有关的源码,这涉及到了抽象类这个基本概念,本篇文章我们就介绍一下它。
根据我们看的那几个类和接口的源码,我们可以用类图(这方面涉及到一种叫做统一建模语言即UML的标准,暂且不深入讨论)来表示它们之间的关系。
我是用微软的Visio软件来画这种图(关于这个软件的使用也暂且不介绍),可能并不一定满足UML的标准,但核心的东西表示出来就行了:
是不是比源码清晰多了呢!而且还很形象,实线箭头表示继承/扩展(某个类),虚线箭头表示实现(某几个接口),memberName表示属性域或方法,不过我已经省略了,<<抽象类>>是我手动加的,为了区分普通类。
事实上,在软件研发过程中,通常都是先把类图和其他的图设计出来,然后再进行代码的编写实现,或者通过工具软件把这些设计图直接转换成代码。这些设计图就好比建筑工程里面的建筑蓝图。
我们现在是通过源码返推出设计图,这样有助于记忆和理解一个软件的结构/架构、流程等等。
以前的文章讲过,类其实就是人脑中的概念在代码中的体现,那么抽象类当然就是指抽象的概念啊。
什么是抽象?下面是百度汉语给出的答案:
1.不具体,太笼统,细节不明确。
2.哲学范畴。指在认识上把事物的规定、属性、关系从复杂的整体中抽取出来的过程和结果。与“具体”相对。
3.一种脱离实际地、孤立片面地观察问题的方法。
4.指撇开事物的非本质属性,而把本质属性抽取出来的过程和方法。与“概括”相对。是形成概念的一种方法。
现实生活中有太多这样的例子,比如水果、动物、交通工具等等,这些概念都是不具体、太笼统、细节不明确的。而我们的HttpServlet
和GenericServlet
也是如此。
HttpServlet
是哪些细节不明确呢?当然就是需要用户去实现的业务细节啊,比如具体如何处理HTTP的GET、POST、PUT、DELETE等请求啊。GET什么东西/资源,技术上是指网页、图像、文件、音频、视频、代码等等,业务上又可能是某种商品的细节啊、某篇新闻报道或小说啊、某部电影或某首流行音乐啊等等。
HttpServlet
明确的是它是使用HTTP协议来与客户端软件进行通信,所以它提供了与HTTP各个方法对应的成员方法来处理请求。处理的请求是HTTP请求HttpServletRequest
,返回的响应HTTP响应HttpServletResponse
。事实上,这只是一种约定而已,你完全可以在doGet
方法中处理一些提交数据或更新数据或删除数据的业务逻辑。然而,这是极不提倡的,约定不就是需要大家一起遵守的吗!否则,你构建的代码将混乱不堪,给自己和别人维护代码带来极大的困难。代码的可维护性很大程度上取决于约定的遵守程度。
所以,正是因为存在不具体,细节不明确的概念,才需要抽象类。从目标上来说,我们提供抽象类是为了给下游的软件研发人员实现进一步的细节,我们只是预留一个已经明确的约定而已。
GenericServlet
也是一个抽象类,那它有哪些细节不明确呢?查看它的源码可以看到它没有HttpServlet
中doXXXX
之类的方法,跟请求和响应相关的方法是下面一个很奇怪的方法,该方法只有方法签名,而没有方法体:
@Override
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
这就是抽象方法,也就是GenericServlet
细节不明确的地方,它的参数是ServletRequest
和ServletResponse
,少了HTTP,就是说它连使用何种通信协议都不明确。这意味着这个抽象类没有约定要使用HTTP协议,你可以使用任何其他协议(事实上很少用到)。
那这个方法是什么意思呢?从方法名来看就是提供某种服务。OK,这就对了,不明确的就是提供何种服务啊,就需要用户去进一步实现这个方法啊。
抽象方法从语义上来说就是实现细节不明确的方法,从技术上来讲就只需加一个abstract
关键字且不实现方法体即可。
从这个角度看,这个service
方法也太抽象了吧,等于什么都没有提供嘛,我们也可以提供一个叫doSomething
的抽象方法啊,那这还叫抽象吗?别忘了service
方法还有参数呢,ServletRequest
和ServletResponse
可是规定了很多特定的方法,当然它们也是抽象方法,不过至少从方法名上可以窥其一二。不过,不得不说service这个方法名起得还是相当不错,语义上就是服务嘛,至于什么服务,就是用户需要实现的啊,可以是用户的注册登录、某个商品的搜索、订单的提交和支付、视频的观看等等。
既然HttpServlet
继承/扩展了GenericServlet
,那HttpServlet
包含的细节应该比GenericServlet
更明确一些,明确在哪呢?就是明确了它是使用HTTP协议的,因此它的service
方法是这样的:
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;//这里是重点
response = (HttpServletResponse) res;//这里是重点
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
service(request, response);//这里是重点
}
首先,因为是继承/扩展GenericServlet
,所以这个service
方法覆盖了GenericServlet
中的service
方法,同时提供了方法体(就是实现了GenericServlet
中的service
方法)。最主要的逻辑是将ServletRequest req, ServletResponse res
强制转换成了HttpServletRequest
和HttpServletResponse
类型,因为只要是进入到HttpServlet
的这个service
方法,那么可以明确的就是使用了HTTP协议,因此可以进行这种转换(当然,这段强制转换代码被异常处理包围着,就是以防万一转换的不是HTTP协议的Servlet请求和响应,那么就抛出异常,关于异常,我们以后再讨论)。转换之后,又调用了HttpServlet
中重载的另外一个service
方法,该方法的参数就是HttpServletRequest
和HttpServletResponse
类型了(关于方法覆盖和重载,可以看看我的这篇文章)。我们再来看看这个service方法:
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();//这里是重点
if (method.equals(METHOD_GET)) {//这里是重点
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);//这里是重点
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);//这里是重点
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {//这里是重点
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);//这里是重点
} else if (method.equals(METHOD_POST)) {//这里是重点
doPost(req, resp);//这里是重点
} else if (method.equals(METHOD_PUT)) {//这里是重点
doPut(req, resp);//这里是重点
} else if (method.equals(METHOD_DELETE)) {//这里是重点
doDelete(req, resp);//这里是重点
} else if (method.equals(METHOD_OPTIONS)) {//这里是重点
doOptions(req,resp);//这里是重点
} else if (method.equals(METHOD_TRACE)) {//这里是重点
doTrace(req,resp);//这里是重点
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
我们只看重点语句,可以看到,这就是从HTTP请求中拿到HTTP方法后,判断是GET、HEAD、POST等等之中的哪一个之后,再调用相应的doXXXX
方法。因此,HttpServlet
这个抽象类比GenericServlet
这个抽象类更加明确一些,明确的是使用HTTP协议来处理请求和响应,但还是不够明确,就是如何处理HTTP协议中的GET、POST之类的请求,这些请求中执行什么业务逻辑,是用户登录还是订单提交支付,还是看电影听音乐等等。这就是我们开发HelloWorld
这个Servlet的目的。
可见,抽象具有层次性,这也符合我们的思维习惯。我们设计类的时候,也应该尽量遵从这种思维习惯,尽量把相关的部分抽象出来形成一个层次。基于这一点,我们甚至可以在继承/扩展HttpServlet的时候,继续设计一个抽象类,再往下扩展时又设计一个抽象类。。。直到你判断业务该是设计具体类的时候。
好,以后我们遇到abstract关键字就不会陌生了,就是抽象类或抽象方法的意思;我们也可以设计自己的抽象类和抽象方法了。
abstract
关键字定义abstract
关键字定义且不能实现方法体,因为一丁点细节都不知道啊,唯一知道的就是它的参数和返回值;HttpServlet
就是这样,但这样的方法往往是需要被覆盖的,它们通常使用protected
关键字;new
关键字生成对象/实例,语义上就是细节很明确的,不过具体类也可以被继承/扩展,它的方法也能被覆盖。