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中的键值对
- query string
- header(我们上面讨论的重复不重复说的是header)
- header里面的cookie,也是键值对
- body(form表单)
- body(json)
- Servlet提供的一些其他的api比如session这里的管理
- sessionId 和Session 对象,又是键值对
- Session对象里头,还可以包含键值对
- 前端form表单,name属性是key,输入框输入的内容是value
- Spring里 bean的名字和bean对象也是键值对
- 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());//返回当前时间戳
}
}