2.9.1 Servlet章 Servlet API

29开篇有重演 tomcat和servlet的配置,和如何用插件

HttpServlet

概念

  • 我们写 Servlet 代码的时候, 首先第一步就是先创建类, 继承自 HttpServlet, 并重写其中的某些方法.
  • 我们编写Servlet 代码用到的核心的类,通过继承这个类,并重写其中的方法,让tomcat去调用到这个逻辑并且执行

核心方法

方法名称调用时机
init在 HttpServlet 实例化之后被调用一次,即webapp被加载的时候被执行
destory在 HttpServlet 实例不再使用的时候调用一次,即webapp在被销毁的时候(tomcat结束)时执行destory
service每次收到任何HTTP 请求的时候都调用此方法,不管你是post还是get还是其它
doGet收到 GET 请求的时候调用(由 service 方法调用)
doPost收到 POST 请求的时候调用(由 service 方法调用)
doPut/doDelete/doOptions/…收到其他请求的时候调用(由 service 方法调用)
  • 对于destory方法: tomcat有两种方法结束:
    • 1.通过8005端口,给tomcat发起特殊的请求,tomcat就关闭了(此时能够执行destory)
    • 2.直接杀死tomcat进程(例如win的任务管理器),此时无法执行destory,实际开发中,这种操作比较多,linux特别对,所以我们写代码不能太依赖destory
  • servlet的生命周期: 生命周期:“什么阶段做什么事”
  • 我们实际开发的时候主要重写 doXXX 方法, 很少会重写 init / destory / service

这些方法的调用时机, 就称为 “Servlet 生命周期”. (也就是描述了一个 Servlet 实例从生到死的过 程).

  • 注意: HttpServlet 的实例只是在程序启动时创建一次. 而不是每次收到 HTTP 请求都重新创建实例.

代码示例

简单代码样式

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("/method")
public class MethodServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("doGet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("doPost");
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("doPut");
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("doDelete");
    }
}
  • 通过http://127.0.0.1:8080/win_1/method访问,注意这里的win_1不是war包的helloServlet啥的,应为我们用idea的tomcat插件重命名了访问路径

  • 可以通古postman构造相对应的http请求

HttpServletRequest

概念

  • 当 Tomcat 通过 Socket API 读取 HTTP 请求(字符串), 并且按照 HTTP 协议的格式把字符串解析成 HttpServletRequest 对象. (即Tomcat收到HTTP请求就会被解析成HttpServletRequest对象)

核心方法

方法描述
String getProtocol()返回请求协议的名称和版本. 比如获取到http1.1
String getMethod()返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。
String getRequestURI()从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请 求的 URL 的一部分。URI != URL URI:唯一资源标识符,URL:唯一资源定位符,描述了网络上的一个资源,URL可以理解成URI的一种实现方式,这里的返回URI本质就是返回URL
String getContextPath()返回指示请求上下文的请求 URI 部分。
String getQueryString()返回包含在路径后的请求 URL 中的查询字符串。
Enumeration getParameterNames()后续开发常用, 返回一个 String 对象的枚举,包含在该请求中包含的参数的名称。通过一些方式,即通过此方法拿到query string 所有的key, 如果body是form表单的形式,则也可以用这个方法来获取from表单的key
String getParameter(String name)后续开发常用,以字符串形式返回请求参数的值,或者如果参数不存在则返回 null。即通过此方法拿到query string 的key对应的value
String[] getParameterValues(String name)返回一个字符串对象的数组,包含所有给定的请求参数的值,如 果参数不存在则返回 null。即一个key对应多个value,一般不怎么用
Enumeration getHeaderNames()后续开发常用,返回一个枚举,包含在该请求中包含的所有的头名。即获取请求头(header)里面的键
String getHeader(String name)后续开发常用,以字符串形式返回指定的请求头的值。即获取请求头(header)里面的键对应的值
String getCharacterEncoding()返回请求主体中使用的字符编码的名称。
String getContentType()返回请求主体 (body) 的 MIME 类型,如果不知道类型则返回 null
int getContentLength()以字节为单位返回请求主体的长度,并提供输入流,或者如果长 度未知则返回 -1。
InputStream getInputStream()用于读取请求的 body 内容. 返回一个 InputStream 对象.可以进一步读取这个流对象,就可以获取body的所有内容了
  • Enumeration getParameterNames() 和 String getParameter(String name) 非常常用,给服务器传递自定义数据,典型的方式是
    • query string querystring 本身是键值对结构的数据,tomcat就会把query string解析成Map这样的键值对,我们使用getParameterNames()拿到所有的key,再通过getParameter() 就可以根据key获取 value
    • body 如果是使用post form表单的形式提交请求的话,此时的body也是一个键值对格式(就和query string 一样的)
  • 通过这些方法可以获取到一个请求中的各个方面的信息

