一、Servlet基础
1.一个Servlet程序就是一个实现了特殊接口的java类,它由支持servlet(具有servlet引擎)的web服务器调用和启动运行。一个Servlet程序负责处理它所对应的一个或一组url地址的访问请求,并接收客户端发出的访问请求信息和产生响应内容。它的请求-响应的典型过程如下图:
2.所有的servlet(自定义的)都必须实现servlet接口(javax.servlet.Servlet)。Servlet接口中有很多方法不需要实现,所以可以去继承已经实现了servlet接口的类。
1)GenericServlet类。GenericServlet实现了servlet接口的基本特征和功能。
2)HttpServlet类继承了GenericServlet类,并在其基础上进行了一些对HTTP协议的扩充。所以一般,我们编写servlet直接继承HttpServlet类,并覆盖需要的方法即可。
3.客户端每次访问一个支持HTTP的Servlet程序时,Servlet引擎都将调用Servlet的service方法来进行处理。
HTTPServletRequest用于封装HTTP请求,HTTPServletResponse用于封装HTTP响应。
二、Servlet的生命周期
1、Servlet的生命周期是由Web服务器来维护的,其相关的方法,在Servlet接口中都有定义。Servlet接口源码如下:
- package javax.servlet;
- import java.io.IOException;
- public abstract interface Servlet
- {
- public abstract void init(ServletConfig paramServletConfig)
- throws ServletException;
- public abstract ServletConfig getServletConfig();
- public abstract void service(ServletRequest paramServletRequest, ServletResponse paramServletResponse)
- throws ServletException, IOException;
- public abstract String getServletInfo();
- public abstract void destroy();
- }
2、Servlet会在服务器启动(load-on-startup 为 1)或第一次请求(load-on-startup 为 0)该Servlet的时候,初始化并开始生命周期,在服务器关闭的时候,销毁这个对象,结束生命周期。只有一个Servlet对象。多客户端并发请求该Servlet时,服务器启动多个线程分别执行该Servlet的service()方法。
3、Servlet的生命周期是由Servlet的容器来控制的,它可以分为3个阶段;初始化,运行,销毁。
第一、初始化阶段:
1)Servlet容器加载servlet类,把servlet类的.class文件中的数据读到内存中。
2)然后Servlet容器创建一个ServletConfig对象。ServletConfig对象包含了Servlet的初始化配置信息。
3)Servlet容器创建一个servlet对象。
4)Servlet容器调用servlet对象的init方法进行初始化。
第二、运行阶段:、
1)当servlet容器接收到一个请求时,servlet容器会针对这个请求创建servletRequest和servletResponse对象。
2)然后调用service方法。并把这两个参数传递给service方法。Service方法通过servletRequest对象获得请求的
信息。并处理该请求。
3)通过servletResponse对象生成这个请求的响应结果。然后销毁servletRequest和servletResponse对象。我们不管这个请求是post提交的还是get提交的,最终这个请求都会由service方法来处理。
第三、销毁阶段:
1)当Web应用被终止时,servlet容器会先调用servlet对象的destrory方法,然后再销毁servlet对象,同时也会销毁与servlet对象相关联的servletConfig对象。
2)我们可以在destroy方法的实现中,释放servlet所占用的资源,如关闭数据库连接,关闭文件输入输出流等。
4、在整个生命周期中:
1)构造方法、init()方法、destroy()方法只会被调用一次
2)每次访问会从新发一次请求调用service方法。
2)getLastModified()只有当访问方式是Get的时候才会调用,返回-1表示永远是新的,不使用缓存
5、java中与两个注解和Servlet的生命周期有关,这两个注解被用来修饰非静态方法,且不能抛出异常。
1)@PostConstruct:在构造方法之后,init()之前被调用,只调用一次。
2)@PreDestroy:在destroy()之后,Servlet被彻底卸载之前调用,只调用一次。
6、总结:Servlet的生命周期,完整应该是这样的:见下图。
7、生命周期的代码如下:
- package servlet.base;
- import java.io.IOException;
- import javax.annotation.PostConstruct;
- import javax.annotation.PreDestroy;
- import javax.servlet.ServletException;
- import javax.servlet.ServletRequest;
- import javax.servlet.ServletResponse;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- /**
- *
- * LifeCycleServlet.java
- *
- * @title Servlet的生命周期
- * @description
- * @author SAM-SHO
- * @Date 2014-9-25
- */
- public class LifeCycleServlet extends HttpServlet {
- private static final long serialVersionUID = 1L;
- public LifeCycleServlet() {
- System.out.println("调用 构造方法 ");
- }
- public void destroy() {
- System.out.println("调用 destroy()方法 ");
- super.destroy();
- }
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- System.out.println("调用 doGet()方法 ");
- }
- public void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- System.out.println("调用 doPost()方法 ");
- }
- public void init() throws ServletException {
- System.out.println("调用 init()方法 ");
- }
- @Override
- protected void service(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- System.out.println("调用 Http service()方法 ");
- super.service(req, resp);
- }
- // 只有以get方式调用该servlet的时候才会调用getLastModified
- // 返回-1 表示永远是最近的,不使用缓存
- @Override
- protected long getLastModified(HttpServletRequest req) {
- System.out.println("调用 getLastModified()方法 ");
- return super.getLastModified(req);
- }
- @Override
- public void service(ServletRequest req, ServletResponse res)
- throws ServletException, IOException {
- System.out.println("调用 service()方法 ");
- super.service(req, res);
- }
- @PreDestroy
- public void preDestroy(){
- System.out.println("调用 preDestroy()方法 ");
- }
- @PostConstruct
- public void postConstruct (){
- System.out.println("调用 postConstruct()方法");
- }
- }
- /*
- 【输出】
- 调用 构造方法
- 调用 postConstruct()方法
- 调用 init()方法
- 调用 service()方法
- 调用 Http service()方法
- 调用 getLastModified()方法
- 调用 doGet()方法
- 调用 service()方法
- 调用 Http service()方法
- 调用 doPost()方法
- 调用 destroy()方法
- 调用 preDestroy()方法
- */
三、Servlet的配置及参数:Web.xml
要运行Servlet,除了要有代码即XxxServlet这个类以后,还需要在Web.xml中配置该Servlet的信息以及一些参数,告诉Servlet容器,应该这么访问这个Servlet。
1、基本标签:
1)<servlet> :是一个Servlet的开始和结束标签,中间的部分就是一个Servlet的配置信息。
A、<servlet-name>:配置Servlet的名称,如LifeCycleServlet,可以取任一字符串,保证在web.xml中唯一。该名称组要是供其他标签使用,如<servlet-mapping>、<filter>等使用。
B、<servet-class> :配置该Servlet的类名,servlet.base.LifeCycleServlet,需要完整的包。
2)<servlet-mapping> :配置该Servlet的访问方式。
A、<servlet-name> :同<servlet>标签中配置的Servlet名称。
B、<url-pattern> :配置该Servlet的访问方式。<url-pattern>配置的值前面加上Web应用程序的路径,在加上服务器域名端口号信息,就是访问该Servlet的网址。
<url-pattern>中允许使用通配符 “*” 和 “?” ,"*"代表任意长度的字符串,“?”表示任意字符。如果<url-pattern>的配置路径为 /servlet/LifeCycleServlet.* ,就可以使用http://localhost:8080/servlet/LifeCycleServlet.xxx 访问LifeCycleServlet 这个Servlet,xxx 代表任意文件类型后缀。
C、Java EE 5以后,<servlet-mapping>可以配置多个 <url-pattern>。例如可以同时将LifeCycleServlet 配置一下多个映射方式:。这时,不论用jsp后缀、php后缀访问,都会正常显示。
- <servlet-mapping>
- <servlet-name>LifeCycleServlet</servlet-name>
- <url-pattern>/servlet/LifeCycleServlet</url-pattern>
- <url-pattern>/servlet/LifeCycleServlet.jsp</url-pattern>
- <url-pattern>/servlet/LifeCycleServlet.asp</url-pattern>
- <url-pattern>/servlet/LifeCycleServlet.php</url-pattern>
- </servlet-mapping>
D、缺省的Servlet配置。所有的web访问都是去找Servlet,服务器会有默认的缺省Servlet,如访问静态资源,就会去访问缺省Servlet,然后该Servlet会帮忙访问静态资源,显示给浏览器。Tomcat中这个缺省的Servlet为其Web.xml中定义的org.apache.catalina.servlets.DefaultServlet。
- <servlet>
- <servlet-name>default</servlet-name>
- <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
- <init-param>
- <param-name>debug</param-name>
- <param-value>0</param-value>
- </init-param>
- <init-param>
- <param-name>listings</param-name>
- <param-value>false</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
- ;servlet-mapping>
- <servlet-name>default</servlet-name>
- <url-pattern>/</url-pattern>
- </servlet-mapping>
自己定义缺省的Servlet的方法为,<url-pattern>/</url-pattern>,只配置“/”即可。
3)<load-on-startup> :配置该Servlet的加载方式,可选值为0和1或正整数。
1-Servlet会在服务器启动的死后就加载该Servlet。当是正整数时,1的优先级最高。(1>2)
0-第一次请求该Servlet的服务器才去加载该Servlet。Struts、Spring等框架都会使用该参数来预先加载核心的Servlet。
2、参数标签:
1)<init-param> :初始化参数,供某一个Servlet使用。
A、配置某个Servlet的初始化参数,配置在<servlet>便签里面,只能由这个Servlet来读取,其他Servlet读取不到。它包括一个参数名<param-name>和一个参数值<param-value>。一个Servlet可以配置多个初始化参数。
B、获取:通过方法getInitParameter() 和 getServletConfig().getInitParameter()获取,getInitParameter()方法内部其实调用的也是后者。
2)<context-param> :上下文参数,供所有Servlet使用。
A、配置所有Servlet的参数,被所有Servlet共享。也是由一个参数名<param-name>和一个参数值<param-value>组成。同样可以配置多个上下文参数。
B、获取:获取<context-param>,可以通过通过ServletContext对象。封装的方法为 getServletContext().getInitParameter()和getServletConfig().getServletContext().getInitParameter()获得。
3)总结:
A、Context和ContextPath : 一个Web工程,如名为JavaWeb,访问的路径为http://localhost:8080/JavaWeb,这整个web应用就成为一个Context,路径/JavaWeb被称为上下文路径(ContextPath )。
B、<init-param> 和<context-param>只能配置简单的字符串类型的参数,更灵活的参数配置,推荐写到xml文件或properties文件,写程序读取。
- <!-- 上下文参数 -->
- <context-param>
- <param-name>A</param-name>
- <param-value>1</param-value>
- </context-param>
- <context-param>
- <param-name>B</param-name>
- <param-value>2</param-value>
- </context-param>
3、session标签:
1)<session-config> :session标签,内置的<session-timeout> 标签可以配置session的有效期,单位是分钟。Tomcat中默认超时时间为20分钟。
2)session的超时时间为MaxInactiveInterval属性,可以利用session的方法 setMaxInactiveInterval(int s) 和 getMaxInactiveInterval() 分别设置于获取session的有效期,单位是秒。
- <!-- session -->
- <session-config>
- <session-timeout>20</session-timeout>
- </session-config>
4、过滤器标签 :
每个过滤器需要配置在web.xml中才能生效,一个Filter需要配置<filter> 和 <filter-mapping>便签。
1)<filter> :配置 Filter 名称,实现类以及初始化参数。可以同时配置多个初始化参数
2)<filter-mapping> :配置什么规则下使用这个Filter 。
A、<url-pattern> :配置url的规则,可以配置多个,也可以使用通配符(*)。例如 /jsp/* 适用于本ContextPath下以“/jsp/ ”开头的所有servlet路径, *.do 适用于所有以“ .do”结尾的servlet路径。
B、<dispatcher> :配置到达servlet的方式,可以同时配置多个。有四种取值:REQUEST、FORWARD、ERROR、INCLUDE。如果没有配置,则默认为REQUEST。它们的区别是:
# REQUEST :表示仅当直接请求servlet时才生效。
# FORWARD :表示仅当某servlet通过forward转发到该servlet时才生效。
# INCLUDE :Jsp中可以通过<jsp:include/>请求某servlet, 只有这种情况才有效。
# ERROR :Jsp中可以通过<%@page errorPage="error.jsp" %>指定错误处理页面,仅在这种情况下才生效。
C、<url-pattern>和<dispatcher> 是且的关系,只有满足<url-pattern>的条件,且满足<dispatcher>的条件,该Filter 才能生效。
3)总结:一个Web程序可以配置多个Filter ,访问有先后顺序,<filter-mapping> 配置在前面的Filter 执行要早于配置在后面的Filter 。
- <!-- 过滤器 -->
- <filter>
- <filter-name>MyFilter</filter-name>
- <filter-class>servlet.filter.MyFilter</filter-class>
- <init-param>
- <param-name>name</param-name>
- <param-value>Sam-Sho</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>MyFilter</filter-name>
- <url-pattern>/jsp/*</url-pattern>
- <url-pattern>*.do</url-pattern>
- <dispatcher>REQUEST</dispatcher>
- <dispatcher>FORWARD</dispatcher>
- </filter-mapping>
5、监听器便签:一般配置在servlet便签前面。主要有<listener> 、<listener-class> 。
- <!--监听器 -->
- <listener>
- <listener-class>servlet.listener.MyListener</listener-class>
- </listener>
6、其他
1)<mime-mapping>:如果Web应用具有用到特殊的文件,希望能保证给他们分配特定的MIME类型。一般可以配置一些允许下载的文件类型。
- <!-- 下载文件类型 -->
- <mime-mapping>
- <extension>doc</extension>
- <mime-type>application/msword</mime-type>
- </mime-mapping>
- <mime-mapping>
- <extension>xls</extension>
- <mime-type>application/msexcel</mime-type>
- </mime-mapping>
- <mime-mapping>
- <extension>pdf</extension>
- <mime-type>application/pdf</mime-type>
- </mime-mapping>
2)<env-entry>:
A、资源注射标签。初始化参数与上下文参数都需要我们主动去获取,而资源注射的内容不需要主动获取,Web启动的时候会主动把资源内容注射到servlet中。这种功能是需要通过注解完成的,@Resource负责这个功能
B、一般包括三组便签:<env-entry-name>、<env-entry-type>、<env-entry-value>,局限是只能配置java.lang包下的标准变量。
C、在代码中,用@Resource(name=")注解标注定义好的属性变量,一定要指定名字,且需要与签定义的一致,这样,在web启动的时候,资源就会注入其中,不需要自己主动去获取。
D、例如:
- <env-entry>
- <env-entry-name>uerName</env-entry-name>
- <env-entry-type>java.lang.String</env-entry-type><!-- 只能配置java.lang包下的标准变量-->
- <env-entry-value>Shao-xiao-bao</env-entry-value>
- </env-entry>
- @Resource(name="uerName")//指定名字,与配置的一致
- private String name;//资源注入的名字
E、资源注射的原理是JNDI,其本质为在web.xml中定义了名为userName的JNDI资源,然后使用 @Resource标签把定义好的资源注射到servlet中。如果不使用@Resource便签,通过查找JNDI资源同样获取。
- Context context = new InitialContext();
- String name = (String) context.lookup("userName");
- System.out.println("利用JNDI获取的资源注入的参数" + name);
F、注解多少会影响到web服务器启动的速度,因为服务器在启动的时候会遍历Web应用的WEB-INF/classes 下的所有class文件,以及lib下的所有jar包,以检查哪些类使用了注解。如果应用程序中没有使用注解,我们可以把这种注解检查功能关掉:通过把web.xml中<web-app>标签的 metadata-complete 属性设为"true"。
- <web-app version="2.5" metadata-complete="true"
- xmlns="http://java.sun.com/xml/ns/javaee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
- http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
7、完整代码如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app version="2.5" metadata-complete="true"
- xmlns="http://java.sun.com/xml/ns/javaee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
- http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
- <!--metadata-complete="true" 关掉服务器启动时的注解扫描检查 -->
- <!-- 参数 -->
- <context-param>
- <param-name>A</param-name>
- <param-value>1</param-value>
- </context-param>
- <context-param>
- <param-name>B</param-name>
- <param-value>2</param-value>
- </context-param>
- <!-- 过滤器 -->
- <filter>
- <filter-name>MyFilter</filter-name>
- <filter-class>servlet.filter.MyFilter</filter-class>
- <init-param>
- <param-name>name</param-name>
- <param-value>Sam-Sho</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>MyFilter</filter-name>
- <url-pattern>/jsp/*</url-pattern>
- <url-pattern>*.do</url-pattern>
- <dispatcher>REQUEST</dispatcher>
- <dispatcher>FORWARD</dispatcher>
- </filter-mapping>
- <!--监听器 -->
- <listener>
- <listener-class>servlet.listener.MyListener</listener-class>
- </listener>
- <!-- Servlet -->
- <servlet>
- <servlet-name>LifeCycleServlet</servlet-name>
- <servlet-class>servlet.base.LifeCycleServlet</servlet-class>
- <init-param>
- <param-name>a</param-name>
- <param-value>1</param-value>
- </init-param>
- <init-param>
- <param-name>b</param-name>
- <param-value>2</param-value>
- </init-param>
- <!--
- 初始化时机: 1-Servlet会在服务器启动的时候初始化 2-第一次真正访问这个Servlet的时候初始化
- -->
- <load-on-startup>0</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>LifeCycleServlet</servlet-name>
- <url-pattern>/servlet/LifeCycleServlet</url-pattern>
- <url-pattern>/servlet/LifeCycleServlet.jsp</url-pattern>
- <url-pattern>/servlet/LifeCycleServlet.asp</url-pattern>
- <url-pattern>/servlet/LifeCycleServlet.php</url-pattern>
- </servlet-mapping>
- <!-- 资源注入 -->
- <env-entry>
- <env-entry-name>userName</env-entry-name>
- <env-entry-type>java.lang.String</env-entry-type><!-- 只能配置java.lang包下的标准变量-->
- <env-entry-value>Shao-xiao-bao</env-entry-value>
- </env-entry>
- <!-- 下载文件类型 -->
- <mime-mapping>
- <extension>doc</extension>
- <mime-type>application/msword</mime-type>
- </mime-mapping>
- <mime-mapping>
- <extension>xls</extension>
- <mime-type>application/msexcel</mime-type>
- </mime-mapping>
- <mime-mapping>
- <extension>pdf</extension>
- <mime-type>application/pdf</mime-type>
- </mime-mapping>
- <!-- session -->
- <session-config>
- <session-timeout>20</session-timeout>
- </session-config>
- <welcome-file-list>
- <welcome-file>index.jsp</welcome-file>
- </welcome-file-list>
- <error-page>
- <error-code>404</error-code>
- <location>/ErrorPage.jsp</location>
- </error-page>
- </web-app>
四、线程安全
1、Servlet只有只有一个实例,多个用户请求同一个Servlet时,会派生出多条线程执行Servlet的代码,因此Servlet是线程不安全的。
2、Servlet不是线程安全的,多线程并发的读写绘导致数据的不同步,下例中,尽量不要定义name属性,而是把name变量分别定义在doGet() 和doPost()方法内。
3、如果是并发的读取servlet的属性的话,并不会导致数据的不同步,所以Servlet里面的只读属性做好定义为final类型的。
- package servlet.base;
- import java.io.IOException;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- /**
- *
- * ThreadSafetyServlet.java
- *
- * @title servlet线程安全
- * @description
- * @author SAM-SHO
- * @Date 2014-9-25
- */
- public class ThreadSafetyServlet extends HttpServlet {
- private static final long serialVersionUID = 2957055449370562943L;
- private String name;
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- name = request.getParameter("name");
- try {
- Thread.sleep(5000);//为了测试,让线程睡一会
- } catch (InterruptedException e) {
- }
- response.getWriter().println("您好, " + name + ". 您使用了 GET 方式提交数据");
- }
- public void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- name = request.getParameter("name");
- response.getWriter().println("您好, " + name + ". 您使用了 POST 方式提交数据");
- }
- }