现在Python和Java是比较流行的两种编程语言,其中使用Java的开发者中大部分从事Web方面的工作(桌面程序市场不大)。所以对于Java开发者来说掌握JavaSE和JavaEE都是很有必要。尽管现在更多使用spring,springboot,springcloud等优秀的框架来简化开发,但是对于基础的JavaEE技术深入研究也是很有必要。一方面能让我们更清楚的认识到web应用的本质,建立自己完整的技术体系;另一方面也有助于我们学习这些优秀框架的源码(相对来讲JDK的源码比spring等框架的源码还是更容易学习的),正所谓一法通万法通,万事皆由易再难,学习路线会更加平滑。
1.什么是Servlet
①字面上来讲Servlet=Server Applet,服务端小程序。
②对于静态的Web技术来说,Servlet技术基于request-response模型为Web应用提供动态的面向用户的内容(即dynamic web)。
③通常说的Servlet本质上是实现Servlet接口的Java类(包括通用的GenericServlet
,处理Http请求的HttpServlet,以及自定义的Servlet等),通过Servlet容器(即Web服务器,如Tomcat,Jetty,WebLogic等)处理客户端的请求并作出响应。
2.Servlet接口
Servlet接口定义了Servlet的生命周期,及获取Servlet信息,上下文的顶层规范。
①Servlet定义了生命周期方法:init,service,destroy
* 生命周期执行顺序:
* Step1:Servlet容器调用Servlet实现的构造方法,创建Servlet实例
* Step2:调用init方法进行初始化(对于每个Servlet实例只初始化一次),例如加载ServletConfig
* Step3:调用service方法,处理客户端请求
* Step4:调用destroy方法,通常在移除Web容器时执行
* Step5:进行GC和销毁Servlet实例
②Servlet提供getServletConfig方法,获取Servlet配置信息
* getServletInfo方法获取Servlet自身的基本信息,例如作者,版本,版权
③ 实现Servlet接口通常采用两种方式:继承GenericServlet和继承HttpServlet
在介绍GenericServlet之前, 这里重点提一下,为什么GenericServlet中定义了
private transient ServletConfig config;
用于存储初始化时的ServletConfig信息。
3.GenericServlet和ServletConfig
①GenericServlet是通用的与协议无关的Servlet,如果使用Http协议可以使用HttpServlet(Servlet技术不限于Http协议)。
通过实现ServletConfig接口扩展Servlet的功能(配合定义的transient ServletConfig config属性,可以对初始化的ServletConfig进行操作,我们这点要掌握这种思想)。
②ServletConfig用于Servlet容器向Servlet传递信息(在初始化Servlet时,调用Servlet的init(ServletConfig config)方法)。
根据定义的方法可以看出ServletConfig可以获取ServletName,初始化参数和ServletContext(即Servlet上下文,用于Servlet容器和Servlet交互)。
4.ServletContext
可以深刻的理解:每个JVM中只有一个ServletContext,并对应一个web应用(用万物皆对象的思想来解释,可以理解为一个ServletContext就是指代war包,也就是我们的一个Web应用)
在分布式中,多个JVM中的同一个Web应用 的不同ServletContext实例是不能共享全局信息的(这个很好理解,不同虚拟机的ServletContext实例不同,可以通过数据库技术实现信息共享)。
这里学习一下ServletContext中的方法主要分两类:获取Servlet容器相关的信息,获取当前Web应用的信息。
①Servlet容器相关
public ServletContext getContext(String uripath);获取当前Servlet容器中的其他Web应用的ServletContext对象。
public int getMajorVersion();等获取支持版本的信息的几个方法
public Set<String> getResourcePaths(String path);等获取资源信息的几个方法
public Object getAttribute(String name);获取容器属性
②Web应用相关
String getContextPath();获取当前ServletContext的路径
public RequestDispatcher getRequestDispatcher(String path);获取请求分配对象
public String getInitParameter(String name);获取应用的初始化参数
以及动态添加Servlet,Filter,Listener的几个方法
总结:ServletContext代表一个Web应用,用于Servlet容器与Web应用进行交互。
5.HttpServlet
由于Web应用通常使用http协议,通常可以继承HttpServlet编写自定义的Servlet类。
这里认真理解一下HttpServlet的源码
①doGet方法
如果处理get请求需要重写doGet方法。Get请求用于处理幂等性操作(如查询类操作,如需改变数据使用其他请求方法)。
②doHead方法
doHead请求用户处理只需要响应头,而不需要响应体的请求。doHead方法也要求幂等性。
③doPost方法
doPost方法对请求数据长度没有限制,并且doPost方法不要求幂等性(可以执行变更数据的操作)。
④doPut方法
doPut方法用于传输文件,并且不能修改请求头,如果Servlet不能处理请求头,请求将被丢弃响应501。doPut方法不要求幂等性,可用于存储临时文件。
⑤doDelete方法
doDelete方法用于删除服务器中的文档或者web页面,doDelete方法不要求幂等性。
⑥doOptions方法用于获取当前服务器支持的http方法有哪些(TRACE,OPTION方法默认支持,可以参考doOptions方法的源码)。
这里学习一下getAllDeclaredMethods()用于获取当前类和父类所有的声明方法。
⑦doTrace方法用于debug
⑧service方法将客户端请求调度到处理Http请求的service方法中,并根据不同的方法类型调用对应的do方法。
6.Servlet的基本使用
①以用@WebServlet注解定义Web应用的Servlet组件(也通过xml配置Servlet,这里不再介绍)
通过urlPatterns
或 value
属性定义Servlet的请求路径。
通过@WebInitParam
注解定义初始化参数。
②获取请求信息
通过ServleRequest获取BufferedReader
或者ServletInputStream,读取请求信息(参数,属性,协议信息等)
③构造响应
通过 ServletResponse获取PrintWriter
或者ServletOutputStream
,向客户端返回响应信息(设置响应头,响应体,setContentType,设置编码格式,状态码,Cookie等)
这里尤其要注意PrintWriter
和ServletOutputStream是互斥的只能获取其中一个用于响应。
setContentType通常为MIME类型,可以参考http://www.iana.org/assignments/media-types/
7.异步Servlet和NIO
Servlet3支持异步的操作,下图展示同步和异步操作的区别:
在异步Servlet中使用非阻塞IO(例子给出了处理输入,作为参照可以也使用NIO处理输出):
8.Servlet线程不安全
①由于容器的多线程引起的线程不安全性
②不同Servlet之间的线程不安全性
③如果单个Servlet实现中使用多线程也有可能造成线程安全问题。
9.总结
Servlet相关的主要内容学习完毕,一些HttpServletRequest等不做过多研究,事件监听机制后续介绍。
知识点:Servlet用于处理请求响应,运行在Servlet容器中,ServletContext指代Web应用,以及Servlet线程安全问题。
通过这次系统的学习,对Servlet体系有了更深入的认识。