Servlet

Servlet概述

SUN公司提出了Servlet规范后Java在Web领域才有了一席之地。Servlet规范不仅规范了Servlet容器,还规范了JavaWeb应用结构和Servlet代码结构。

Servlet容器为JavaWeb应用提供运行时环境,它负责管理Servlet和JSP的生命周期,以及管理他们的共享数据。

Servlet容器也称为JavaWeb容器,或者Servlet容器。

一个Java Web应用程序是由一组Servlet、HTML页面、类,以及其他资源组成的运行在web服务器上的完整的应用程序,以一种结化的有层次的目录形式存在。组成Web应用程序的这些文件要部署在相应的目录层次中,根目录代表整个web应用程序的“根”。

通常将web应用程序的目录放在webapps目录下,在webapps目录下的每一个子目录都是一个独立的web应用程序,子目录的名字就是web应用程序的名字,也就是web应用程序的“根”。用户通过web应用程序的“根”来访问web应用程序中的资源。

Servlet规范中定义了web应用程序的目录层次:
应用程序的根目录(名字随便起):

WEB-INF(大写,并且是中划线)
    --classes(当前应用程序java文件编译出来的.class文件)
    --lib当前应用程序关联的jar文件
    --web.xml配置文件

第一个Servlet程序

1:搭建JavaWeb项目:

  1. 创建一个Java项目:HelloServletWeb;
  2. 在HelloServletWeb中创建一个文件夹webapp,表示web项目的根
  3. 在webapp中创建WEB-INF文件夹
  4. 在WEB-INF中创建文件夹:lib、classes
  5. 在WEB-INF中去Tomcat根/conf拷贝web.xml文件,只需要保留根元素。
  6. 把当前项目的classpath路径改成webapp/WEB-INF下的classes中

2:编写Servlet:

  1. 为该项目增加Servlet的支持
    1.1:把Tomcat根/lib中servlet-api.jar文件拷贝到项目WEB-INF下的lib中
    1.2:在项目中选择servlet-api.jar,鼠标右键,build path-->add to build path
  2. 开发Servlet程序:
    2.1:定义一个类HelloServlet,并让该类去实现javax.servlet.Servlet接口;
    2.2:实现Servlet接口 中的init,service,destory等方法

注意:若生成方法中的参数是arg0或者arg1等格式的,原因是还没有关联源代码:关联上:apache-tomcat-7.x.x-src.zip,并重新实现/覆写方法就OK;

3:配置Servlet:HelloServlet仅仅是一个普通的实现类而已,而我最终要运行在Tomcat服务器中,所以得告诉Tomcat,来帮我管理HelloServlet类:

  1. 找到项目根目录下的WEB-INF下的web.xml文件;
  2. 在根元素web-app中创建一个新的元素节点:servlet
  3. 在根元素web-app中创建一个新的元素节点:servlet-mapping

在web.xml中的配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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_3_0.xsd"
  version="3.0">

    <servlet>
        <!-- 为Servlet起一个名字 -->
        <servlet-name>HelloServlet</servlet-name>
        <!-- Servlet类的全限定名称 -->
        <servlet-class>ee.coding.servlet.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <!-- 该限定名必须和上面servlet节点中的servle-tname一样 -->
        <servlet-name>HelloServlet</servlet-name>
        <!-- 给servlet映射的路径 -->
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

</web-app>

4:部署访问:

在Server.xml文件的Host元素中:

<Context docBase="D:\WorkSpace\HelloServlet\webapp" path=""/>

通过:http://localhost/hello 访问

Servlet的生命周期和执行流程

Servlet的生命周期

Servlet容器为JavaWeb应用提供运行时环境,它负责管理Servlet和JSP的生命周期,以及管理他们的共享数据。

Servlet生命周期方法

public class HelloServlet implements Servlet {
    
    /*
     *1.执行一次创建一个对象 
     */
    public HelloServlet() {
    }

