全面总结了 Servlet 相关的 API ,以及各类之间的内在联系系。
Servlet 接口
所有的 Servlet 类都必须要直接或者间接是实现 Servlet 接口,因为 Servlet容器是通过调用 Servlet 接口的方法来和 Servlet 类进行数据的交换的。
这里的数据交换说的更细致点就是,Servlet 容器会把收到的 HTTP 请求报文的字节流对象(本质上就是 Socket 通信),转成字符串。将这种以字符串形式表示的数据,进行字符串解析,封装到相应的类的对象中进行存储。然后将这种装载了请求信息的对象传递给 Servlet 类,以此实现它们之间的通信。
这个封装了请求参数信息的对象就是 ServletRequest 接口类型的对象,也就是 request ,封装了 web 应用返回给客户端信息的对象就是 ServletResponse 接口类型的 response 对象。之所以要这样做,当然是面向对象编程思想的指导啊,难不成把这些请求和响应信息用一个字符串数组装起来吗?
该接口的五个方法
其中三个方法是 Servlet 对象的声明周期的三个方法
-
void init(ServletConfig config)
该方法接收 Servlet容器传递过来的 ServletConfig 对象 -
void service(ServletRequest req, ServletResponse res)
,每次请求访问 Servlet 类时,Servlet 容器就会调用此方法 -
void destroy()
,Servelt 实例对象被销毁之前,会调用此方法。
得到 Servlet 类的相关信息
-
String getServletInfo()
- 一般用来返回编写这个 Servlet 类的作者名字、版本号等信息。
有点类似于一个类的 toString() 方法,用来返回对这个 Servlet 类的一些描述信息
- 一般用来返回编写这个 Servlet 类的作者名字、版本号等信息。
-
ServletConfig getServletConfig()
- 返回封装了这个 Servlet 类配置信息的 ServletConfig 对象,也就是 JSP 中的 config 对象
GenericServlet
这是一个实现了 Servlet 和 ServletConfig 接口的抽象类。其实抽象类存在的意义就是帮助一个接口真正的实现类做一些事情,让真正实现该接口的实现类少做点事情,毕竟面向对象的核心思想就是不能写重复代码,所以搞一个抽象类来替其他类写一部分代码。
比如,如果自己定义一个类来实现 Servlet 接口的时候,就需要手动的声明一个全局变量来接收,由 Servlet容器传递给init(ServletConfig config)
方法的 ServletConfig 对象,才能够让其他方法也能使用到 ServletConfig 对象。这样的话,每一个 Servlet 类都需要手工做一次这种重复工作,这显然不符合面向对象的设计思想。
所以,这里提供了一个抽象类 GenericServlet 来完成这件事,在这各类中写一遍就可以了,其他 Servlet 类不再需要写重复代码了。自定义的 Servlet 类只需要继承这个抽象类即可,同样还是 Servlet 类,而且用起来更省事。
GenericServlet 类到底做了什么?
完成上述工作的具体方式是这样的,此类新增了一个无参的init()
方法,是为了避免它的子类去重写init(ServletConfig config)
方法,而且每次还都需要显示的使用super.init(ServletConfig)
来调用 GenericServlet 类的方法,使得它特意做的工作失效了。
所以,提供了一个无参的init()
方法,子类只需要重写无参的init()
方法,在此方法内部完成部分初始化相关的工作就可以了。因为 GenericServlet 类实现了 Servlet 接口的init(ServletConfig config)
时在该方法的最后一行调用了无参的init()
方法。这就相当于说它的子类重写的init()
方法会被当做初始化方法一样调用,它也是初始化方法中的一部分。仔细思考这些思路的话,发现这里面深层次的含义就是 JavaSE 部分那些基本概念的应用啊,不懂这些基本概念,是不可能设计的出来这种方法的,也不可能理解得了别人的想法。
全局的 ServletConfig 对象仍然是由 GenericServlet 类来保管的。正确的理解,应该是一个 Servlet 实例对象内部持有了一个 ServletConfig 对象,它们是组合关系。
总结下的话,GenericServlet 类重写了 Servlet 接口的有参的init()
方法,把 Servlet容器传递过来的和这个 Servlet 类对象一一对应的 ServletConfig 对象保存为一个全局变量,将 config 对象作为一个属性去持有。并提供了一个无参的init()
方法供他的子类去使用。
HttpServlet
WEB 开发用的是 HTTP 协议,所以肯定要针对 HTTP 协议做了一下个性化的功能填充。
HttpServlet 类到底做了什么?
该类的核心点就是它重写了父类的void service(ServletRequest req, ServletResponse res)
方法,在此方法内部把 request 和 response 转换成 Http 类型的对象,然后调用它自己新增的service(HttpServletRequest req, HttpServletResponse resp)
方法,在此内部根据 HTTP 请求方法,将请求分发到各个和 HTTP 的几种请求方式对应的方法去处理。自定义的 Servlet 只需要继承 HttpServlet ,并重写需要的方法就可以了。常用的有 doGet() 和 doPost() 两种方法!
也就是说 HttpServlet 把 Servlet 接口的service()
方法给实现了,然后屏蔽了,子类只需要重写相应的doGet()
和doPost()
就可以了。限于篇幅,只保留了部分核心源码:
String method = req.getMethod();//获取请求方式
//然后是7个equals()的判断,因为HTTP协议一共有7种请求方式
if (method.equals(METHOD_GET)) {
doGet(req, resp);
} 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 {
}
总结下,HttpServlet 实现了 Servlet 接口的service()
方法,并提供了根据doGet()
,doPost()
等根据请求方式命名的方法,供它的子类去使用。它根据请求方法类型做了更为细致的分发,真正的 Servlet 类只需要根据请求类型重写对应的方法即可。比如,Get 请求就重写 doGet() 方法,Post 请求就重写 doPost() 方法。
学习感悟:其实编写 JavaSE 或者 JavaWeb 的代码,和使用框架是一样的道理,都需要遵循它的规则才行,在它的规定下进行编码。从这个角度来说,他们也是框架!用别人的东西,当然要受别人的限制啊。
这个道理其实可以应用到任何地方,就好像拿笔写字,只有用笔尖那头才能写字,拿着笔盖怎么写得到呢?这就是笔的规则,要使用它,就要遵循它的这个规则。在这个世界生存,也同样需要遵循这个世界的规则。当然做人不能什么规则都遵守,这样的人永远不会有大成就。善于、敢于打破规则的人才能成事。
ServletRequest 接口
浏览器以字节流的方式把数据发送到 Servlet 容器,Servlet 容器把字节流转成字符串后,再封装到 ServletRequest 对象中去。最后交给 Servlet 程序以面向对象的方法来使用这个信息。当然会封装很多个方法,然后每个方法返回一些相关的信息,面向对象不就是干这个事情的吗?
Servlet 中拿到的 ServletRequest 接口的对象和 JSP 中名字为 request 的对象是同一个对象,只是 JSP 中命名好了,Servlet 中没有指定命名方式而已。
ServletResponse
它和 ServletRequest 是一样的道理, Servlet 程序通过面向对象的方式给这个 response 对象设定相应信息,然后 Servlet 容器再把这些信息转成 HTTP 协议响应报文,返回给浏览器。
Servlet 中拿到的 ServletResponse 接口的对象和 JSP 中名字为 response 的对象是同一个对象,只是 JSP 中命名好了,Servlet 中没有指定命名方式而已。
ServletConfig
Servlet 容器会把 Servlet 类的初始化参数的 name 和 value 值封装到该类中,并提供了一个getInitParam(String name)
的方法返回 name 对应的 value 值。尽管没有看过 ServletConfig 对象的源码,但是通过学习其他 MVC 框架源码后,在此不负责任的猜测,该接口的实现类肯定是用一个 Map 对象来存储这些信息的。并且提供了一个 getServletContext()
方法获得 ServletContext 类型的 application 对象。
ServletContext
对于一个 web 应用,Servlet 容器只会给它创建一个 ServletContext 对象,也就是 JSP 中的 application 对象。该对象封装了 web.xml 中的所有的信息,当然也会包括它的初始化参数信息。查看该接口的源码,就可以知道它几乎封装了整个 web.xml 中的配置信息。
Servlet/JSP 应用程序中,不仅可以用注解的方式配置 Servlet 还可以用 xml 的方式。两者各有优点,但是 xml 的方式明显更牛,权限更高,可以覆盖注解的配置。这个例子还能够证明, web.xml 和注解可以同时存在,所以说合理的配合两者运用,才是最明智的选择。世间万物都是如此!