Servlet的基本使用

1、Servlet的基本介绍

servlet(全称:server applat)是运行在 Web 服务器或应用服务器上的程序,依赖于服务器才能运行。Servlet 类实际上就是一个接口,它没有主方法,没有main方法。

servlet 可以由 tomcat 来执行,它定义了能被 tomcat 识别的规则。

 

2、Servlet 的基本使用

servlet 就是一个接口,通过一个类来 implements 实现这个接口,再在 web.xml 中配置该类,该类就可以依赖于服务器运行起来,无需主方法。

一个简单的类实现 servlet :需要导入javax.servlet等一些类。

package cn.servletPackageTest;

import javax.servlet.*;
import java.io.IOException;

public class ServletTest01 implements Servlet {

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        
    }

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

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {

    }

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

    @Override
    public void destroy() {

    }
}

在 java web 项目中的 web.xml 配置文件下配置该 servlet :

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>test01</servlet-name>
        <servlet-class>cn.servletPackageTest.ServletTest01</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>test01</servlet-name>
        <url-pattern>/test01</url-pattern>
    </servlet-mapping>
</web-app>

其中,servlet-class 是完整的类名,servlet-name 给你所配置的类名指定了一个名称,下面的 servlet-mapping 里面的 servlet-name 跟上面的 servlet-name 相匹配,url-pattern 指定的是你今天访问该 servlet 的路径,配置完后你就可以通过 ' 资源路径 + url-pattern ' 来访问 servlet,比如:http://localhost:8080/test01 

 

2.2、使用注解来配置(@WebServlet())

在 servlet 3.0 及以上的版本当中,servlet 可以不用 web.xml 文件进行配置,而是使用注解配置:@WebServlet()。JDK6 及以上即可支持 servlet 3.0 及以上版本

由此我们在创建一个 java ee 项目时就可以不用创建 web.xml 文件,直接在 servlet 上进行配置:

@WebServlet(urlPatterns = "/demo01")
//或者直接写即可,无需写urlPatterns,如下:
//@WebServlet("/demo01")
public class ServletDemo01 implements Servlet {
    
   .....

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("servlet 3.0 测试。。。");
    }
    
    .....  
}

 

@WebServlet() 里面的 urlpatterns 可以有多个写法:

//写成数组,匹配多个路径
@WebServlet({"/demo01", "/demo02", "demo03"})

//多层路径
@WebServlet({"/demo01/demo02"})

//使用 * ,注意此时前面没有 / 
@WebServlet({"*.do")

 

3、servlet 的生命周期(init()、service()、destroy())

Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:

  • Servlet 通过调用 init () 方法进行初始化。
  • Servlet 调用 service() 方法来处理客户端的请求。
  • Servlet 通过调用 destroy() 方法终止(结束)。

3.1、init() 方法

init() 方法只会被调用一次,默认情况下 init() 方法在 Servlet 创建时执行,而 Servlet 会在用户第一次调用该 Servlet 时被创建,所以 init() 方法只会在第一次调用 servlet 时执行,在后续每次用户请求调用时都不会被调用。

你也可以通过 <load-on-startup> 标签来指定 servlet 的 init() 方法在服务器第一次启动时被加载执行。

<servlet>
    <servlet-name>demo01</servlet-name>
    <servlet-class>cn.itcast.web.servlet.ServletDemo01</servlet-class>
    <!--指定servlet的创建时机
        值为负数时,在第一次被访问时创建
        值为0或正数时,在服务器启动时就创建-->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>demo01</servlet-name>
    <url-pattern>/demo01</url-pattern>
</servlet-mapping>

 

3.2、sevice() 方法

service() 方法是 servlet 用来处理客户端请求的,它会被服务器自动调用,Servlet 容器(即 Web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端。service() 方法在每次客户端请求 servlet 时都会被调用。

每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。service() 方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet、doPost、doPut,doDelete 等方法。

 

3.3、destroy() 方法

destroy() 方法会在 servlet 正常关闭时执行,在执行完 destroy() 方法后,servlet 才会被关闭。只有在服务器被正常关闭时,destroy() 方法才会被执行。 

 

4、servlet 是单例的

servlet 的 init() 方法只会执行一次,说明一个 servlet 只会被创建一次,一个 servlet 在内存中只存在一个 servlet 实例对象。所以说,servlet 是单例的。

由于 servlet 是单例的,所以在多个用户同时访问同一个 servlet 时,实际上访问的都是同一个实例对象,此时就有可能会存在线程安全问题。比如说 servlet 有一个成员变量 num,当任何人都可以通过调用 servlet 的 service() 方法来获取、输出、改变 num,这就有可能会导致线程安全问题,数据错乱。

可以通过加锁来解决线程安全问题,将方法改为同步方法,比如在张三处理完 service() 方法后,李四才能调用 service() 方法。但是这不适用于 servlet,因为对性能影响太大。

解决方法:

在一个对象当中,成员变量是被共享的,但是局部变量是不被共享的。所以我们建议尽量不要在 servlet 中定义成员变量,而是在 service() 方法内部定义局部变量。因为局部变量不被共享,所以彼此之间的函数调用不会产生影响。即使是在 servlet 中定义了成员变量,也不要在方法体内对其进行赋值。

 

5、HttpServlet类

HttpServlet 是一个抽象类,它继承了 GenericSerevlet 类(该也是一个抽象类,它实现了 Serlvet 接口)。HttpServlet 是专门用来处理 http 请求的,通过继承 HttpServlet 类可以获取 http 请求数据和响应 http 请求。

继承 HttpServlet 类无需覆写 service() 方法,只需覆写 http 响应的方法,比如 doGet()、doPost()、doDelete() 等。HttpServelt 类的 service() 方法内部会判断 servlet 所接收到的 http 请求方式,然后再来调用相应的 doGet、doPost等到方法。

package sessiontest;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/demo02")
public class SessionTest01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("get请求");
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String name = "myName";
        response.setCharacterEncoding("utf-8");
        request.setCharacterEncoding("utf-8");
        response.setContentType("application/json; charset=utf-8");
        //拼接json数据
        String jsonStr = "{\"name\":\""+name+"\",\"age\":\"20\"}";
        //将数据写入流中
        response.getWriter().write(jsonStr);
    }
}

 