    /*
     * 2.Servlet对象被创建后执行该方法,并且只执行一次
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
    }

    /*
     *3.每次客户请求都执行 
     */
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        System.out.println("Hello!");
    }
    
    /*
     *4.正常关闭服务器并且该Servlet对象被销毁的时候执行 
     */
    @Override
    public void destroy() {
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }
}

结论:Servlet对象是单例的,通过缓存的方式实现

为该Servlet指定一个访问地址:

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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_3_0.xsd"
  version="3.0">
    
    <servlet>
        <!-- 为Servlet起一个名字 -->
        <servlet-name>HelloServlet</servlet-name>
        <!-- Servlet类的全限定名称 -->
        <servlet-class>ee.coding.servlet.HelloServlet</servlet-class>
        <!-- 服务器启动的时候就会创建该Servlet对象,并且执行init方法,数字越小越先加载执行 -->
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <!-- 该限定名必须和上面servlet节点中的servle-tname一样 -->
        <servlet-name>HelloServlet</servlet-name>
        <!-- 给servlet映射的路径 -->
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

</web-app>

Servlet执行流程

  1. 在浏览器上请求:http://localhost:8080/hello
  2. 浏览器发送请求信息:
    GET /life HTTP/1.1
    Host: localhost:8080
  3. Tomcat接收到浏览器发送的请求信息,根据请求信息中的Host的值找虚拟主机
  4. 解析请求中的资源路径,并到项目的web.xml中去匹配<url-pattern>,假如没有匹配到则返回404错误,如果匹配到,寻找对应servlet-name
  5. 根据<servlet-name>的名字到Servlet对象缓存池中查询。若找到执行第7步;
  6. 根据<servlet-name>找到<servlet-class>值,使用反射创建Servlet实例,并放到Servlet对象缓存池中;
  7. Servlet容器调用该Servlet对象上的init方法,并传入封装web.xml配置信息的ServletConfig对象;
  8. Servlet容器调用该Servlet对象上的service执行,并且传入封装了客户端请求信息的request对象和向客户端返回响应信息的response对象;
  9. 如果tomcat关闭,当Servlet要被销毁的时候,Servlet容器就会调用destroy方法。

ServletConfig接口

Servlet容器会将解析出的Servlet相关信息封装在ServletConfig对象中,当Servlet初始化时将ServletConfig对象传入init方法中。

可以在Servlet中通过Servlet对象获取servlet相关的配置信息,ServletConfig的方法看API。

为HelloServlet配置一些初始化参数:

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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_3_0.xsd"
  version="3.0">

    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>ee.coding.servlet.HelloServlet</servlet-class>
        <!-- 为HelloServlet提供一些初始化参数 -->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
        <init-param>
            <param-name>language</param-name>
            <param-value>en</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

</web-app>

Servlet继承体系

GenericServlet实现了Servlet接口和ServletConfig接口,里面提供了一些简便的访问ServletConfig对象中信息的方法。

HttpServlet

HttpServlet中主要是处理HTTP请求,它根据客户的请求方式在service方法中将请求处理分为几种方式。最常使用GET和POST,分别覆盖doGet和doPost方法即可。也可以直接通过覆盖service(HttpServletResponse,HttpServletResponse)方法来统一处理所有请求方式的请求。

HttpServletRequest

HttpServletRequest是ServletRequest接口的子接口,表示HTTP协议的请求对象。既然HttpServletRequest是HTTP的请求对象,那么该接口中包含了获取各种请求信息的方法。

常用方法:

String getContextPath():获取上下文路径,<Context path="上下文路径".../>
String getHeader(String headName):根据指定的请求头获取对应的请求头的值
String getRequestURL():返回当前请求的资源名称。上下文路径/资源名称
StringBuffer getRequestURL():返回浏览器地址栏的内容
String getRemoteAddr():返回请求服务器的客户端的IP

获取请求参数的方法:

String getParameter(String name):根据参数名称,获取对应参数的值
String[] getParameterValues(String name):根据参数名称,获取该参数的多个值
Enumeration<String> getParameterNames():获取所有请求参数的名字
Map<String,String[]> getParameterMap():返回请求参数组成的Map集合