注意: 请求对象是服务器收到的内容, 不应该修改. 因此上面的方法也都只是 “读” 方法, 而不是 “写” 方法.

代码示例1

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;
import java.util.Enumeration;

@WebServlet("/request")
public class RequestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 上面这个操作也是必要的. 显式告诉浏览器, 你拿到的数据是 html.
        // 告诉浏览器你要拿html的方式显示这些内容,这样也就保证了下边可以用br换行,应为此时页面是html
        resp.setContentType("text/html");
        // 调用 req 的各个方法, 把得到的结果汇总到一个字符串中, 统一返回到页面上.
        StringBuilder respBody = new StringBuilder();

        //不能使用'\n' 应为\n是string的换行,而不是html的换行,br标签是html换行
        // 下列内容是在浏览器上按照 html 的方式来展示的. 此时 \n 在 html 中并不是换行.
        // 而使用 <br> 标签表示换行
        respBody.append(req.getProtocol());
        respBody.append("<br>");
        respBody.append(req.getMethod());
        respBody.append("<br>");
        respBody.append(req.getRequestURI());
        respBody.append("<br>");
        respBody.append(req.getContextPath());
        respBody.append("<br>");
        respBody.append(req.getQueryString());
        respBody.append("<br>");

        // 拼接 header
        Enumeration<String> headers = req.getHeaderNames();
        while (headers.hasMoreElements()) {//迭代器遍历风格代码
            String header = headers.nextElement();
            respBody.append(header + ": " + req.getHeader(header));
            respBody.append("<br>");
        }

        // 统一返回结果
        resp.getWriter().write(respBody.toString());
    }
}

  • http://127.0.0.1:8080/win_1/request 来访问
  • 可以来request后面加上键值对,这样它返回的null,比如http://127.0.0.1:8080/win_1/request/?a=10&b=20

代码示例2

获取querry string 默认客户端是使用query string传参数的

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("/parameter")
//继承了HttpServlet
public class ParameterServlet extends HttpServlet {
    private String password;

    // 约定, 客户端使用 query string 传递数据.
    // 例如: query string 形如: username=zhangsan&password=123
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");//这个方法可以通过key拿到values
        String password = req.getParameter("password");
        System.out.println("username=" + username);
        System.out.println("password=" + password);
        resp.getWriter().write("ok");//不然会返回空白页面,这么做可以检测是否返回空白页面
    }
}

  • 通过postman构造出 127.0.0.1:8080/win_1/parameter?username=yzy&password=233或者用http://127.0.0.1:8080/win_1/parameter?username=yzy&password=233 发送请求
  • http://127.0.0.1:8080/win_1/parameter?username=yzy&password=这样解析出来的password就是空的
  • http://127.0.0.1:8080/win_1/parameter?username=yzy这样解析出来的password也是空的

代码示例3

获取body,我们这里只考虑html里面from表单的格式,from表单的格式和query string的格式一样,也是= &分割的

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("/parameter2")
public class Parameter2Servlet extends HttpServlet {
    // 预期让客户端发送一个 POST 请求. 同时使用 form 格式的数据, 在 body 中把数据传递过来.
    // body 形如:
    // username=zhangsan&password=123
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        System.out.println("username=" + username);
        System.out.println("password=" + password);
        resp.getWriter().write("ok");
    }
}
  • 用postman在body处构造key和value
  • dopost才可以在body构造东西,doget的body是空
  • 注意我们在一个项目中,所有的servlet path需要保证不重复

