在上一篇文章中,咱们演示也证实了Servlet 是一种动态web资源开发的技术,即我能够在浏览器中输入URL,而后就能够在浏览器中看到咱们编写的Servlet资源。java
那当咱们在浏览器上一块儿一个HTTP请求以后,具体的流程是怎么样的呢?借用LinkinStar博文中的图:web
上面这副图讲解了整个HTTP请求到相应的过程,在这里进行下解释:浏览器
当在浏览器中输入URL后,会经过hosts文件/dns服务器解析为IP地址,进而找对应的服务器来提供服务,当服务器端收到请求后:tomcat
1)分析当前的这个请求访问的是当前服务器中的那个WEB应用安全
这个能够从请求行中的请求资源部分来分析出来。服务器
2)分析当前请求要访问的这个WEB应用的那个资源:多线程
从上述请求行的资源部分能够分析出请求的是什么资源并发
3)查找web.xml文件,查看有没有对应的虚拟路径,若是有则用这个虚拟路径对应的资源作出相应。app
4)服务器从response对象中获取以前写入的数据,而后组成响应发送到浏览器中。less
一个HTTP响应表明服务器向客户端回送的数据,它包括:一个状态行、若干消息头、以及实体内容。
2、Servlet的运行过程及生命周期
Servlet程序由WEB服务器调用,web服务器收到客户端的Servlet请求后,会首先检查是否已经装载并建立了该Servlet的实例对象;若是是,则直接跳到第四步,不然,执行第二步。
1)装载并建立该Servlet实例对象【加载:容器经过类家长群使用Servlet类对应的文件加载Servlet,建立:经过调用servlet构造函数建立一个Servlet对象】
2)调用Servlet实例对象的init方法
3)处理客户请求:每当有一个客户请求,容器会建立一个线程来处理客户请求,用于封装HTTP请求消息的HttpServletRequest对象和一个表明HTTP响应消息的HttpServletRespons对象,而后调用Servlet的service方法并将请求和响应对象做为参数传递进去。
4)卸载:调用destroy方法让servlet本身释放其占用的资源。
所以Servlet的生命周期能够分为5个阶段:加载、建立、初始化、处理客户请求、卸载。
(1)一般状况下,服务器会再Servlet第一次调用时候加载并建立此Servlet的实例对象,同时调用init方法作初始化操做。
(2)一旦此Servlet实例建立出来后,该实例就驻留在内存中,为后续对这个Servlet的请求作出相应的服务,每次对这个Servlet的访问都会致使Servlet中service方法执行;
(3)当web应用被移除容器或者关闭服务器的时候,随着web应用的销毁,Servlet也会被销毁,在销毁以前服务器会调用Servlet的destroy方法作一些善后的工做。
下面3个方法能够基本表明Servlet的生命周期:
*init方法,负责初始化Servlet对象
*service方法,负责响应客户的请求(调用doGET 或 doPost 方法)
*destory方法,当Servlet对象退出生命周期的时候,负责释放占用的资源。
PS:在Servlet的整个生命周期内,Servlet的init方法只有在Servlet被建立的时候被调用一次,每次对这个Servlet的访问都会致使Servlet中的service方法执行。
例如:如今浏览器连续访问Servlet 5次,内存中只有一个Sevlet对象。Servlet对象由服务器建立(建立一次),request和response由Servlet容器建立(建立5次),会调用5次service方法。
发送一次请求:http://localhost:8000/Servlet_Demo01/servlet/ServletDemo01
咱们看下控制台的输出:
可见,正常Servlet是在被第一次访问的时候加载并建立,同时开始调用service方法。
那咱们再发送5次请求看下结果如何:
可见没有打印Servlet 初始化,但发送了请求,可见service方法就被调用了,紧接着咱们中止容器看下,是否会调用destory方法。
可见当中止web服务器的时候,servlet被销毁了。
总结:能够看到,Servlet只会初始化一次,以后的话,咱们屡次访问的是同一个Servlet对象,此时,即便关掉网页,Servlet也不会销毁,只有tomcat服务器才会销毁servlet.
那么咱们有没有办法,启动web服务器的时候同时加载并建立servlet呢?方法是有的,须要在web.xml中配置:<load-on-startup>1</load-on-startup>
<servlet> <description>This is the description of my J2EE component</description> <display-name>This is the display name of my J2EE component</display-name> <servlet-name>ServletDemo01</servlet-name> <servlet-class>mdj.servlet.study.ServletDemo01</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>ServletDemo01</servlet-name> <url-pattern>/servlet/ServletDemo01</url-pattern> </servlet-mapping>
而后咱们看下现象:
可见启动的时候,就进行了建立和加载,那么咱们再次访问URL请求的时候,是否还会建立呢,答案应该是不会,看下面结果。
3、Servlet的结构
上面咱们讲解了Servlet的运行过程和生命周期,下面咱们看看其结构:
1)Servlet接口有五个方法
init初始化,就是把servlet装载到内存中,只会被调用一次
getServletConfig获取servletConfig对象
service主要的服务方法,放业务逻辑,每次都会被调用
getServletInfo获得servlet配置信息
destroy销毁该servlet,从内存中清除掉
2)继承GenericServlet
GenericServlet实现了servlet接口
而后只有一个抽象方法须要你本身去重写
那就是service方法,因此相比来讲init别的方法他都帮你实现好了,只要你写service方法就能够了。
至少看起来继承GenericServlet比直接实现servlet接口要方便
3)继承HttpServlet
由于后来发现servlet主要是为了服务于http请求的,并且发现GenericServlet对于http来讲还不够好
因此有了HttpServlet,首先它是继承自GenericServlet
而后它有不少http相关的方法,post,get,put等待
用户能够根据本身须要来实现这些方法
每一个过来的请求都会调用service方法,最后service会根据不用的请求分发到不一样的地方去作。
相比较而言,HttpServlet覆写了GenericServlet,service方法体内的代码会自动判断用户的请求方式,如为GET的请求则调用doGet方法,如为Post请求,则调用doPost方法,所以开发人员在编写servlet的时候,一般指须要继承HttpServlet,而后覆写doGet和doPost方法,不要去覆写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 = req.getDateHeader(HEADER_IFMODSINCE); 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); } }
这个就是HttpServlet中service方法中的源码;
首先咱们看到的就是String method = req.getMethod();这个就是经过request获取方法,而后根据方法判断调用哪个方法,要说明的是前面它已经定义好了这些字符串
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
须要指出的是,前面说过 HttpServlet是继承自GenericServlet,而GenericServlet须要用户实现一个service,刚才咱们看到的是HttpServlet本身的。
在GenericServlet中也有一个service方法:
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { throw new ServletException("non-HTTP request or response"); } service(request, response); }
源码是这样的,它会把原来的ServletRequest 请求直接强转换成HttpServletRequest而后再去调用它正真的service方法。
这点是须要指出的,HttpServletRequest比ServletRequest进行了进一步的封装,方法更适合http。
4、Servlet映射匹配问题
因为客户端是经过URL的地址找到对应web服务器中的资源,因此Servlet程序若是想要被外界访问,那么必须把servlet程序映射到一个URL地址上,这个工做在web.xml文件中<servlet>和<servlet-mapping>元素组成。
<servlet>元素用于注册Servlet,它包含有两个主要的子元素:<servlet-name>和<servlet-class>,分别用于设置Servlet的注册名称和Servlet的完整类名。
一个<servlet-mapping>元素用于映射一个已注册的Servlet的一个对外访问路径,它包含有两个子元素:<servlet-name>和<url-pattern>,分别用于指定Servlet的注册名称和Servlet的对外访问路径。
须要注意的是:
1.一个servlet能够对应多个servlet-mapping,从而一个servlet能够有多个路径访问。
2.url-pattern中的路径也可使用*通配符,可是只有两种固定的格式:一种格式“*.扩展名”,另外一种是正斜杠/ 开头并以/* 结尾。
一般匹配的优先级是哪一个精确找哪一个,*.后缀的格式永远匹配级最低。
5、线程安全问题
Servlet引擎采用多线程模式运行,它为并发的每一个访问请求都使用一个独立的线程来进行响应。
可是因为默认状况下Servlet在内存中只有实例存在,所以当多个浏览器并发访问Servlet的时候,就会有可能产生线程的安全问题。
Servlet线程不安全,至始至终,之维护一个实例对象,当同一个资源被多个线程同时访问操做的时候,就可能会互相干扰。
解决的方法是:
一、SingleThreadModel接口(标记接口,单线程模型接口):不能真的防止线程安全问题(已过期)
Servlet实现了SingleThreadModel接口,那么Servlet引擎将以单线程模式来调用Servlet的service方法。对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每一个线程分别调用独立的一个Servlet实例对象。
注:此接口在API 2.4中就已通过时,虽然解决了线程安全问题,可是消耗了大量性能(不一样的客户端同时访问会建立不一样的Servlet实例),因此此方法建议不用,实际开发中也不用。
二、使用同步代码块,但效率低。在Servlet中尽可能少用类变量(成员变量),若是必定要用类变量则用锁来防止线程安全问题,可是要注意锁住内容应该是形成线程安全问题的核心代码,尽可能的少锁主内容,减小等待时间提升servlet的响应速度。
几个小题目:
下面有关servlet service描述错误的是?
一、不论是post仍是get方法提交过来的链接,都会在service中处理
答:每次请求都会调用service方法,最终都会在service中处理,正确;
二、doGet/doPost 则是在 javax.servlet.GenericServlet 中实现的
答:GenericServlet只是继承了Servlet的接口,实现了它其中的5个方法,其中须要用户重写的是service方法,而doGet/doPost是由于以后出现了HttpServlet才有的,是针对http请求才有了这个类,才有了doPost和doGet,因此是错误的。
三、service()是在javax.servlet.Servlet接口中定义的
答:Servlet接口一共定义了5个方法,其中就有service(),正确;
四、service判断请求类型,决定是调用doGet仍是doPost方法
答:正确,缘由见上面。
今天就复习到此,下一节讲解Servlet中比较经常使用到的ServletConfig 和ServletContext 对象。