中文乱码问题

Tomcat接收请求的时候,默认使用的是ISO-8859-1编码,而该编码只占一个字节,不支持中文(两个字节)

解决方案:

  1. 对乱码使用ISO-8859-1解码 ---> byte数组
  2. 对byte数组重新使用UTF-8编码
//使用ISO-8859-1解码,恢复为二进制
byte[] data = username.getBytes("ISO-8859-1");
//重新使用UTF-8编码
username = new String(data, "UTF-8");

POST方式:

request.setCharacterEncoding("UTF-8");//设置  请求参数的编码方式

注意:必须在获取第一个参数之前设置,只对POST方式有效

GET方式:可以修改Tomcat对GET方式的编码(不建议修改)

71    <Connector port="80" protocol="HTTP/1.1"
72               connectionTimeout="20000"
73               redirectPort="8443" />

等价于:

71    <Connector port="80" protocol="HTTP/1.1"
72               connectionTimeout="20000"
73               redirectPort="8443" 
74               URLEncoding="ISO-8859-1"
75               />

改成:UTF-8:

71    <Connector port="80" protocol="HTTP/1.1"
72               connectionTimeout="20000"
73               redirectPort="8443" 
74               URLEncoding="UTF-8"
75               />

HttpServletResponse

HttpServletResponse是ServletResponse的子接口,表示HTTP协议的响应对象。该接口中包含了处理响应的方法。

常用方法:

OutputStream getOutputStream():获取字节输出流,文件下载
Writer getWriter():获取字符输出流,输出内容

注意:以上两个方法不能共存,只能使用其中一个。

设置响应的编码:

response.setCharacterEncoding("UTF-8")

设置响应的MIME类型:

response.setContentType("text/html");//不要写错了

上述两个操作可以合并为:

response.setContentType("text/html;character=utf-8");

如果向客户端输出中文内容,为了防止乱码必须指定编码:

response.setContentType("text/html;character=utf-8");
PrintWriter printWriter = response.getWriter();
printWriter.println("这是返回给客户端的内容");

设置响应头和响应编码:

response.setHeader("Locatioin","http://localhost/docs");
response.setStatus(307);

Servlet映射细节

1.同一个Servlet可以配置多个url-pattern
2.资源通配符配置:*(任意个数的任意字符)

第一种:/*或者 /system/*
    /*:随便一个字符,都可以访问当前Servlet
    /system/*:所有以/system/打头的资源名才可以访问该Servlet(登陆验证)
第二种:*.拓展名
    *.do:资源名必须以.do结尾才可以访问当前Servlet

3.配置Servlet的时候,<servlet-name>不能起名为default:在Tomcat中,主web.xml文件规定了:访问静态资源都得通过default的Servlet
4.Servlet对象的生命周期:

第一次访问的:
    构造器 ---> init ---> service
第N次:
    service
在框架中,我们习惯给Servlet配置:<load-on-startup>来决定其构建和初始化顺序

学习Struts1/SpringMVC的时候,优先启动该Servlet(该Servlet主要负责加载资源和初始化操作)
5.从Tomcat7开始支持Servllet的注解映射:首先保证:web-app的metagata-complete属性为false,缺省值就是false

<web-app 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_3_0.xsd"
  version="3.0" metadata-complete="false">

</web-app>

Servlet线程安全

Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由web容器负责的。在Servlet对象缓冲池中,同一个Servlet只会存在一个实例。这样,当两个或多个线程同时访问同一个Servlet时,会发生多个线程同时访问同一资源的情况,数据可能变得不一致。所以在用Servlet构建的Web应用时如果不注意线程安全的问题,会使所写的Servlet程序有难以发现的错误。

解决方案:

  • 让Servlet实现 javax.servlet.SingleThreadModel接口:同时只能让一个线程来访问资源,若是多个资源,进入等待(不推荐);
  • 在service()中不要使用成员变量。Struts1,SpringMVC也是线程不安全的,Struts2是线程安全的。

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注