从源码角度分析Servlet
一.Servlet简介
1. servlet是什么?
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
2. TomCat作为Servlet容器
TomCat可以搭建一个HTTP服务器作为Servlet容器。
Sevrlet容器具有3个基本任务:
创建一个request对象,用可能会在调用的Servlet中使用到的信息,如参数,头,cookie,查询字符串,URI等。Request对象是javax.servlet.ServletRequest接口或javax.servlet.http.ServletRequest接口的一个实例
创建一个调用Servlet的response对象,用来向Web客户端发送响应,response对象是javax.servlet.ServletResponse接口或javax.servlet.http.ServletResponse接口的一个实例。
调用Servlet的service()方法,将request对象和response对象作为参数传入。Servlet从request对象读取信息,并通过response对象发送响应信息。
一个Servlet容器可以由多个Servlet程序用来服务不同的HTTP请求(即使用service()方法服务),并且对于每个的Servlet程序由许多类组成,这些类或多或少使用javax.servlet.ServletRequest和javax.servlet.http.ServletRequest的对象。
3. servlet的生命周期
Servlet编程需要使用到javax.servlet和javax.servlet.http两个包下的接口和类,在所有的类和接口中,javax.servlet.servlet接口最为重要。所有的servlet程序都必须实现该接口或继承自实现该接口的类。
Servlet接口中声明了5个方法,其签名如下:
1.Public void init(ServletConfig config) throws ServletException
2.Public void service(ServletRequest request , ServletResponse response) Throws ServletException,java.io.IOException
3.Public void destroy()
4.Public ServletConfig getServletConfig()
5.Public java.lang.String getServletInfo()
Init()方法
当Servlet容器实例化某个servlet类后,Servlet容器将会调用其init()方法进行初始化。Servlet容器只会调用一次init()方法,初始化代码块比如连接数据库,设置一些初始值等。
Service()方法
当Servlet容器初始化完成后会调用service()方法执行相应的服务信息。
比如,当一个或多个客户端请求达到后,Servlet容器为每个客户端创建一个线程,并且这些线程将会同步的调用service()方法。并且将相应的javax.servlet.ServletRequest,javax.servlet.ServletResponse参数传递进来。ServletRequest对象包含了客户端的HTTP请求的信息,ServletResponse对象则封装servlet响应。在servlet对象的生命周期内,service()方法将会多次调用。
Destroy()方法
当Servlet容器关闭或Servlet容器要释放内存时,才会将servlet实例移除,而且只有当servlet实例的service()方法中的所有线程都退出或执行超时后,才会调用destroy()方法。当Servlet容器调用了某个实例的servlet实例的destroy()后,它将不会再拥有该实例servlet实例的service()方法。调用destroy()方法后,让servlet对象有机会清理自身持有的资源,如内存,文件句柄和线程等。
二、源码分析
对一个Servlet的每个HTTP请求,一个功能齐全的servlet容器需要做下面几件事:
1. 当一次调用某个servlet时,要载入该servlet类,并调用其init()方法(仅此一次)
2. 针对每个request请求,创建一个javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例
3. 调用该servlet的service()方法,将servletRequest对象和servletResponse对象作为参数传入。
4. 当关闭该servlet类时,调用其destroy()方法,并卸载该类
下面是一个简单的servlet容器,它将做下面几件事:
1. 等待HTTP请求
2. 创建一个servletRequest对象和一个servletResponse对象
3. 若请求是静态资源,则调用StaticResouresProcess对象的process()方法,传入servletRequest对象和servletRespones对象
4. 若请求的是servlet,则载入相应的servlet类,调用其service()方法,传入servletRequest对象和servletResponse对象。
另外,在该servlet容器中,每次请求的servlet都会载入相应的servlet类。
类导图:
l HttpServer1.java
l Request.java
l Response.java
l StaticResouresProcess.java
l ServletProcess1.java
l Constants.java
1. HttpServer1类
1)使用的JAVA类库
2)类的常量字段
HttpServer1类中变量字段,SHUTDOWN_COMMAD字段用关闭命令。Shutdown用作await()方法中循环判断字段,其作用主要是根据URL“SHUTDOWN_COMMAD”字段是否有写关闭命令来显性关闭服务器响应。
3)方法字段
在这片段的await()方法字段中,主要是进行服务器端的初始化工作。服务器端,通过实例化serverSocket对象,调用accept()方法监听指定的端口,与客户端进行连接。
①.serverSocket的构造方法
①.Accept()方法
public Socket accept() throws IOException
侦听并接受到此套接字的连接。此方法在连接传入之前一直阻塞。
①.JAVA IO的使用
当客户端-服务器端连接建立,客户端的请求信息,和服务器的响应信息,都可以通过IO对数据流的输入输出。
在该await()方法片段中,通过用户URL显视关闭循环,并且调用accept()方法,获取与客户端连接。当连接建立好之后,实例化相应的输入输出流(在之后的编程中,将围绕着,这两个输入输出流进行编程)
在这里面,将实例化Request和Response类,其中Request.parse()方法用于解析URL。
在剩下的部分中,通过条件判断,请求的是静态资源还是servlet服务的资源。最后根据用户的URL判断是否关闭服务器响应。
2. Request类
对于一个Servlet容器中,由于HTTP请求,它将实例化一个javax.servlet.ServletRequest和一个javax.servlet.ServletResponse的实例。对于服务相应的HTTP请求的Servlet程序,调用它的service()方法,并且将上述实例传递给它。
对于Request类,表示被传递给Servlet的service()方法的一个request对象,所以它必须要实现javax.servlet.ServletRequest接口中所声明的所有方法(在后面的程序中将会继续给出)
1)使用的JAVA类库
2)声明的类变量成员
3)类中的三个公共方法
一个是public Request(InputStream input)构造函数,用于得到输入流的实例对象。
另一个是public String getUri() 用于其他类的公共方法,作用是返回解析后的字符串信息。
最后一个Request类的公共方法public void parse() 用于在HtpServer类中调用该方法,从客户端请求中获取URL字段。InputStream类的read()方法可以把输入流数据串行化放到相应的字节区中。
4)Request类私有方法
用途是传递客户端读入流字节信息,并且解析相应的字串,解析标准是根据HTTP请求报文格式。比如,在请求报文中,URL字串在方法+空格后,和空格+协议版本前面。
3. Response类
这个类将对客户端请求的URL字段中请求的文件,返回给客户端。
若文件不存在,则返回404状态码错误信息。
Response类实现了javax.servlet.ServletResponse接口,需要复写其中的方法,
并且作为参数需要传递个service()方法中的一个参数
1) Response使用的类库
2) Response类的变量字段
3) Response类的公共方法
public Response(OutputStream output)公共方法是Response类的构造函数主要用于获得从Httpserver1类获得的实例化输出流。
public void setRequest(Request request) 公共方法将会接受一个Request类的实例对象
public void sendStaticResource()公共方法用于发送一个静态资源文件给客户端,比如这里发送的是index.html文件。
首先,该文件通过WEB_ROOT获得用户的父路径比如说:”G:\计算机学习文献\Java\Tomcat\HowTomcatWorks\webroot”路径,然后在通过客户端请求的子路径URL实例化一个File对象。下图是File对象的构造函数
4. StaticResourceProcesser类
该类主要用于处理静态资源的请求,这个类只有一个公共方法,即process()方法,该方法接受Request,Response类的实例对象,调用Response.sendStaticResource()方法
5. servletProcessor1类
servletProcessor1类主要用于客户端通过servlet容器访问相关资源时,该怎么调用service()方法服务相应的客户端请求。
该类只有一个方法process()方法,该方法接受的两个参数,一个是javax.servlet.ServletRequest实例,和javax.servlet.ServletResponse实例。
这一部分是实现一个类载入器。
剩下的部分通过类载入器,获取servlet容器中创建的servlet实例字段。然后把Request类的实例,Response类的实例向上转型为ServletRequest实例,和ServletResponse的实例。再调取service()方法。通过这样的形式,响应客户端的请求。
6. Constants类
这个类主要用于获取服务器的文件内容路径
File.separator将打印一个\的分隔符.webroot是服务器文件存放地。
三. 存在的问题
对于servletProcessor1类中,service()方法其传递的参数是Request(实现了javax.servlet.ServletRequest接口)向上转类型的ServletRequest,和Response(实现了javax.servlet.ServletResponse接口)向上转类型的ServletResponse。
这是一种很不安全的方法,为什么?
因为在service()方法中,将继续可以使用Request类中声明的公共方法getUrl(),parse()方法和Response类中声明的公共方法sendStaticReource()方法。显然,对于这两个方法我们希望在其他类,甚至其他包使用(不修改访问权限)。但我们不想在service()方法使用,若在service()方法中使用,将产生不可预知的错误。
可以做的改变:1. 把Request类和Response类中的公共方法修改其访问权限
比如说把公共方法修改为受保护的方法,因此可以解决这个问题,但却不是我们想要的结果。
2. 使用外观类
添加两个外观类,RequestFacade(实现了javax.servlet.ServletRequest接口)类和ResponseFacade(实现了javax.servlet.ServletResponse接口)类,其构造函数传递Request类的实例,和Response的实例,通过把javax.servlet.ServletRequest实例向下转换为Request类实例。
在通过把RequestFacade实例对象向上转换为javax.servlet.ServletRequest对象传递给service()方法中,这样service()方法中只能使用javax.servlet.ServlerRequest接口中定义的方法。这样可以不需修改类方法访问权限,也能避免相应的问题。
对于Response类也类似。
1. RequestFacade类
这个类ServletRequest对象被定义成私有的,很明显,在实际使用时可以避免发生必要的错误
2. ResponseFacade类
同上面
3. 修改的ServletProcessor2
这是相应的修改字段,对于service()方法,我们只需要传递RequestFacade和ResponseFacade类的实例即可
4. 修改的HttpServer2类
实例化产生一些改变