6、request 对象

HttpServletRequest 接口继承于 ServletRequest 接口,可以通过 HttpServletRequest 对象或者 ServletRequest 对象来获取请求的信息。

 

6.1、获取请求行数据(方法名、URL、参数等等)

请求报文的 “请求行” 只有三个内容,即:方法、请求URL、以及HTTP的版本。在 get 请求当中,我们可以通过 URL 来得到请求参数。

request 对象可以通过一系列方法来获取请求行的数据:

  • reqObj.getMethod():获取请求的 HTTP 方法的名称,该方法返回一个字符串,例如,GET、POST 或 PUT。
  • reqObj.getQueryString():获取包含在URL后的请求参数,适用于GET请求
  • reqObj.getContextPath():获取请求的虚拟目录
  • reqObj.getRequestURI():获取请求的URI

示例:

//假设请求为:
http://localhost:8080/servlet_test01_war_exploded/demo03?username=wen&age=12

@WebServlet("/demo03")
public class ServletDemo03 extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     //获取请求方式,比如:GET
     String method = request.getMethod();

    //获取包含在URL后的请求参数,适用于GET请求,比如获取到的结果:username=wen&age=12
     String queryString = request.getQueryString();

     //获取请求的虚拟目录,比如:/servlet_test01_war_exploded
     String contextPath = request.getContextPath();
 
     //返回请求的URL:http://localhost:8080/servlet_test01_war_exploded/demo03
     StringBuffer requestURL = request.getRequestURL();

     //返回请求的URI:/servlet_test01_war_exploded/demo03
     String requestURI = request.getRequestURI();
  }
}

 

6.2、获取请求头数据(host、referer、user-agent等等)

浏览器通过请求头告诉服务器关于浏览器的一些信息,格式: 请求头名称:请求头值。

各重要请求头信息如下:

 

我们可以通过 HttpServletRequest 的 getHeaderNames() 方法获取所有的请求头名称,该方法返回一个枚举 Enumeration ,通过遍历该枚举可以获取请求头名称,然后再通过 getHeader(nameStr) 方法可以根据请求头名称来获取请求头的值。

//假设请求为:
http://localhost:8080/servlet_test01_war_exploded/demo03?username=wen&age=12

@WebServlet("/demo03")
public class ServletDemo03 extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取请求的所有请求头的名称,返回一个枚举类型值
    Enumeration<String> headerNames = request.getHeaderNames();
   
    //遍历枚举类型的值
    while (headerNames.hasMoreElements()) {         
         String name = headerNames.nextElement();
         //根据请求头名称来获取请求头数据
         String value = request.getHeader(name);
         System.out.println(name + " --- " + value);
    }

    //比如直接根据请求头名称来获取请求头数据,名称不区分大小写
    String userAgent = request.getHeader(“user-agent”);
    System.out.println(userAgent );
}

上面获取到的请求头 -- 值可能为:

host --- localhost:8080
connection --- keep-alive
cache-control --- max-age=0
upgrade-insecure-requests --- 1
user-agent --- Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
accept --- text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
sec-fetch-site --- none
sec-fetch-mode --- navigate
sec-fetch-user --- ?1
sec-fetch-dest --- document
accept-encoding --- gzip, deflate, br
accept-language --- zh-CN,zh;q=0.9
cookie --- JSESSIONID=3F30DF9F2CF0C9A253C5D5CAA8F94A10; Idea-2ee3f81a=20bc9b1a-fb07-4e10-b72e-be28df5a80fa

 

