本文共 8115 字,大约阅读时间需要 27 分钟。
要介绍Servlet则要从Servlet容器说起,因为Servlet是放在Servlet容器里面运行的,就类似于子弹是放在枪膛里才能打出去一样。(两者则是通过标准化的协议接口来连接 的)
Servlet容器独立发展,种类很多,各有定位和特点,比较流行的有Jetty、Tomcat等,其中以Tomcat占据市场主流。
这里以主流的Tomcat为例,Tomcat作为一种Servlet容器,它是如何管理Servlet的呢?
先看以下Tomcat容器结构图:
先明白两点:
(1)Servlet容器并不直接管理Servlet,而是通过中间层来实现(Context容器)。一个Servlet容器可以包含多个Context容器,每个Context容器对应管理了一个Web工程,分为多个Servlet,且被包装成Wrapper被管理。
(2)为什么适用中间层(即出现了Context容器和Wrapper包装层)这种管理手段?
第一:一个Servlet容器可以放多个Web工程,但是每个工程要被单独管理,所以就要借助Context容器单独管理Web工程。
第二:Servlet是独立开发的Web标准,不应该和容器产生强关联性耦合,所以要借助具有容器特征的StandWapper包装成子容器(Wapper,也就是Servlet实例)添加到Context中管理。(所以,可以得出另一个结论:Context容器才是真正运行Servlet的Servlet容器)
一:组件等级划分
Tomcat按功能划分为许多不同的组件,并可通过/conf/server.xml文件对其定义和配置,包括Server, Service, Connector,Engine, Host, Context……一般可分4个等级:
1、顶级组件:位于配置层次的顶级,并且彼此间有着严格的对应关系,有Server和Service组件;
2、连接器:连接客户端(浏览器或Web服务器)请求至Servlet容器,如Connector组件,
3、容器:功能是处理传入请求的组件,并创建相应的响应。如Engine处理对一个Service的所有请求,Host处理对特定虚拟主机的所有请求,Context处理对特定web应用的所有请求;
4、被嵌套的组件:位于一个容器当中,但不能包含其它组件;一些组件可以嵌套在任何Container中,而另一些只能嵌套在Context中。
二:组件关系
如果把Tomcat比较一个框架类,那么一个Server服务器表示一个Tomcat实例,通常一个JVM也只包含一个Tomcat实例,一个Server中可以有多个Service服务,Service可以包含一个或多个Connector用于连接Container容器中的一个Engine,连接器Connector通过特定端口接收客户端请求,并将其转发到关联的Engine;
而Engine作为Servlet容器引擎,包含多个Host组件和其他组件,引擎接收并处理来自一个或多个连接器的请求,并辨别请求的HTTP首部信息以便确定发往哪个Context或Host(客户端通常使用主机名来标识他们希望连接的服务器),并将完成的响应返回到连接器,以便最终传输回客户端。
一个Context上下文对应一个Web应用程序,即一个Web project,而一个Web工程包含多一个Wrapper(Servlet应用程序的包装类)。也就是说,在tomcat容器等级中,Context容器直接管理Servlet在容器中的包装类Wrapper,所以Context容器如何运行将直接影响servlet的工作方式。
Tomcat启动时通过org.apache.catalina.startup.Tomcat这个启动类完成的,启动步骤如下:
(1)创建实例
(2)添加Web应用
(3)启动Tomcat
(4)调用Servlet
Tomcat tomcat = getTomcatInstance(); //1:创建实例File appDir = new File(getBuildDirectory(), "webapps/examples"); tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath()); //2:添加Web应用tomcat.start(); //3:启动实例ByteChunk res = getUrl("http://localhost:" + getPort() + "/examples/servlets/servlet/HelloWorldExample"); assertTrue(res.toString().indexOf("Hello World!
") > 0); //4:调用servlet
启动时序如下所示:
1: 根据自己的理解,Servlet是什么?作用?
(是什么?)Servlet是实现了Servlet接口,并通过“请求-响应”模式来访问的服务端Java程序;
(干什么的)主要用于开发动态Web资源;交互式的浏览和修改数据,动态的扩展Server的能力。
(1)接受请求,并与服务器资源进行通信;
(2)创建并返回一个基于客户端请求性质的动态内容HTML页面给客户端。
下面是一段Servlet的具体定义:
Servlet是sun公司提供的一种用于开发动态web资源的技术,其API提供了servlet接口,若用户想要开发一个动态web资源,需要:
1、编写一个实现servlet接口的Java类。 2、把开发好的Java类部署到web服务器中。 通常,按照习惯把实现了Servlet接口的java程序,称为Servlet。Servlet依赖于Servlet容器(如常用的Tomcat和Jetty),两者类似于枪和子弹,相互依存、相互增强,但又相互独立,通过标准化接口进行协调工作。
Servlet容器独立发展,种类很多,各有定位,各有特点,比较流行的有Jetty、Tomcat等,其中以Tomcat占据市场主流。
2 Servlet生命周期
servlet始于装入web服务器内存,并在web服务器终止或重装servlet时结束。servlet一旦被装入web服务器,一般不会从web服务器内存中删除,直至web服务器关闭或重装才会结束。
(1)初始化:web服务器启动时或web服务器接收到请求时加载Servlet。执行init();(2)调用:运行service()方法,service()自动派遣运行与请求相对应的doGet()或doPost()方法;
(3)销毁:停止服务器时调用destroy()方法,销毁实例。
3 Servlet调用流程
请求流程如图:
结合着生命周期说,Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
(1)Web服务器接收到浏览器发送的请求之后,首先检查是否已经装载并创建了该Servlet的实例对象。(2)如果没有Servlet实对象,装载并创建该Servlet的一个实例对象,调用Servlet实例对象的init()方法初始化。
(3)创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
(4)WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
Servlet的运行模式是一个“握手型的交互式”运行场景,其中:
其中ServletRequest和ServletResponse发生了一系列转化:其转变过程如下:
tomcat接到请求首先将会创建org.apache.coyote.Request和org.apache.coyote.Response,这两个类是Tomcat内部使用的描述一次请求和相应的信息类,它们是一个轻量级的类,作用就是在服务器接收到请求后,经过简单解析将这个请求快速分配给后续线程去处理。
接下来当交给一个用户线程去处理这个请求时又创建org.apache.catalina.connector.Request和org.apache.catalina.connector.Response对象。这两个对象一直贯穿整个Servlet容器直到要传给Servlet,传给Servlet的是Request和Response的Facade类。
其中ServletConfig和Request类均使用了门面设计模式。
简述过程如下:
1:用户通过浏览器向服务器发送一个请求url: http://hostname:port/contextpath/servletpath; 其中,hostname和port用来与服务器简历TCP连接,contextpaht和servletpath路径用于选择服务器中对应子容器服务请求。
2:如何选择子容器?使用org.apache.tomcat.util.http.mapper这个映射类保存Container容器中所有子容器信息, org.apache.tomcat.catalina.connector.Request请求类进入Container容器之前,Mapper类就根据hostname和contextpath在mappingData属性中保存了host和Container容器之间的关系,可以根据这个映射关系选择子容器。
3:Mapper类又如何保存这个映射关系呢?使用类似于观察者模式的方法,将MapperListener作为一个监听者加入Container容器的每个子容器,一旦容器发生变化,保存容器关系的MapperListener类的mapper属性也会修改更新。
上面的Request路由图描述了一次Request请求是如何到达最终的Wrapper容器的,我们现在知道了请求如何到达正确的Wrapper容器,但是在请求达到最终的Servlet前还要完成一些步骤,必须要执行Filter链,以及通知你在web.xml中定义的Listener。
接下来就要执行Servlet的service方法了。通常情况下,我们自己定义的servlet并不直接去实现javax.servlet.servlet接口,而是去继承更简单的HttpServlet类或者GenericServlet类,我们可以有选择的覆盖相应的方法去实现要完成的工作。
Servlet的确能够帮我们完成所有的工作了,但是现在的Web应用很少直接将交互的全部页面用Servlet来实现,而是采用更加高效的MVC框架来实现。这些MVC框架的基本原来是将所有的请求都映射到一个Servlet,然后去实现service()方法,这个方法也就是MVC框架的入口。当Servlet从Servlet容器中移除时,也就表明该Servlet的生命周期结束了,这时Servlet的destory方法将被调用,做一些扫尾工作。
Step 1:继承HttpServlet类别
Step 2:重写doGet()或者doPost()方法
import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class ApplicationServlet extends HttpServlet{ //处理HTTP GET请求 @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } //处理HTTP POST请求 @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//第一种:返回渲染结果 resp.setContentType("text/html;charset=utf-8"); PrintWriter out = resp.getWriter(); out.println(""); out.println("Servlet06</head> "); out.println(""); out.print("我是返回值"); out.print(req.getParameter("name")); out.println(""); out.println("");//第二种:重新定向到servlet02 resp.sendRedirect("s02");//第三种:请求分派 //获取请求分派器 RequestDispatcher dispatcher = req.getRequestDispatcher("servlet04"); //将请求转发至指定路径的资源 dispatcher.forward(req,resp);//第四种:添加传递参数 String str = "在Servlet05中存放请求域属性"; req.setAttribute("content",str); //获取请求分派器 RequestDispatcher dispatcher = req.getRequestDispatcher("servlet06"); //将请求转发至指定路径的资源 dispatcher.forward(req,resp);//第五种:获取初始化参数信息 //获取ServletContext实例 ServletContext context = this.getServletContext(); //获取指定名称的web应用上下文初始参数的字符串值 String str = context.getInitParameter("appName"); resp.setContentType("text/html;charset=utf-8"); PrintWriter out = resp.getWriter(); out.print("获取ServletContext的初始化参数\"appName\"的字符串值为:" + str); } @Override public void destroy() { } @Override public void init() throws ServletException { }}
Step 3:在web.xml文件中注册Servlet方法
HelloServlet Servlet.HelloServlet HelloServlet /servlet/HelloServlet
1.用Servlet容器自动装载已经注册的servlet,需在web.xml配置:<loadon-startup>1</loadon-startup>,数字越小,级别越高。
2.Servlet容器首次启动后,客户首次向Servlet发送请求。
3.Servlet类文件被更新后,重新装载Servlet。
整个生命周期,初始化init()方法只被执行一次;例子执行结果:
<load-on-startup>1</load-on-startup>
<load-on-startup>2</load-on-startup>
Servlet内置对象与初始化
Servlet内置初始化参数
1.web.xml文件配置:
This is the description ofmy J2EE component This is the display nameof my J2EE component ServletInitParameter servletInitParameter.ServletInitParameter username admin password 123456
2.在servlet类中通过如下代码获取参数:
public void init() throws ServletException { this.setUsername(this.getInitParameter("username")); this.setPassword(this.getInitParameter("password")); }
相对路径和绝对路径
Servlet路径跳转
相对路径访问Servlet 绝对路径访问Servlet
相对访问路径使用"servlet/ServletPathDirection"前面不带”/”,绝对访问路径使用“/servlet/ServletPathDirection”,前面加上”/”表示当前根目录;
MVC思想