代码示例3

获取body,但是考虑json格式,例子如下

{
    username:"yzy",
    password:"233"
}
  • 这种格式在开发中很常见,但是servlet自身不能对json格式进行解析,我们就需要引入第三方库来解析body数据了,我们使用的库叫做jackson(后续使用的spring 里面解析json就是用的 jackson )

用maven引入jackson

  • 去中央仓库找到jackson,拿到maven仓库地址
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.14.2</version>
</dependency>
  • 复制到你的pom设置文件中的dependences内即可(一个dependencies中可以包含多个依赖的)

如何使用jackson

常用方法1

  • JsonData 这个类用来表示解析之后生成的 JSON 对象. 这个类的属性的名字和类型要和 JSON 字符 串的 key 相对应.

常用方法2

  • ObjectMapper(Object对象 Map映射)
  • Jackson 库的核心类为 ObjectMapper. 其中的 readValue 方法把一个 JSON 字符串转成 Java 对象. 其中的 writeValueAsString 方法把一个 Java 对象转成 JSON 格式字符串.
  • 总结下来就是把一个对象映射到 JSON字符串,也可以把JSON映射到对象中

常用方法3

  • readValue 的第二个参数为 JsonData 的 类对象. 通过这个类对象, 在 readValue 的内部就可以借助 反射机制来构造出 JsonData 对象, 并且根据 JSON 中key 的名字, 把对应的 value 赋值给 JsonData 的对应字段.
  • 这个方法有很多版本readValue(InputStream src, Class<T> valueType),其作用就是把一个 json 字符串解析成一个 java 对象 ,其中:
    • 第一个参数为是一个流对象,也就表示json字符串从哪里来读,由于请求的body是通过getInputStream 得到的一个流对象,进一步的读取出来,我们即可以通过这个流对象传到第一个参数里面
    • 第二个参数则是指定一个类型,当前你这边得到的json字符串要转成一个啥样的java对象,需要指定一下对象类型,可以自己建一个class类,让这个类里面的属性的名字和类型都和json字符串匹配

常用方法4

  • String writeValueAsString(Object value)
  • 把java对象转成json格式
  • 在java中json的表示是用字符串表示的

java中编写代码

json转字符串
import com.fasterxml.jackson.databind.ObjectMapper;

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;

class User {
    public String username;
    public String password;
}

@WebServlet("/json")
public class JsonServlet extends HttpServlet {
    // 此处约定客户端body按照json格式来进行传输
    //具体格式形如:
    //    {
    //       username:"yzy",
    //       password:"233"
    //    }

    // 使用 jackson, 最核心的对象就是 ObjectMapper
    // 通过这个对象, 就可以把 json 字符串解析成 java 对象; 也可以把一个 java 对象转成(映射成)一个 json 格式字符串.
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 通过 post 请求的 body 传递过来一个 json 格式的字符串.
        //下面这一步完成了从字符串到对象之间的解析,即把字符串和user对象建立联系,把json里面的键值对设置到对应的user对象的属性里面
        //我们输入的是json字符串,得到的是一个user对象(但要求这两个内容是匹配的json里面有两个键值对username和password,则user对象中也要有username和password两个属性)
        User user = objectMapper.readValue(req.getInputStream(), User.class);
        System.out.println("username=" + user.username + ", password=" + user.password);

