一、Servlet简介
Servlet是JavaWeb或者JavaEE基础、学会了servlet, 你就入了Javaweb开发的门。Servlet是sun公司提供的一门用于开发动态web资源的技术。Sun公司提供了一套标准的ServletAPI, Servlet开发有二个步骤(1)书写servlet代码 (2) 部署到应用服务器上(Tomcat等)。同样servlet使用Java语言、具有Java语言的优点,如夸平台。
在文章之前推荐一本工具书--《head first jsp&Servlet》, 该书不仅讲述了JSP和SERVLET的基础内容, 吸引人的是他还讲述TOMCAT如何作为"应用服务器"和"SERVLET容器"。
二、Servlet开发
Servlet接口SUN公司定义了两个默认实现类,分别为:GenericServlet、HttpServlet。
HttpServlet指能够处理HTTP请求的servlet,它在原有Servlet接口上添加了一些与HTTP协议处理方法,它比Servlet接口的功能更为强大。因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口。
HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。
GenericServlet业务方法为service、不仅仅可以处理HTTP请求、还可以处理其他请求, 但是javaweb是浏览器开发,使用的基本上是HTTP请求,所以绝大多数情况下我们应该继承HttpServlet。
项目创建是web-project, 在创建servlet应该选择Servlet选项(自动填写web.xml信息), 默认路径会带上/servlet,在创建servlet是next可以选择你想要的路径。GenericServletDemo 中ServletRequest是HTTPServletRequest父类。
public class GenericServletDemo extends GenericServlet { private Book book; @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { //getParameter->获取request域的参数 String bookname=req.getParameter("bookname"); Double price=Double.parseDouble(req.getParameter("price")); book=new Book(bookname, price); //调用service业务层 //调用Dao,可以使用原生的JDBC或者使用DBUtil工具保存数据 //这里演示就打印数据 System.out.println(book); //跳转页面,GenericServlet怎么实现跳转 HttpServletRequest request=(HttpServletRequest)req; request.getRequestDispatcher("index.jsp").forward(req, res); }
class HttpServletDemo extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //假设Post和Get处理方式一直 } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //控制层操作 } }
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <servlet> <servlet-name>GenericServletTest</servlet-name> <servlet-class>it.servlet.GenericServletTest</servlet-class> </servlet> <servlet> <servlet-name>HttpServletDemo</servlet-name> <servlet-class>it.servlet.HttpServletDemo</servlet-class> </servlet> <servlet-mapping> <servlet-name>GenericServletTest</servlet-name> <url-pattern>/GenericServletTest</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>HttpServletDemo</servlet-name> <url-pattern>/HttpServletDemo</url-pattern> </servlet-mapping> </web-app>
三、Servlet与Java对象
Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎(容器)来控制和调度。
针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才会销毁。
四、Servlet的生命周期
应用服务器启动之后, Servlet不会马上被创建(优化原则),当第一次Http请求时,web服务器收到客户端的Servlet访问请求后:
①Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。
②装载并创建该Servlet的一个实例对象。
③调用Servlet实例对象的init()方法。
④创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
⑤WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
1、Servlet创建和init
Servlet是单列模式, 也就是在第一次处理请求时创建,之后的请求不会再调用,除非重启服务器或者重新部署。Servlet创建之后调用init函数,也是只执行一次,用于Servlet初始化。也就是后面的请求不会创建Servlet,更不会在调用init初始化方法。
2、service方法
tomcat服务器封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象, 然后调用Servlet的service()方法来决定调用哪个具体的方法,时doPost,或者doGet,还是其他。
3、doPost和doGet方法
处理具体的请求。
4、destroy() 方法
destroy() 方法只会被调用一次,在 Servlet 生命周期结束时被调用。destroy() 方法可以让您的 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘,并执行其他类似的清理活动。在调用 destroy() 方法之后,servlet 对象被标记为垃圾回收。
五、Servlet如何初始化程序
如果我们需要在处理请求之前做一些事情,比如创建数据库,表或者写入一些常量数据。Servlet的运行需要tomcat容器的创建的调度,如果在服务器启动的时候就马上创建一个InitServlet,调用init方法做些处理,就可以实现。这样的Servlet怎么写?
如果在<servlet>元素中配置了一个<load-on-startup>元素,那么WEB应用程序在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。
举例:
<servlet> <servlet-name>invoker</servlet-name> <servlet-class> org.apache.catalina.servlets.InvokerServlet </servlet-class> <load-on-startup>1</load-on-startup> <!-- 1 数值表示优先创建, 比如2, 就会在该Servlet创建之后在创建--> </servlet>
六、Servlet配置的杂事
Servlet配置文件为根目录WEB-INFO子目录下的web.xml文件, 他长什么样子,如下。
<!--
<servlet>标签给<Servlet-class>提供一个签名<Servlet-name> 注意一个Servlet只能有一个名字,不同的Servlet,名称不一样。
<servlet-mapping> //前端JSP请求配置内容、 意思是<url-pattern>路径下的请求由 <servlet-name>处理,在根据 <servlet-name>找到<Servlet>调用相对应的class。<servlet-mapping> 针对不用请求可以
//使用同一个servlet-class来处理。
-->
<servlet> <servlet-name>HttpServletDemo</servlet-name> <servlet-class>it.servlet.HttpServletDemo</servlet-class> </servlet> <servlet-mapping> <servlet-name>HttpServletDemo</servlet-name> <url-pattern>/HttpServletDemo</url-pattern> </servlet-mapping>
对于新手或者不注重细节的朋友来说,会发现有时web项目中居然看不见配置文件(看不见不代表没有)。而创建Servlet,是如下这样子的。
解释一下:@WebServlet("/TestServlet")为访问路径注解,web.xml可以识别这种引用,和直接在web.xml配置是一样的道理。学过Spring的朋友,肯定非常容易理解。
@WebServlet("/TestServlet") public class TestServlet extends HttpServlet { private static final long serialVersionUID = 1L; 。。。
}
七、Servlet和JSP的区别
jsp经编译后就变成了Servlet。JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的java类。Servlet中的内置对象
都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到.Jsp是Servlet的一种简化,使用Jsp只需要完成程序员需要输出到客户端的内容,Jsp中
的Java脚本如何镶嵌到一个类中,由Jsp容器完成。而Servlet则是个完整的Java类,这个类的Service方法用于生成对客户端的响应。
八、如何书写线程安全的Servlet
Servlet是单线程模式,如果存在共享的资源时,很可能会出现线程不安全的情况。Servlet线程安全是学习Servlet必须掌握的内容。不过话说回来,在以后的学习或者开发中,
线程安全都是问题。如果是JavaEE初级开发者,多线程安全是难题之一。所以需要以后不断的总结和积累,这里简述初级的解决方案。
1、Servlet单实例多线程机制
Servlet采用多线程来处理多个请求同时访问。servlet依赖于一个线程池来服务请求。线程池实际上是一系列的工作者线程集合。Servlet使用一个调度线程来管理工作者线程。
当容器收到一个Servlet请求,调度线程从线程池中选出一个工作者线程,将请求传递给该工作者线程,然后由该线程来执行Servlet的service方法。当这个线程正在执行的时候,容器收到另外一个请求,调度线程同样从线程池中选出另一个工作者线程来服务新的请求,容器并不关心这个请求是否访问的是同一个Servlet.当容器同时收到对同一个Servlet的多个请求的时候,那么这个Servlet的service()方法将在多线程中并发执行。
Servlet容器默认采用单实例多线程的方式来处理请求,这样减少产生Servlet实例的开销,提升了对请求的响应时间,对于Tomcat可以在server.xml中通过<Connector>元素设置线程池中线程的数目。
2、如何开发线程安全的Servlet程序
(1)实现 SingleThreadModel 接口
SingleThreadModel是一个标志接口,对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线程分别调用一个独立的Servlet实例对象。
这种处理,Servlet变成多例模式,好处是不存在线程安全问题,缺点也比较明显,比如需要维护大量的Servlet对象,这种方案有没谁采取,答案是有的,Struts2就是多例模式。但是Servlet本身为单例模式,我们不应该随便修改它,以免造成不必要的麻烦。在极端的情况下,或许可以,在Servlet API 2.4中,已经将SingleThreadModel标记为Deprecated(过时的),连Servlet本身都否定这种方式。
(2)使用synchronized同步代码块
这是针对多线程问题提出的解决方案。Servlet访问量高时,同步块会导致运行效率较低,服务器响应速度慢,这可能导致严重问题。和(1)一样,慎重使用。
(3)避免使用实例变量
多线程问题是由于多个线程同时操作统一资源造成的,比如Servlet的实例变量(局部变量是不影响的)和读写同一个文件。问题是这么导致的,我们就应该在源头上尽量避免。再者Servlet中定义属性或者类属性本来就没必要性。
综上可知, 解决Servlet线程安全相对比较简单,就是:慎用或者不用SingleThreadModel接口和synchronized代码块,不定义局部变量,源头上避免操作同一资源。单例模式简单易用,SpringMVC框架同样使用这种模式。