目录
HttpServletRequest和HttpServletResponse的区别
Servlet的概念:服务器上能够响应客户端请求的对象
Servlet是一组 Tomcat服务器提供给程序员开发网站的API,Servlet是一种实现动态页面的技术,也就是页面会根据时间、用户、输入参数不同发生相应的改变。Servlet技术的核心就是在服务器端创建能够响应用户请求的对象,被创建的对象习惯上被称为一个Servelt。
Servlet主要的工作
1.初始化:这个过程允许程序员注册一个类到Tomcat中,让这个类和HTTP中的一个特定的请求相关联(特定的请求比如:doGet、doPost、doPut.......) ,这个过程类似于JS中给按钮关联一个点击事件。
2.进入循环(循环的处理多个请求),这一步又有很多过程:
a).读取http请求,Servlet解析这个请求字符串,然后生成一个HttpServletRequest对象;
b).根据请求对象生成一个HttpServletResponse对象来表示响应,根据请求生成响应,这个过程就是初始化阶段的类里面的代码完成的;
c).把HttpServletResponse对象转换成HTTP响应,返回给浏览器;
上面的1、a、c这几个步骤是由Tomcat/Servlet完成的;
b过程是由程序员完成的,也就是说程序员只需要根据请求来写响应里面的业务逻辑即可,比如用户在浏览器中输入花,那么我们这边的服务器程序就应该将有关花的信息返回给浏览器
Servlet API
整个Servlet中最核心的类HttpServlet、HttpServletRequest、HttpServletResponse
一个Servlet程序的结构是下面这样:
@WebServlet("/路径")//功能是为了把这个类和一个具体的HTTP请求的路径关联起来
public class 类名 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest rep,HttpServletResponse resp) throws ServletException, IOException{
//要执行的任务
}
}
这个结构中一定不要忘了也不要写错类上面的注解@WebServlet("/路径"),它的功能是把写的这个类和一个具体的HTTP请求的路径关联起来。
servlet的运行原理
1.用户在浏览器中输入URL,然后浏览器构造一个HTTP请求。
2.这个HTTP请求经过网络协议栈逐层进行封装,然后生成二进制的数据流经过物理层的硬件设备装换成光电信号传输出去。
3.经过在网络上的传播最终到达服务器主机,服务器将二进制数据流层层解析,还原成HTTP请求,然后交给Tomcat中的进程处理。
4.Tomcat通过Socket读取到这个请求,并按照HTTP请求的格式来解析这个请求,并根据请求中的Context Path确定一个webapp,再通过Servlet Path确定一个具体的类,再根据当前请求的方法(GET/POST/...)来决定调用这个类的doGet或者doPost等方法,然后HttpServletRequest中就包含了这个HTTP请求的详细信息。
5.在doGet或者doPos中,写一些我们自己的业务逻辑,然后将这些信息放入到HttpServletResponse中。
6.doGet或者doPos执行完毕后,Tomcat就会自动把HttpServletResponse设置成一个HTTP格式的字符串,通过Socket发送出去。
7.再通过网络传输到达客户端浏览器,浏览器再经过层层解析将响应HTTP中body部分的内容加载到浏览器界面上。
Tomcat服务器里面Servlet引擎对请求进行解析,根据Servlet Path找到要访问的Servelt,然后实现了Servelt类(比如做登录功能的类)处理清理里面的业务,将执行的结果通过HttpServletResponse返回给Tomcat。
Servelt引擎访问Servelt的过程
如果是第一次请求,Servlet引擎会找到相应的Servlet类/对象,然后Servelt类/对象就会创建Servlet实例并调用init()方法进行初始化设置,然后调用service方法处理浏览器请求,最终将业务结果返回给Servlet引擎,最终返回给Tomcat服务器。
如果是第二次请求,Servlet引擎会找到相应的Servlet类/对象,然后直接调用service()方法处理业务,最终将结果返回给Tomcat。这中间就没有进行Servlet实例化和初始化。
Servelt引擎判断是否是第一次请求主要是靠Servlet里面的service方法:
Servlet请求在调用Servlet里面的service方法之前,会先创建request对象和空的response对象,然后service方法读取request请求,将处理的结果放在响应response里面。下一次请求的时候,service方法再次读取请求的时候,请求里面多少就会带上上一次响应的东西,根据这个信息的有无判断是否是第一次请求。
HttpServlet
我们写Servlet代码的时候,首先第一步就是创建类,让这个类去继承HttpServlet,并重写HttpServlet里面的相关方法。
重写的目的主要是为了将业务逻辑插入到Tomcat框架中,让Tomcat能够进行调用。
HttpServlet中主要的方法
doGet 功能:处理客户的GET请求(当客户使用GET方式请求Servlet时,Web容器调用doGet方法处理请求) 调用时机:收到GET请求时
doPost 功能:处理客户的POST请求 调用时机:收到POST请求时
doPut/doDelete/...
HttpServlet的调用时机(生命周期)
调用时机就是方法被调用的时间,它也被称为Servlet的生命周期
Servlet的生命周期:Servlet在实例化时调用一次,然后Servlet每次收到请求时会被调用一次,Servlet在销毁时会被调用一次。
Servlet的使用过程
1.创建一个maven项目。
2.引入依赖。
3.创建目录结构 webapp/WEB-INF/web.xml。
4.编写代码。
1).创建一个类,继承自HttpServlet。
2).给类上面加上一个注解@WebServlet("/..."),把这个类和一个具体的HTTP请求的路径关联起来。
3).重写HttpServlet中的方法(doGet/doPost)。
4). 在方法里面根据计算响应。
5.打包,使用maven,其中pom.xml要修改打包的类型为war包,然后可以指定一下包的名字。
6.部署,将war包拷贝到Tomcat的webapps目录中,Tomcat就会自动对这个war包进行解压缩,得到一个同名目录。
7.输入URL进行验证。
下面演示一下Servlet如果使用(如果你的IDEA是社区版的话就不能创建maven文件了,所以去网上买一个专业版吧):
1.创建一个maven项目
2.引入依赖,这个需要一个Servlet的API,然后可以去中央仓库去下载(https://mvnrepository.com/):
在里面搜索servlet,然后下载第一个
3.创建目录结构 webapp/WEB-INF/web.xml。
向web.xml中添加这样一段代码,这段代码主要是web-app开发所需要的相当于工具类的东西
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
4.编写代码
5.打包,使用maven,其中pom.xml要修改打包的类型为war包,然后可以指定一下包的名字
按着步骤来打包,如果你的第5步没有成功,可能是你的pom.xml文件的依赖没有引入成功。关于maven工具的使用,可以看我前面的博客。
上面虽然包已经打出来了,但这个包是一个jar包,并不是我们想要的,我们想要的是一个war包。
jar包和war包的区别
jar包是普通的java程序打包的结果,里面包含一些.class文件。war包是java web的程序,里面除了会包含.class文件之外,还会包含HTML,CSS,JavaScript,图片,以及其他的jar包,打成war包格式才能被Tomcat识别。
为了打war包,需要在pom.xml中新增一个packing标签,表示打包的方式是一个war包
<packaging>war</packaging>
然后为了修改war包的名字,则需要在pom.xml中新增一个build标签,内置一个finalName标签,表示war包的名字是HelloServlet
<build>
<finalName>HelloServlet</finalName>
</build>
6.部署,将war包拷贝到Tomcat的webapps目录中,Tomcat就会自动对这个war包进行解压缩,得到一个同名目录
7.输入URL进行验证
在进行验证前不要忘了运行Tomcat
上面这种使用Servlet的方法非常不方便,如果我们更改方法里面的内容,就要重新打包,然后将war包拷贝到webapps目录下面,所有的前提还要让Tomcat处于运行状态。因此在实际开发中一般不会用这种方法,下面介绍另一种方法,不用打包和手动启动Tomcat。
在IDEA的设置中,插件目录下面下载一个Smart Tomcat,安装以后重启IDEA。然后点击Add Configurations
然后点击运行
然后在浏览器中输入URL,注意这回的URL的路径:
演示Servlet处理GET和POST请求
在webapp目录下面准备一个testMethod.html文件在里面写相关的事件。然后在创建一个新的类MethodServlet,并在里面重写doGet和doPost方法
下面是testMethod.html文件:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="http://code.jquery.com/jquery-2.2.1.min.js"></script>
<button onclick="sendGet()">发送Get请求</button>
<button onclick="sendPost()">发送Post请求</button>
<script>
function sendGet(){
$.ajax({
type:'GET',
url:'method',
success:function (data,status){
console.log(data);
console.log(status);
}
});
}
function sendPost() {
$.ajax({
type: 'POST',
url: 'method',
body:'request body',
success:function (deta,status){
console.log(deta);
console.log(status);
}
});
}
</script>
</body>
</html>
上面url中的路径写的是相对路径,它的基准目录是http://127.0.0.1:8080/Test/,若要写成绝对路径的话可以写成这样——http://127.0.0.1:8080/Test/testMethod.html
下面是testMethod.java文件:
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 testMethod extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html,charset=utf-8");
resp.getWriter().write("hello");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html,charset=utf-8");
resp.getWriter().write("goodbey");
}
}
代码里面指定编码格式一定要在
运行结果:
HttpServletRequest
当Tomcat通过Socket API读取HTTP请求(字符串),并且按照HTTP协议的格式把字符串解析成HttpServletRequest,HtttServletRequest这个类就表示HTTP请求,理解这个类就是在理解HTTP协议的格式。
HttpServletRequest的方法
HttpServletRequest的方法很多,这里就不依依列举,只讨论几个重要的方法:
http:127.0.0.1:8080/Test/method?a=10&b=20&c=30
Host: 127.0.0.1:8080
user-Agent: xxxx
Referer: http://127.0.0.1:8080/login
Enumeration getParameterNames() 功能:得到所有的a、b、c...
String getParameter(String name) 功能:根据a、b、c得到它们对应的值
String[] getParameterValues(String name) 功能:将a、b、c对应的值放入一个String类型的数组,数组里面允许有相同的值
Enumeration getHeaderNames() 功能:得到header中的所有键值对
String getHeader(String name) 功能:根据header里面的键和到它们对应的值
InputStream getInputStream() 功能:获取InputStream对象里面的body
通过几个案例来演示它们的使用:
创建ShowRequest类
@WebServlet("/show")
public class ShowRequest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//给响应的HTTP协议设置字符编码
resp.setContentType("text/html,charset=utf-8");
//创建一个容器来存放HTTP中的内容
StringBuilder respBody = new StringBuilder();
//得到请求协议的名称和版本号
respBody.append(req.getProtocol()+"<br>");
//得到请求协议中的方法名
respBody.append(req.getMethod()+"<br>");
//得到请求协议中的url
respBody.append(req.getRequestURL()+"<br>");
//得到请求协议中url中从端口号后面直到后面
respBody.append(req.getRequestURI()+"<br>");
//得到请求协议的ContextPath————文件路径
respBody.append(req.getContextPath()+"<br>");
//得到请求协议的查询字符串
respBody.append(req.getQueryString()+"<br>");
//得到请求协议的正文部分
respBody.append("<h3>header:</h3>");
Enumeration<String> headerNames = req.getHeaderNames();
while(headerNames.hasMoreElements()){
String headerName = headerNames.nextElement();
respBody.append(headerName+": ");
respBody.append(req.getHeader(headerName)+"<br>");
}
//将字符串结果按照HTTP协议格式写到reponse中
resp.getWriter().write(respBody.toString());
}
}
此处补充一个知识点:
GET请求中参数一般都是通过query string传递给服务器的,而POST请求中参数一般通过body传递给服务器,它们都可以通过getParameter来获取参数的值。
这里写一个通过ajax来构造一个body为json格式的数据:
创建PostParameterjson类和testPostjson文件
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<button onclick="sendJson()">发送Json格式的Post</button>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script>
function sendJson(){
let body = {
classId:1,
userId:100
};
$.ajax({
url: 'post',
method: 'POST',
contentType:'application/json;charset=utf-8',
data: JSON.stringify(body),
success: function (body, status) {
console.log(body);
}
});
}
</script>
</body>
</html>
@WebServlet("/json")
public class PostParameterJson extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String body = readBody(req);
resp.getWriter().write(body);
}
private String readBody(HttpServletRequest req) throws IOException {
int contentLength = req.getContentLength();
byte[] buffer = new byte[contentLength];
InputStream inputStream = req.getInputStream();
inputStream.read(buffer);
return new String(buffer,"utf-8");
}
}
但是上面只是在控制台中输出了,若想要获取到userId和classId的具体的值,还需要搭配JSON库进一步解析。
换一种方法来获取POST请求中的参数:
引入Jackson这个库,进行JSON解析,去中央仓库搜索Jackson,选择JackSon Databind,然后将依赖添加到pom.xml中,像下面这样:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
总的pom.xml代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>Servlet</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
<!--provided标签表示,当前jar包里只是在开发阶段使用,而不需要打包到最终的发布包中
Tomcat里面内置了Servlet,把程序部署到Tomcat上的时候,已经就有Servlet了
就不需要把你自己的代码里面把Servelet也打包一份放到Tomcat上-->
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
</dependencies>
<packaging>war</packaging>
<build>
<finalName>ServletHelloWorld</finalName>
</build>
</project>
PostParameterjson类:
class JsonData {
public String userId;
public String classId;
}
@WebServlet("/json")
public class PostParameterJson extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String body = readBody(req);
//创建ObjectMapper对象,这个是Jackson中的核心类,
ObjectMapper objectMapper = new ObjectMapper();
//通过readvalue方法把body这个字符串转成JsonData对象
JsonData jsonData = objectMapper.readValue(body,JsonData.class);
resp.getWriter().write("userId: "+jsonData.userId+","+"classId: "+jsonData.classId);
}
private String readBody(HttpServletRequest req) throws IOException {
int contentLength = req.getContentLength();
byte[] buffer = new byte[contentLength];
InputStream inputStream = req.getInputStream();
inputStream.read(buffer);
return new String(buffer,"utf-8");
}
}
上面代码中最重要的两段就是:
ObjectMapper objectMapper = new ObjectMapper();
//通过readvalue方法把body这个字符串转成JsonData对象
JsonData jsonData = objectMapper.readValue(body,JsonData.class);
上面这两段代码完成了Json格式的字符串到java对象的解析过程,body就是Json格式的字符串;JsonData.class是一个类对象,他主要完成以下工作:
1.先把Json格式的字符串转换成类似于HashMap的键值对结构:
"userId" => 100、"classId" => 1
2.根据类对象获取到要转换结果的类都有那些属性,每个属性的名字是什么,这个例子中就通过反射机制获取到JsonData这个类对象里面有两个属性,名字分别是userId和classId。关于反射可以去看我的另一篇博客 反射机制和类加载机制_咸鱼吐泡泡的博客-CSDN博客_反射和类加载
3.拿着JsonData这里的每个属性的名字,去第一步构造出来的哈希表里面拿去对应的值,如果查找到了,就把查询的值赋值到JsonData对应的属性里面。这里要求创建JsonData类的时候,就需要这里的成员的名字得和Json字符串里的key是匹配的
最终结果:
postman的使用
在上面几个案例中,构造HTTP请求都是通过自己写一个页面html,使用form或者ajax来构造请求。这种构造请求的方式比较麻烦,如果服务器这边每次写了一个新的API,提供了一个新的接口,我们不可能每次都写一个html来测试。更简单的方法是我们可以使用专门的第三方工具来构造请求,postman就是其中一个。
去浏览器搜索postman,然后下载安装并注册登录:
创建请求:
post还可以根据body生成对应的代码:
利用postman可以创建各种格式的请求,可以很方便的帮助我们测试接口。
HttpServletResponse
Servlet中的doXXX方法的目的就是根据请求计算得到响应,然后把响应的数据设置到HttpServletResponse对象中,然后Tomcat就会把这个HttpServletResponse对象按照HTTP协议的格式,转成一个字符串,并通过Socket写回给浏览器。
HttpServletRequest和HttpServletResponse的区别
HttpServletRequest里面的内容,是客户端构造的,服务器需要做的是获取到这里面的内容,因此它的方法否是get开头的;
HttpServletRsponse里面的内容,是服务器构造的,要返回给客户端,所以它的方法都是以set开头。
HttpServletResponse的方法
它的方法有很多,这里只讲最常用的:
void setContentType(String type) 功能:设置发送到客户端响应内容的类型
void setCharacterEncoding(String charset) 功能:设置发送到客户端响应内容里面的字符编码,例如:utf-8
void sendRedirect(String location) 功能:使用指定的URL发送临时响应到客户端
PrintWriter getWriter() 功能:用于往body中写入文本格式数据
OutputStream getOutputStream() 功能:用于往body中写入二进制数据格式
演示几个案列:
设置状态码:
@WebServlet("/status")
public class StatusServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String status = req.getParameter("status");
if(status!=null){
resp.setStatus(Integer.parseInt(status));
}
resp.getWriter().write("status: "+status);
}
}
当什么都不输入是,状态码为空
当输入状态码为200时,可以看到响应里面的状态码也是200
Servlet中的上传文件
Servlet也支持上传文件的功能。
在日常开发中通常是利用前后端交互的方式来上传文件,前端代码中有一个input标签里面的type类型,当类型为file是表示一个文件类型,每个文件就是一个Part :
<input type="file" name="MyImage">
<input type="submit" value="提交图片">
后端则是通过Part类里面的方法,利用每个文件都有自己的name,服务器就可以根据这个name找到对应的Part,然后进行上传操作。
在写前端代码的时候要注意:上传文件一般都是通过POST请求的表单来实现的,而且在form中要加上nectype="multipart/form-data"字段。
上传文件的核心方法
HttpServletRequest类里面的上传文件的方法:
Part getPart(String name) 功能:获取请求中给定的name文件
collection<Part> getParts() 功能:获取所有的文件
Part类里面的方法:
String getSubmittedFileName() 功能:获取提交的文件名
String getContentType() 功能:获取提交文件的类型
long getSize() 功能:获取文件的大小
void write(String path) 功能:把提交的文件写入磁盘文件
下面演示如何通过网页提交一个图片到服务器上:
准备一张图片到webapp目录下面:
在webapp目录下面创建一个Uplode.html文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="upload" enctype="multipart/form-data" method="post">
<input type="file" name="2">
<input type="submit" value="提交图片">
</form>
</body>
</html>
然后在JAVA目录下面创建一个UploadServlet文件:
@MultipartConfig
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取到请求中指定的图片
Part part = req.getPart("2");
//获取文件的属性
System.out.println(part.getSubmittedFileName());
System.out.println(part.getContentType());
System.out.println(part.getSize());
//将文件保存到本地
part.write("e:/2.png");
//向服务器上传一张图片
resp.getWriter().write("upload ok");
}
}
启动服务器,在浏览器中输入URL:
上面的一定要给UploadServlet类上面加上一个@MultipartConfig注解,否者服务器代码无法使用getPart方法;
getPart的参数需要和form中input标签的name属性对应;
总结Servlet
Servelt是Web服务器(比如Tomcat)里面的一个个解决用户请求的对象,在Servelt和Web服务器之间还有一层Servlet引擎,通过它来管理Servlet。Servlet里面的service方法是沟通Servlet引擎的桥梁,通过request对象和response对象来处理完成业务逻辑。Servlet的生命周期实例化——初始化——service——destroy。