        resp.getWriter().write("ok");
    }
}
  • 用postman构造请求,body中选raw右侧选json构造为json的body

  • {
        username:"yzy",
        password:"233"
    }
    
  • **json引号问题:**json格式,key一定是字符串类型,所以原则上key不写""也是完全可以的,但是有些库/程序,检查更加严格,就必须强制我们把key加上引号,js的ajax的方式来构造json时,此时key就没有引号,但是如果用postman构造就要加引号

  • 理解jackson的readValue工作过程

    • 会先把json字符串解析成键值对,先放到Map中,再根据参数填入的类对象,通过反射api就可以知道这个类里面有哪些属性,每个属性的名字和类型,依次把这里的每个属性取出来,通过属性名字查询上诉的Map,把得到的值赋值给这个类的属性
    • 因此要知道json结构,才能构造出相对应的java对象,才能从获取json格式到编程java对象的格式
    • 此处的json格式,相当于前端和后端传输数据的媒介,还发这样的项目就需要前端和后端程序员规定好前后端数据格式(json格式是啥,包括路径,请求和key,value等)
字符串转json
import com.fasterxml.jackson.databind.ObjectMapper;

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;

class User {
    public String username;
    public String password;
}

@WebServlet("/json")
public class JsonServlet extends HttpServlet {
    // 此处约定客户端body按照json格式来进行传输
    //具体格式形如:
    //    {
    //       username:"yzy",
    //       password:"233"
    //    }

    // 使用 jackson, 最核心的对象就是 ObjectMapper
    // 通过这个对象, 就可以把 json 字符串解析成 java 对象; 也可以把一个 java 对象转成(映射成)一个 json 格式字符串.
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 通过 post 请求的 body 传递过来一个 json 格式的字符串.
        //下面这一步完成了从字符串到对象之间的解析,即把字符串和user对象建立联系,把json里面的键值对设置到对应的user对象的属性里面
        //我们输入的是json字符串,得到的是一个user对象(但要求这两个内容是匹配的json里面有两个键值对username和password,则user对象中也要有username和password两个属性)
        User user = objectMapper.readValue(req.getInputStream(), User.class);
        System.out.println("username=" + user.username + ", password=" + user.password);


        //jackson 还提供了把java对象转成json字符串
        String userString = objectMapper.writeValueAsString(user);
        System.out.println("userString: "+userString);

        resp.getWriter().write("ok");
    }
}

  • 注意: java中的json是用字符串来表示的,不代表json就是字符串,所以java对象转json格式,在java中本质就是对象转json

存在问题

Q:是否可以把对象转成json字符串的对象属性写成private呢?

  • 可以,但是也要写出向对应的get和set方法,不然获取不了方法,要么就直接public,就不用写get方法
  • jackson会通过反射的方式,把类里面包含的public属性全都给获取出来,此时就可以根据反射这里得到的"属性名字",去json解析出来的键值对进行匹配,如果key对应上了属性名字,就把value设置到刚才得到的属性中.由于我们把类属性改成了private,而jackson并不会针对private属性进行扫描,username就不认识了
  • 这样做是为了安全

HttpServletResponse

概念

  • HttpServletResponse 表示一个HTTP的响应
  • HttpServletResponse 同样也适合HTTP响应数据是匹配的,可以争对以下属性进行自定义设置
    • 状态码
    • 各种header
    • body
  • Servlet 中的 doXXX 方法的目的就是根据请求计算得到相应, 然后把响应的数据设置到 HttpServletResponse 对象中.
  • 然后 Tomcat 就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式, 转成一个字符串, 并通过 Socket 写回给浏览器.

请求和响应的对比

  • 请求对象,我们拿到之后的目的,是为了获取里面的属性**(读)**
  • 响应对象,我们拿到之后的目的是为了设置里面的属性**(写)**
  • 请求对象,时Tomcat收到请求之后,对http协议解析得到对象
  • 响应对象,时tomcat 创建的空对象,我们在代码中我们把响应对象的属性给设置好( 响应对象,就相当于一个输出型参数)

核心方法