6.3、获取请求体数据(适用于post请求)

只有 post 请求才有请求体,post 请求在请求体中封装了请求参数。GET 请求没有请求主体,它的请求参数放在请求 URL 中,比如:www.baidu.com?username=aaa。

要想获取请求体数据,应该先获取流对象(字符流、字节流),然后再从流对象中拿数据。

通过 BufferedReader brObj = reqObj.getReader() 可以获取字符流对象,通过字符流对象获取的只能是文本字符数据。

通过 ServletInputStream inputObj = reqObj.getInputStream() 可以获取字节流对象,通过字节流对象可以获取所有类型的数据,比如图像、文件等

 

6.3.1、通过字符流获取请求体数据(getReader() )

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取字符流
    BufferedReader br = request.getReader();

    //读取数据
    String line = null;
    while ((line = br.readLine()) != null) {
        System.out.print(line);    //获取到的数据格式类似于:name=wen&age=12
    }
}

6.3.2、通过字节流获取请求体数据

 

6.4、通用的获取请求参数的方法

获取请求参数通用方式,不论 get 还是 post 请求方式都可以使用下列方法来获取请求参数

  • String str = reqObj.getParameter(name):根据参数名称获取参数值。
  • String[] strArr = reqObj.getParameterValues(name):根据参数名称获取参数值数组。适用于参数出现一次以上,例如复选框 fruit=apple&fruit=banana。
  • Enumeration<String> = reqObj.getParameterNames():获取请求的所有参数名称。该方法返回一个枚举 Enumeration ,对该枚举进行遍历可以分别获取参数名称。Enumeration 可参考:https://www.runoob.com/java/java-enumeration-interface.html
  • Map<String, String[]> = reqObj.getParamterMap():获取所有参数的map集合。对该map集合进行遍历可以获取参数键值对

代码示例:

@WebServlet("/my02")
public class MyServlet02 extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //根据参数名称获取参数值
        String userName = request.getParameter("username");
        System.out.print(userName);

       //获取请求的所有参数名称
       Enumeration<String> parNames = request.getParameterNames();
       while (parNames.hasMoreElements()) {
            String name = parNames.nextElement();
            System.out.print("键:" + name);
            String value = request.getParameter(name);
            System.out.print("值:" + value);
       }
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {
        doPost(request, resp);
    }
}

 

6.5、获取请求中文参数乱码问题

服务器端在获取到前端发过来的请求参数时,tomcat 默认会使用 ISO8859-1进行解码操作,所以参数如果是中文的话就一定会有乱码问题。

(在 tomcat8 中 get 请求你可能不会出现中文乱码问题,因为tomcat8内置帮我们解决了中文乱码问题)

 

6.5.1、解决get请求乱码问题

get 接口解决中文乱码问题:

String user=newString(request.getParameter("user").getBytes("ISO-8859-1"),"UTF-8");

由此在获取 user 参数时会使用 utf-8 编码,就能解决乱码问题。该方法同样适用于 post 请求。

注意,是使用 utf-8 还是其他编码是由你页面的编码格式决定的,类似于下面的一行:

 

或者是在浏览器发送前使用JS函数对要传递的中文进行两次 encodeURIComponent(encodeURIComponent("中文参数")),服务端在接受时使用 java.net.Decoder.decode() 进行相应的解码,便可以得到正确的中文参数。但是这种方式经过测试,如果将参数中的特殊符号,比如 | 等一同编码两次,会出现服务端接收不到参数的问题,原因是经过2次编码之后的字符串与浏览器URL地址规则冲突。所以最好仅对参数中的中文部分进行编码后再传输。

 

6.5.2、解决post请求乱码问题

对于Post方式提交的数据,可以使用 request.setCharacterEncoding("utf-8") 来明确指定获取请求参数时使用编码即可。是使用 utf-8 还是其他编码也是由你页面的编码格式决定的,

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("post请求。。。");

        request.setCharacterEncoding("utf-8");

        String str = request.getParameter("name");   //获取请求参数
        System.out.print(11 + str);
}

此种方式只对Post方式提交有效。这是因为 request.setCharacterEncoding("utf-8") 只设置请求实体的编码,而GET提交的数据是存放在请求行中的[资源名?param1="张三"&param2=123],所以对GET请求的方式无效。

 

7、请求转发(getRequestDispatcher)

请求转发是一种在服务器内部的资源跳转方式。我们可以使用 request 对象获取转发器对象 RequestDispatcher,然后通过该对象的 forward() 方法来进行转发。

