JavaWeb学习笔记(三)—— Servlet
一、Servlet概述
1.1 什么是Servlet
Servlet是sun公司提供一套规范(接口),是JavaWeb的三大组件之一(Servlet、Filter、Listener),它属于动态资源。Servlet的作用是处理请求,服务器会把接收到的请求交给Servlet来处理,在Servlet中通常需要:
- 接收请求数据;
- 处理请求;
- 完成响应。
例如客户端发出登录请求,或者输出注册请求,这些请求都应该由Servlet来完成处理!Servlet需要我们自己来编写,每个Servlet必须实现javax.servlet.Servlet接口。
1.2 Servlet的实现方式
实现Servlet有三种方式:
- 实现javax.servlet.Servlet接口;
- 继承javax.servlet.GenericServlet类;
- 继承javax.servlet.http.HttpServlet类;
Servlet类由我们来写,通常我们会去继承HttpServlet类来完成我们的Servlet,但对象由服务器来创建,并且由服务器来调用相应的方法。
1.3 Servlet的运行过程
Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
①Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。
②装载并创建该Servlet的一个实例对象。
③调用Servlet实例对象的init()方法。
④创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
⑤WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
用户第一次访问Servlt的时候,服务器会创建一个Servlet的实例,Servlet中的init()方法就会执行。任何一次请求服务器都会创建一个新的线程访问Servlet中的service方法。在service方法内部根据请求方式的不同调用doXXX方法。(get请求调用doGet,post请求调用doPost)。当Servlet从服务器中移除掉,或者关闭服务器时,Servlet的实例就会被销毁,此时destroy方法就
二、Servlet的生命周期
所谓xxx的生命周期,就是说xxx的出生、服务,以及死亡。Servlet生命周期也是如此!与Servlet的生命周期相关的方法有:
- void init(ServletConfig);
- void service(ServletRequest,ServletResponse);
- void destroy();
2.1 Servlet的创建
默认情况下,第一次访问servlet时服务器才会创建该对象,如果需要服务器启动时就创建Servlet,那么还需要在web.xml文件中配置:
<servlet> <servlet-name>hello1</servlet-name> <servlet-class>cn.itcast.servlet.Hello1Servlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>hello1</servlet-name> <url-pattern>/hello1</url-pattern> </servlet-mapping> <servlet> <servlet-name>hello2</servlet-name> <servlet-class>cn.itcast.servlet.Hello2Servlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>hello2</servlet-name> <url-pattern>/hello2</url-pattern> </servlet-mapping> <servlet> <servlet-name>hello3</servlet-name> <servlet-class>cn.itcast.servlet.Hello3Servlet</servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>hello3</servlet-name> <url-pattern>/hello3</url-pattern> </servlet-mapping>
在<servlet>元素中配置<load-on-startup>元素可以让服务器在启动时就创建该Servlet,其中<load-on-startup>元素的值必须是一个整数,表示servlet应该被载入的顺序,当值为0或者大于0时,表示容器在启动时就加载并初始化这个servlet;当值小于0或者没有指定时,则表示容器在该Servlet被请求时,才会去加载。正数的值越小,该Servlet的优先级就越高,应用启动时就优先加载。所以,<load-on-startuo>x</load-on-startuo>中x的取值1,2,3,4,5代表的是优先级,而非启动延迟时间。上例中,根据<load-on-startup>的值可以得知服务器创建Servlet的顺序为Hello1Servlet、Hello2Servlet、Hello3Servlet。通常大多数Servlet是在用户第一次请求的时候由应用服务器创建并初始化,但<load-on-startup>n</load-on-startup> 可以用来改变这种状况,根据自己需要改变加载的优先级!
而且一个Servlet类型,服务器只创建一个实例对象,例如在我们首次访问http://localhost:8080/helloservlet/helloworld时,服务器通过“/helloworld”找到了绑定的Servlet名称为cn.itcast.servlet.HelloServlet,然后服务器查看这个类型的Servlet是否已经创建过,如果没有创建过,那么服务器才会通过反射来创建HelloServlet的实例。当我们再次访问http://localhost:8080/helloservlet/helloworld时,服务器就不会再次创建HelloServlet实例了,而是直接使用上次创建的实例。
因为一个类型的Servlet只有一个实例对象,那么就有可能会出现一个Servlet同时处理多个请求,那么Servlet是否为线程安全的呢?答案是:“不是线程安全的”。这说明Servlet的工作效率很高,但也存在线程安全问题!
所以我们不应该在Servlet中创建成员变量(可以创建局部变量),因为可能会存在一个线程对这个成员变量进行写操作,另一个线程对这个成员变量进行读操作
在Servlet被创建后,服务器会马上调用Servlet的void init(ServletConfig)方法。请记住, Servlet出生后马上就会调用init()方法,而且一个Servlet的一生,这个方法只会被调用一次。这好比小孩子出生后马上就要去剪脐带一样,而且剪脐带一生只有一次。
我们可以把一些对Servlet的初始化工作放到init方法中!
2.2 Servlet的服务
当服务器每次接收到请求时,都会去调用Servlet的service()方法来处理请求。服务器接收到一次请求,就会调用service() 方法一次,所以service()方法是会被调用多次的。正因为如此,所以我们才需要把处理请求的代码写到service()方法中!
2.3 Servlet的销毁
Servlet是不会轻易离去的,通常都是在服务器关闭时Servlet才会离去!在服务器被关闭时,服务器会去销毁Servlet,在销毁Servlet之前服务器会先去调用Servlet的destroy()方法,我们可以把Servlet的临终遗言放到destroy()方法中,例如对某些资源的释放等代码放到destroy()方法中。
三、Servlet的相关配置
3.1 基本配置
<servlet> <servlet-name>abc</servlet-name> <servlet-class>com.itheima.servlet.QuickStratServlet</servlet-class> <init-param> <param-name>url</param-name> <param-value>jdbc:mysql:///mydb</param-value> </init-param> <!--服务器启动时创建servlet--> <load-on-startup>3</load-on-startup> </servlet> <servlet-mapping> <servlet-name>abc</servlet-name> <url-pattern>/quickStratServlet</url-pattern> </servlet-mapping>
3.2 url-pattern的配置方式
<url-pattern>是<servlet-mapping>的子元素,用来指定Servlet的访问路径,即URL。它必须是以“/”开头!
【完全匹配】:访问的资源与配置的资源完全相同才能访问到
<url-pattern>/quickStratServlet</url-pattern>
可以在<servlet-mapping>中给出多个<url-pattern>,例如:
<servlet-mapping> <servlet-name>AServlet</servlet-name> <url-pattern>/AServlet</url-pattern> <url-pattern>/BServlet</url-pattern> </servlet-mapping>
那么这说明一个Servlet绑定了两个URL,无论访问/AServlet还是/BServlet,访问的都是AServlet。
【目录匹配】:格式:/虚拟的目录../* *代表任意
<url-pattern>/servlet/* <url-patter>:/servlet/a、/servlet/b,都匹配/servlet/*;
【 扩展名匹配】:格式:*.扩展名
<url-pattern>*.do </url-pattern>:/abc/def/ghi.do、/a.do,都匹配*.do;
注意:第二种与第三种不要混用,通配符要么为前缀,要么为后缀,不能出现在URL中间位置,也不能只有通配符。例如:/*.do就是错误的,因为星号出现在URL的中间位置上了。*.*也是不对的,因为一个URL中最多只能出现一个通配符。
【缺省路径】/
通常情况访问html页面时,首先从当前web项目的web.xml文件寻找匹配路径,如果没有找到,再从tomcat默认的web.xml匹配,将使用缺省servlet
<servlet> <servlet-name>hello1</servlet-name> <servlet-class>cn.itcast.servlet.Hello1Servlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>hello1</servlet-name> <url-pattern>/servlet/hello1</url-pattern> </servlet-mapping> <servlet> <servlet-name>hello2</servlet-name> <servlet-class>cn.itcast.servlet.Hello2Servlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>hello2</servlet-name> <url-pattern>/servlet/*</url-pattern> </servlet-mapping>
tomcat获得匹配l路径时,优先级顺序:完全路径匹配>目录匹配>扩展名匹配
当访问路径为http://localhost:8080/hello/servlet/hello1时,因为访问路径即匹配hello1的<url-pattern>,又匹配hello2的<url-pattern>,但因为hello1的<url-pattern>中没有通配符,所以优先匹配,即设置hello1。
四、ServletConfig对象
4.1 ServletConfig的作用
ServletConfig对象是由服务器创建的,然后传递给Servlet的init()方法。ServletConfig对象对应web.xml文件中的<servlet>元素。例如你想获取当前Servlet在web.xml文件中的配置名,那么可以使用servletConfig.getServletName()方法获取!
ServletConfig对象主要是用于加载servlet的初始化参数,它可以获取<servlet>中配置的<init-param>信息。
在一个web应用可以存在多个ServletConfig对象(一个Servlet对应一个ServletConfig对象)
4.2 ServletConfig的API
-
String getServletName():获取Servlet在web.xml文件中的配置名称,即<servlet-name>指定的名称;
-
ServletContext getServletContext():用来获取ServletContext对象;
-
String getInitParameter(String name):用来获取在web.xml中配置的初始化参数,通过参数名<param-name>来获取参数<param-value>的值;
-
Enumeration getInitParameterNames():用来获取在web.xml中配置的所有初始化参数名称;
五、ServletContext对象
5.1 ServletContext概述
一个项目只有一个ServletContext对象!我们可以在N多个Servlet中来获取这个唯一的对象,使用它可以给多个Servlet传递数据。
WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用:
-
ServletContext对象的创建是在服务器启动时完成的;
-
ServletContext对象的销毁是在服务器关闭时完成的。
ServletContext对象的作用是在整个Web应用的动态资源之间共享数据!例如在AServlet中向ServletContext对象中保存一个值,然后在BServlet中就可以获取这个值,这就是共享数据了。ServletContext对象通常也被称之为context域对象。
5.2 获取ServletContext
-
在Servlet中获取ServletContext对象:Servlet接口的void init(ServletConfig config)中:
ServletContext context = config.getServletContext();,
- 在GenericeServlet或HttpServlet中获取ServletContext对象:GenericServlet类有getServletContext()方法,所以可以直接使用this.getServletContext()来获取:
ServletContext servletContext = this.getServletContext();
5.3 ServletContext的作用
【获得web应用全局的初始化参数】
Servlet也可以获取初始化参数,但它是局部的参数;也就是说,一个Servlet只能获取自己的初始化参数,不能获取别人的,即初始化参数只为一个Servlet准备!
可以配置公共的初始化参数,为所有Servlet而用!这需要使用ServletContext才能使用!
web.xml中配置初始化参数:
<context-param> <param-name>driver</param-name> <param-value>com.mysql.jdbc.Driver</param-value> </context-param>
通过context对象获得参数:
// 获得ServletContext对象 ServletContext context = this.getServletContext(); // 获得初始化参数 String initParameter = context.getInitParameter("driver"); System.out.println(initParameter);
【获得web应用中任何资源的绝对路径】
【在整个Web应用的动态资源之间共享数据】
例如在AServlet中向ServletContext对象中保存一个值,然后在BServlet中获取这个值
【ServletContext是一个域对象】
ServletContext是JavaWeb四大域对象之一(PageContext、ServletRequest、HttpSession、ServletContext)。
域对象就是用来在多个Servlet中传递数据的,所有域对象都有存取数据的功能,因为域对象内部有一个Map,用来存储数据,下面是ServletContext对象用来操作数据的方法:
-
void setAttribute(String name, Object value):用来存储一个对象,也可以称之为存储一个域属性,例如:servletContext.setAttribute(“xxx”, “XXX”),在ServletContext中保存了一个域属性,域属性名称为xxx,域属性的值为XXX。请注意,如果多次调用该方法,并且使用相同的name,那么会覆盖上一次的值,这一特性与Map相同;
-
Object getAttribute(String name):用来获取ServletContext中的数据,当前在获取之前需要先去存储才行,例如:String value = (String)servletContext.getAttribute(“xxx”);,获取名为xxx的域属性;
-
void removeAttribute(String name):用来移除ServletContext中的域属性,如果参数name指定的域属性不存在,那么本方法什么都不做;
- Enumeration getAttributeNames():获取所有域属性的名称;
六、小案例——统计访问量
public class AServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取ServletContext对象 ServletContext context = this.getServletContext(); // 从ServletContext对象中获取名为count的属性 Integer count = (Integer) context.getAttribute("count"); if (count == null) { // 如果不存在:说明是第一次访问,向Servletcontext中保存名为count的属性,值为1 context.setAttribute("count", 1); } else { // 如果存在:给访问量加1,然后再保存回去; context.setAttribute("count", count + 1); } // 向浏览器输出 PrintWriter writer = response.getWriter(); writer.print("<h1>" + count + "</h1>"); } }