方法描述
void setStatus(int sc)为该响应设置状态码,比如200,404等
void setHeader(String name, String value)设置响应报头,设置一个带有给定的名称和值的 header. 如果 name 已经存在, 则新的覆盖旧的值
void addHeader(String name, String value)设置响应头,添加一个带有给定的名称和值的 header. 如果 name 已经存在, 不覆盖旧的值, 并列添加新的键值对**(相当于两个key相同的键值对,使用addheader,header中就可能出现key相同的两个键值对)**,一般来说键值对中key是唯一的,但实践中,确实也有需要key不唯一的时候,这种情况,相当于一个key对应多个value,请求也有相同的header,不过是由浏览器或者http客户端控制的
void setContentType(String type)设置被发送到客户端的响应的内容类型。
void setCharacterEncoding(String charset)设置被发送到客户端的响应的字符编码(MIME 字符集)例如, UTF-8。这里告诉浏览器要按照什么样的字符集响应body,如果不描述清楚,可能浏览器展示的内容就会乱码
void sendRedirect(String location)使用指定的重定向位置 URL 发送临时重定向响应到客户端
PrintWriter getWriter()用于往 body 中写入文本格式数据
OutputStream getOutputStream()用于往 body 中写入二进制格式数据

注意: 响应对象是服务器要返回给浏览器的内容, 这里的重要信息都是程序猿设置的. 因此上面的方 法都是 “写” 方法.

注意: 对于状态码/响应头的设置要放到 getWriter / getOutputStream 之前. 否则可能设置失效.

复习一下HTTP中的键值对

  1. query string
  2. header(我们上面讨论的重复不重复说的是header)
  3. header里面的cookie,也是键值对
  4. body(form表单)
  5. body(json)
  6. Servlet提供的一些其他的api比如session这里的管理
    1. sessionId 和Session 对象,又是键值对
    2. Session对象里头,还可以包含键值对
  7. 前端form表单,name属性是key,输入框输入的内容是value
  8. Spring里 bean的名字和bean对象也是键值对
  9. Spring里面的配置文件还是键值对

代码示例1

状态码设置

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("/status")
public class StatusServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setStatus(404);
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write("返回 404 响应!");
    }
}
  • 用postman来构造
  • 我们可以在返回状态码的同时卖给body也写入数据,就可以得到一些个性化的错误页面
  • tomcat有个内置的错误页面使用在代码中使用resp.sendError(404)即可

代码示例2

使用setHeader设置任意响应报头

这里通过 refresh 属性,设置浏览器自动刷新, refresh设置成2, 浏览器没给隔两秒刷新一次

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("/refresh")
public class RefreshServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 每隔 1s 自动刷新一次.
        resp.setHeader("Refresh", "1");
        resp.getWriter().write("time=" + System.currentTimeMillis());//返回当前时间戳
    }
}
Linux安装Apipost的过程相较于Windows可能略有不同,因为它通常依赖于包管理器或者命令行操作。以下是几种在Linux上安装Apipost的常见方法: 1. 使用snap安装(适用于支持snap的Linux发行版): ```bash sudo snap install apipost ``` 2. 如果你的Linux发行版支持flatpak,也可以尝试使用flatpak进行安装: ```bash flatpak install apipost ``` 3. 通过下载压缩包安装: - 访问Apipost的官方网站或者其他可信赖的下载源下载适用于Linux的压缩包。 - 解压下载的压缩包到指定目录。 - 根据需要将其移动到系统的应用程序目录中。 - 通过终端运行Apipost可执行文件,或者为其创建快捷方式。 4. 使用命令行安装: - 对于基于Debian的系统(如Ubuntu),可以下载`.deb`包并使用`dpkg`安装: ```bash sudo dpkg -i apipost-x.x.x.deb ``` - 对于基于RPM的系统(如Fedora),下载`.rpm`包并使用`rpm`或`dnf`安装: ```bash sudo rpm -ivh apipost-x.x.x.rpm ``` - 或者使用`yum`安装: ```bash sudo yum install apipost-x.x.x.rpm ``` 5. 通过Node.js安装(如果你的系统安装了Node.js): - 使用npm安装Apipost的命令行工具: ```bash sudo npm install -g apipost ``` - 注意,这可能需要Node.js和npm已安装在你的系统上。 请注意,版本号`x.x.x`应该替换为实际的Apipost版本号。如果你不确定你的系统类型或包管理器,请先进行一些基础的Linux系统知识学习,或者使用系统自带的软件包管理工具搜索Apipost。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Ap0stoL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值