文章目录
- 介绍
- Servlet类图
- 为什么需要抽象类
- 抽象方法
- 抽象的层次
- 总结
介绍 前面的文章粗略的看了一下Tomcat提供的servlet-api.jar包中的Servlet有关的源码,这涉及到了抽象类这个基本概念,本篇文章我们就介绍一下它。
Servlet类图 根据我们看的那几个类和接口的源码,我们可以用类图(这方面涉及到一种叫做统一建模语言即UML的标准,暂且不深入讨论)来表示它们之间的关系。
我是用微软的Visio软件来画这种图(关于这个软件的使用也暂且不介绍),可能并不一定满足UML的标准,但核心的东西表示出来就行了:
文章图片
是不是比源码清晰多了呢!而且还很形象,实线箭头表示继承/扩展(某个类),虚线箭头表示实现(某几个接口),memberName表示属性域或方法,不过我已经省略了,<<抽象类>>是我手动加的,为了区分普通类。
事实上,在软件研发过程中,通常都是先把类图和其他的图设计出来,然后再进行代码的编写实现,或者通过工具软件把这些设计图直接转换成代码。这些设计图就好比建筑工程里面的建筑蓝图。
我们现在是通过源码返推出设计图,这样有助于记忆和理解一个软件的结构/架构、流程等等。
为什么需要抽象类 【我的Java Web之路 - Java基础(5)- 抽象类和抽象方法】以前的文章讲过,类其实就是人脑中的概念在代码中的体现,那么抽象类当然就是指抽象的概念啊。
什么是抽象?下面是百度汉语给出的答案:
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 {HttpServletRequestrequest;
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关键字就不会陌生了,就是抽象类或抽象方法的意思;我们也可以设计自己的抽象类和抽象方法了。
- 类图是一个很好的设计、交流的工具,我们也可以尝试为源码画画类图,这样有助于记忆和理解;
- 因为存在不具体、细节不明确的概念,所以需要抽象类,这就符合人的思维层次性;
- 抽象类的目标为了给下游的软件研发人员去实现进一步的细节(即给别人继承/扩展之用的),我们只是根据已经明确的细节预留一个约定而已;
- 抽象是具有层次性的,因而也就形成了继承/扩展层次;
- 分层的思想很重要,也可以称之为分层模式、分层范式,大的有架构上的分层,小的有方法上的分层;
- Java中抽象类使用
abstract
关键字定义 - Java中抽象方法使用
abstract
关键字定义且不能实现方法体,因为一丁点细节都不知道啊,唯一知道的就是它的参数和返回值; - 拥有抽象方法的类必须定义为抽象类;
- 但抽象类不必有抽象方法,它可以有具体细节,因为可以提供具体的但很粗糙的默认方法实现,
HttpServlet
就是这样,但这样的方法往往是需要被覆盖的,它们通常使用protected
关键字; - 抽象类不能生成对象/实例,但可以声明一个抽象类的引用类型的变量,该变量可以指向一个具体类的对象/实例;
- 具体类能直接使用
new
关键字生成对象/实例,语义上就是细节很明确的,不过具体类也可以被继承/扩展,它的方法也能被覆盖。 - 所谓语义,就是在语言上的含义,它通常是分层的依据,说直接一点就是类名、方法名一定要在一定程度上符合或表达或反映各个层次的细节,不该放在该层的就不要放到该层,这又体现了单一职责原则。
推荐阅读
- Integer常量池结合源码解析
- Java基础|Java 打印空心等腰三角形(方法2)
- gradle 每次运行都会下载依赖的解决办法
- 使用vector代替数组
- 如何获取ResultSet的行数和列数
- Java 时间戳格式化
- Java基础|Android开发——JVM、Dalvik以及ART的区别
- XML|XML报文转Map
- Java 8 时间,字符串和Long时间戳互转
- JAVA基础|JAVA基础(TreeMap键是Student值是String案例)