一.Servlet简介
1.什么是servlet
概念:Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
定位:Java Servlet用Java编写的服务器端程序(web application)。作用:其主要功能在于交互式地浏览和修改数据,生成动态Web内容。
理解:狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一 般情况下,我们将Servlet理解为后者。
1.1Servlet定位图
1.2Servlet核心作用
参考上图
1.3Servlet标准 API核心包(在线文档)
Servlet API有以下3个Java包:
javax.servlet(重点):其中包含定义Servlet和Servlet容器之间的类和接口
javax.servlet.http(重点):其中包含定义HTTP Servlet和Servlet容器之间的类和接口
javax.servlet.annotation:其中包含标注Servlet,Filter,Listener的注解
备注:JavaWeb中主要关注javax.servlet和javax.servlet.http的成员。
参考http://tomcat.apache.org/tomcat-7.0-doc/servletapi/index.html
二.Servlet环境设置
三.Servlet常见方法与生命周期
3.1HttpServlet处理Http请求
Servlet的service()方法是请求的入口方法,HttpServlet实现service()方法在这个入口方法中根据不同的Http请求方法(如GET、POST请求)调用不同的方法。
大多数应用程序都是要于HTTP结合起来使用。这意味着可以利用HTTP提供的特性。
包是Servlet API中的第二个包,其中包含了用于编写Servlet应用程序的类和接口,并且许多类型都覆写了中的类型。
HttpServlet类覆盖了 javax.servlet.GenericServlet 类。使用HttpServlet时,需要使用代表Servlet请求和Servlet响应的 HttpServletRequest 和 HttpServletResponse 对象。
3.2Servlet 中service 方法的应用(重点)
在讲这个知识点之前,我们先来看下面试中常问到的一个我问题,就是抽象类的作用是什么?
答:降低接口实现类对接口实现过程难度
将接口中不需要使用的抽象方法教给抽象类进行完成
这样接口实现类只需要对接口中自己所需要方法进行重写就行了
理解了这个知识点后,我们再来看service()方法:
当浏览器发出请求,请求路径到达服务器中的项目,如果用到了指定的 servlet 接口实现类来处理请求,那么此时tomcat此时会创建这个指定的servlet接口实现类的实例对象。
例如此时这个servlet接口实现类名为OneServlet,那么
1:tomcat会默认执行 Servlet oneservlet=new OneServlet();创建OneServlet这个接口实现类的实例对象。
2:使用这个实例对象调用service()方法:oneServlet.service()
那么此时会有同学好奇了,这个service()方法到底是从哪里来的呢?
一般我们在编写servlet接口实现类的时候都会让这个servlet接口实现类去继承我们的HttpServlet类,而当我们点到HttpServlet源码的时候会发现HttpServlet继承自GenericServlet,然后
GenericServlet实体类实现了我们的Servlet接口,点到Servlet接口源码我们发现了我们最"原始"的service方法。
哈哈到这里我们所描述的语言可能还是有点太死板,并且会有很多同学会问到为什么要设计的这么麻烦,为什么不直接让我们自己编写的接口实现类直接去实现Servlet接口呢?
那样不是更加方便吗?那好现在我们来回答这个问题:
首先我们会发现我们所自己设计的接口实现类在被tomcat接口实现类创建实例对象的时候所调用的方法只有servic方法,那么先来看Servlet接口中的方法都有哪些?
此时我们看到,Servlet方法中只有service方法被用到了,其他方法都没有用到,那么假如我们所设计的接口实现类去实现Servlet接口的话,就要重写这个接口中的所有方法,而用到的只有service方法,这样就会显得代码很冗余,那么此时回到最开始我们所问的抽象类的作用是什么?就是将接口中不需要使用的抽象方法教给抽象类进行完成 ,这样接口实现类只需要对接口中自己所需要方法进行重写就行了,那么我们的servlet也是这样做的,下面请看源码解析:
在源码中我们会发现,此时使用了GenericServlet抽象类继承了Servlet接口
在GenericServlet抽象类中将Servlet接口中除掉service方法以外的所有方法都进行了重写,唯独没有将Servlet接口中service方法没有重写,至于为什么这么做,我们待会说,目的其实还是为了让我们的接口实现类去重写或者继承自己想要的方法。
小tip:在这里要注意一个点,如果一个非抽象类去继承我们的接口的时候是必须去重写接口中所有的抽象方法的,如果是一个抽象类去实现了某一个接口的话,可以不重写这个接口中所有的方法,只重写一部分方法也可。
此时我们会发现还有一个抽象类HttpServlet继承了GenericServlet类,为什么这么做呢?原因是我们想在HttpServlet这个类中去重写定义我们的service方法,从而让我们最终所自定义的servlet类无需自己定义service方法,从而去继承HttpServlet类中的service方法,并且在HttpServlet中的service方法会检验前台用来发送请求的HTTP方法(通过调用request.getMethod()获取 ),然后去调用相应的doGet,doPost等方法,而HttpServlet一共定义了有下面的七种方法来处理前台用来发送请求的HTTP方法,其中doGet方法和doPost方法尤为重要:
名称 |
重要性 |
doGet |
重要且常用 |
doPost |
重要且常用 |
doHead |
了解 |
doPut |
了解 |
doTrace |
了解 |
doOptions |
了解 |
doDelete |
了解 |
欧克到了这里我们来总结一下一个请求传输的流程:
这里我们就假定我们自己定义了一个OneServlet类来处理前台的请求,前台的请求方法为get:
当请求传到了后台的servlet实现类的时候,此时tomcat会自动调用语句Servlet oneservlet=new OneServlet();创建我们OneServlet的实例对象,然后再由我们的实例对象oneservlet执行
oneservlet.service()语句来调用我们的service方法,此时的这个service方法继承于父类HttpServlet中的方法,那么我们来看下父类HttpServlet中的service方法的实现吧:
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); long lastModified; if (method.equals("GET")) { lastModified = this.getLastModified(req); if (lastModified == -1L) { this.doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader("If-Modified-Since"); } catch (IllegalArgumentException var9) { ifModifiedSince = -1L; } if (ifModifiedSince < lastModified / 1000L * 1000L) { this.maybeSetLastModified(resp, lastModified); this.doGet(req, resp); } else { resp.setStatus(304); } } } else if (method.equals("HEAD")) { lastModified = this.getLastModified(req); this.maybeSetLastModified(resp, lastModified); this.doHead(req, resp); } else if (method.equals("POST")) { this.doPost(req, resp); } else if (method.equals("PUT")) { this.doPut(req, resp); } else if (method.equals("DELETE")) { this.doDelete(req, resp); } else if (method.equals("OPTIONS")) { this.doOptions(req, resp); } else if (method.equals("TRACE")) { this.doTrace(req, resp); } else { String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[]{method}; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(501, errMsg); } }
此处首先会使用 String method = req.getMethod();语句接收到前台所发来的方法,然后运用equals方法来挨个作比较,如果前台发来的是get方法的话,那么此处便去执行this.doGet(req, resp);语句,注意此处的this为当前对象的引用,而之前我们的实例对象oneservlet调用了此service方法,所以此处的this就是我们的实例对象oneservlet,等价于oneservlet这个实例对象去执行HttpServlet父类中的doGet方法,但是我们一般在我们自己实现的servlet实现类中都会重写父类HttpServlet中的doGet,doPost这两个常用的方法(其他五个用到的时候再重写就好,此处我们只先谈到doGet和doPost方法),所以此时oneservlet这个实例对象便会去执行自己所定义的doGet方法,执行完后将结果返回给前端。
3.3Servlet 的生命周期(以及 load-on-startup 的用法)
什么叫做生命周期,指的是类所创建出来的对象从创建到销毁的全过程。
servlet 的生命周期,指的是 servlet 对象从创建到销毁的全过程。
服务器启动,servlet 对象不创建。
浏览器发出请求,请求路径到达服务器中的项目,如果用到了指定的 servlet 来处理请求,则服务器会自动的为我们创建出来一个 servlet 对象来处理请求。
servlet 对象创建完毕后,马上调用 init 方法,执行对象的初始化操作
servlet 对象在创建完毕后,是以单例的形式存储在服务器上。第 2~n 次访问的时候,就不重新创建对象了,而是使用第一次访问时创建出来的单例对象。
浏览器发出的请求,由 servlet 中的 doGet/doPost 方法来处理请求。
关闭服务器,servlet 对象销毁。
在 servlet 对象销毁之前,调用 destory 方法执行最后的处理工作,servlet 对象被标记为垃圾回收,destroy() 方法可以让您的 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘,并执行其他类似的清理活动。
加入load-on-startup 可以更改 servlet 对象的创建时机,由以前用到时才创建(懒汉)更改为启动服务器(此处指tomcat)就创建(饿汉)。(后续会讲到)
servlet 中的单例其实是一种假单例。因为 servlet 对象没有进行构造方法私有化的操作,是能够在其他的类中随意创建的(我们自己手动创建 servlet 对象没有任何意义)。
四:Servlet请求处理过程图
我们知道url和servlet实例是要有对应的映射关系的,所以往往需要我们配置web.xml来进行映射:
假设此时我们使用OneServlet接口实现类来处理前台的请求的话,那么在web.xml文件中需要指定映射关系代码,代码如下:
<servlet> <servlet-name>OneServlet</servlet-name> <servlet-class>com.bjpowernode.controller.OneServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>OneServlet</servlet-name> <url-pattern>/one</url-pattern> </servlet-mapping>
下面我们来挨个分析:
<servlet>标签的作用:将Servlet接口实现类类路径地址交给Tomcat
<servlet>
<servlet-name>OneServlet</servlet-name> <!--声明一个变量存储servlet接口实现类类路径-->
<servlet-class>com.bjpowernode.controller.OneServlet</servlet-class><!--声明servlet接口实现类类路径-->
</servlet>
等价于Tomcat 中有此句话: String OneServlet = "com.bjpowernode.controller.OneServlet
<servlet-mapping> 标签的作用:为了降低用户访问Servlet接口实现类难度,需要设置简短请求别名
<servlet-mapping>
<servlet-name>OneServlet</servlet-name>
< url-pattern>/one</url-pattern> <!--url-pattern作用为设置简短请求别名,别名在书写时必须以"/"为开头-->
</servlet-mapping>
好啦,讲完web.xml配置后我们再来将一个之前遗留的小问题:我们都知道我们的servlet接口实现类的实例对象只有当这个接口实现类检测到请求时才会创建,那么可不可以当http服务器启动的时候就去创建呢?
答案当然是可以,在web.xml配置文件中假如load-on-startup 便可以修改创建时间了,如下所示:
<servlet> <servlet-name>OneServlet</servlet-name> <servlet-class>com.bjpowernode.controller.OneServlet</servlet-class> <load-on-startup>30<load-on-startup><!--填写一个大于0的整数即可--> </servlet>