如下,在访问该接口的某个 get 请求时,又转发至 test02 请求。

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("/test02").forward(req, resp);  //请求转发的路径此时不需要写上虚拟目录,因为该资源是给服务器访问的
}

请求转发有以下特点:

  • 浏览器地址栏路径不会发生变化
  • 只能转发到当前服务器的内部资源
  • 虽然转发了请求,但前端只有一次请求。即一次请求,所以我们可以使用 request 域对象来共享数据

 

7.1、request域

request 是表示一个请求,只要发出一个请求就会创建一个request,它的作用域仅在当前请求中有效。用处:常用于服务器间同一请求不同 servlet 之间的参数传递。

通过请求转发,我们可以通过下列一些方法,在一个请求不同 servlet 之间传递参数:

request.setAttribute(String name, Object obj);   //存储数据
reuqest.getAttribute(String name);   //通过键获取值
request.removeAttribute(String name);  //通过键移除键值对

示例:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setAttribute("name", "wen");   //存储数据,在转发请求的下一个servlet就可以获取该数据
        req.getRequestDispatcher("/test02").forward(req, resp);
}

 

8、response响应对象

8.1、设置响应行

  • void setStatus(int sc):设置状态码

 

8.1.1、重定向(sendRedirect)

在 Java 中,可以通过设置状态码为 302,并且将 location 响应头为 URL 地址,由此来设置重定向。

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setStatus(302);
        resp.setHeader("location", "./test02.html");  //这里的location可以是绝对路径或者相对路径,浏览器将会根据该路径来访问资源。如果是相对路径则是相对于浏览器当前访问的资源的,即相对路径是相对于重定向之前的资源。
}

 

Java 提供了一个简单的方法来设置重定向:sendRedirect():

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       resp.sendRedirect("http://www.baidu.com");
}

 

重点向的特点:

  • 地址栏会自动发生变化,即浏览器会自动跳转访问重定向后的资源。(注意,当前端使用 ajax 请求后端数据时,后端返回重定向,前端是不会自动跳转页面的。如果需要跳转,可以将响应头的 location 数据作为 url 进行跳转)
  • 重定向可以访问其他服务器的资源
  • 重定向是两次请求

 

8.1.2、重定向和请求转发中的路径写法

只要不以/开头,都是相对路径。相对路径是从当前资源出发,去寻找其他资源。只要路径中以/开肉,就是全路径。全路径是从项目根目录出发,去寻找其他资源。在开发中,写文件路径时,最好使用全路径。

重定向中的 / 表示服务器根目录,而请求转发中的/表示WebContent目录即发布后的项目根目录。所以重定向中如果要访问本服务器资源,需要加上虚拟目录名。因为转发请求是在本服务器内的,所以并不需要加上虚拟目录。

比如:

//假设需要访问资源为:http://localhost:8080/javaweb_war/test.html

//重定向:
response.sendRedirect("/javaweb_war/test.html");  //正确写法应该是用request.getContextPath()方法来动态获取虚拟目录

//请求转发:
request.getRequestDispatcher("/test.html").forward(req, resp);

 

8.2、设置响应头

  • void setHeader(String name, String value):设置一个带有给定的名称和值的响应报头。

 

8.3、设置响应体

先获取输出流(字符输出流、字节输出流),然后使用输出流,将数据输出到客户端浏览器。

 

8.3.1、字符输出流(getWriter())

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取字符输出流
        PrintWriter pw = resp.getWriter();
        //输出数据
        pw.write("hello");
}

字符输出乱码问题:使用上面方法输出中文时,在浏览器接收到的数据可能会出现乱码。这是因为 tomcat 服务器默认使用 ISO8859-1 编码,所以我们应该设置服务器响应数据的编码:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置流的编码(这一步可省略)
        resp.setCharacterEncoding("UTF-8");

        //告诉浏览器,服务器发送的消息体数据的编码
        resp.setHeader("content-type", "text/html;charset=utf-8");  //或者可以直接使用 resp.setContentType("text/html;charset=utf-8"); 方法来设置

        resp.getWriter().write("你好啊 hello");
}   

 

8.3.2、字节输出流(getOutputStream())

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取字节输出流
        ServletOutputStream sos = resp.getOutputStream();
        //输出数据
        sos.write("helloA".getBytes());
}

解决中文乱码问题:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       resp.setContentType("text/html;charset=utf-8");
  
        //获取字节输出流
        ServletOutputStream sos = resp.getOutputStream();
        //输出数据
        sos.write("你好啊".getBytes("utf-8"));
}

 

posted @ 2020-03-25 15:39  wenxuehai  阅读(604)  评论(0编辑  收藏  举报
//右下角添加目录