servlet API

目录

1.Tomcat伪代码

2.Servlet API

           2.1HTTPServlet

           2.2HTTPServletRequest

           2.3HTTPServletResponse


1.Tomcat伪代码

Tomcat伪代码包括了两部分的核心逻辑,一是Tomcat的初始化,二是处理请求。

①Tomcat的初始化:

(1)Tomcat先从指定目录中找到所有要加载的servlet类。

前面我们部署的时候,就是把servlet代码编译成了.class,然后打了war包,然后拷贝到了webapps目录下,而Tomcat就会从这个目录下来找到哪些.class对应的是servlet类,并且需要进行加载。

(2)根据刚才类加载的结果,给这些类创建servlet实例

// 用来存储所有的 Servlet 对象
private List<Servlet> instanceList = new ArrayList<>();
 public void start() {
       // 根据约定,读取 WEB-INF/web.xml 配置文件;
        // 并解析被 @WebServlet 注解修饰的类
       
        // 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类. 
        Class<Servlet>[] allServletClasses = ...;
        
        // 这里要做的的是实例化出所有的 Servlet 对象出来;
        for (Class<Servlet> cls : allServletClasses) {
            // 这里是利用 java 中的反射特性做的
           // 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
            // 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是
            // 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。  
            Servlet ins = cls.newInstance();
            instanceList.add(ins);
       }

(3)实例创建好之后,就可以调用当前servlet实例的init方法了。

  // 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;
        for (Servlet ins : instanceList) {
            ins.init();
       }

init()方法是servlet自带的方法,默认情况下什么也不干,我们在继承一个HTTPServlet的时候,也可以选择自己重写init,从而在这个阶段写一些能够帮助我们完成一些初始化工作的内容。

(4)创建TCP socket,监听8080端口,等待客户端来连接。

// 利用我们之前学过的知识,启动一个 HTTP 服务器
        // 并用线程池的方式分别处理每一个 Request
        ServerSocket serverSocket = new ServerSocket(8080);
        // 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
        ExecuteService pool = Executors.newFixedThreadPool(100);
        
        while (true) {
            Socket socket = ServerSocket.accept();
            // 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
            pool.execute(new Runnable() {
               doHttpRequest(socket); 
           });
       }

每次当客户端有连接的时候,accpet就会返回,然后给当前线程池里加上一个任务,然后doHttpRequest(socket)就在这个任务里负责这个请求。Tomcat工作的大部分时间都是在这个循环里完成的。

(5)如果循环退出来,Tomcat也要结束了的时候,就会依次调用每个servlet的destroy的方法。

  // 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
        for (Servlet ins : instanceList) {
            ins.destroy();
       }

这是属于Tomcat的收尾工作,和init一样,默认也是什么也不干,但是也是可以被我们重写的。除此之外,虽然有destroy这个流程,但是这个环节并不一定可靠,只有当Tomcat是正常流程结束时,才可以这么调用,而大部分情况都是采用直接结束进程这种非正常流程来退出的,这个时候就来不及来调用它。

②Tomcat处理请求

(1)Tomcat 从 Socket 中读到的 HTTP 请求是一个字符串, 然后会按照 HTTP 协议的格式解析成一个HttpServletRequest 对象.(req,而此处resp相当于new了一个空对象)

(2)Tomcat 会根据 URL 中的 path 判定这个请求是请求一个静态资源还是动态资源. 如果是静态资源, 直接找到对应的文件把文件的内容构造到resp对象的body中,以 Socket(即返回这个resp对象)返回如果是动态资源, 才会执行到 Servlet 的相关逻辑。

(3)Tomcat 会根据 URL 中的 Context Path(确定是哪一个webapp)  Servlet Path(确定是哪个servlet类) 确定要调用哪个 Servlet 实例的 service。如果没有找到匹配的,就会返回404。

(4)找到刚刚servlet的对象来调用service 方法,在service内部就会进一步调用到我们之前写的 doGet 或者 doPost

方法 .

代码如下:

class Tomcat {
    void doHttpRequest(Socket socket) {
        // 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建
        HttpServletRequest req = HttpServletRequest.parse(socket);
        HttpServletRequest resp = HttpServletRequest.build(socket);
        
        // 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态
内容
        // 直接使用我们学习过的 IO 进行内容输出
        if (file.exists()) {
            // 返回静态内容
            return;
       }
        
        // 走到这里的逻辑都是动态内容了
        
        // 根据我们在配置中说的,按照 URL -> servlet-name -> Servlet 对象的链条
        // 最终找到要处理本次请求的 Servlet 对象
        Servlet ins = findInstance(req.getURL());
        
        // 调用 Servlet 对象的 service 方法
        // 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
        try {
       ins.service(req, resp); 
       } catch (Exception e) {
            // 返回 500 页面,表示服务器内部错误
       }
   }
}

其中第(4)步代码:

class Servlet {
    public void service(HttpServletRequest req, HttpServletResponse resp) {
        String method = req.getMethod();
        if (method.equals("GET")) {
            doGet(req, resp);
       } else if (method.equals("POST")) {
            doPost(req, resp);
       } else if (method.equals("PUT")) {
            doPut(req, resp);
       } else if (method.equals("DELETE")) {
            doDelete(req, resp);
       } 
       ......
   }
}

在整套流程中涉及到的servlet方法主要有三个:(也被称为一个servlet的生命周期)

(1)初始化阶段,对象创建好了之后,就会执行到.用户可以重写这个方法,来执行一些初始化逻辑。

(2)在处理请求阶段来调用.每次来个请求都要调用一次service

(3)退出主循环, tomcat结束之前会调用,用来释放资源.

2.Servlet API

           2.1HTTPServlet

①常见API

在实际中我们主要重写do...的方法,很少重写init,destory,service这些方法。

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

②处理GET请求:

(1)直接在浏览器中,通过URL就能构造(GET请求最常用用法)

(2)通过ajax构造GET请求:

创建 MethodServlet.java, 创建 doGet 方法
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 {
        //super.doGet(req, resp);
        resp.setContentType("text/html; charset=utf-8");
        resp.getWriter().write("GET response获取响应");
    }
}
创建  TestMethod.html, 放到 webapp 目录中,与WEB-INF处于同级关系
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
        $.ajax({
            type: 'get',
            url: 'method',
            success: function (body) {
                console.log(body);
            }
        });
    </script>
</body>
</html>
启动smart Tomcat一键打包部署后结果如下:

 注意!!!加上resp.setContentType("text/html; charset=utf-8");

不然的话遇到中文字符就会出现如图所示乱码情况

 ③处理POST请求:

我们这里是直接通过ajax来构造请求的

(1)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 MethodServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doGet(req, resp);
        resp.setContentType("text/html; charset=utf-8");
        resp.getWriter().write("post response响应");
    }
}

(2)html代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
        $.ajax({
            type: 'post',
            url: 'method',
            success: function (body) {
                console.log(body);
            }
        });
    </script>
</body>
</html>

注意!!!

a.创建 TestMethod.html, 放到 webapp 目录中,与WEB-INF处于同级关系。

 b.还需要注意这里的服务资源路径

 c.打包部署后:

post是不能通过地址栏访问,现在是js里边写代码,通过ajax来访问的。所以我们可以通过ajax页面来进行访问。

d.我们还需要是否加/的问题。

           2.2HTTPServletRequest

Tomcat 通过 Socket API 读取 HTTP 请求 ( 字符串 ), 并且按照 HTTP 协议的格式把字符串解析成 HttpServletRequest 对象。HttpServletRequest对应到一个HTTP请求,HTTP请求中有啥,这里就有啥。
①一些常见API:
方法描述
String getProtocol()返回请求协议的名称和版本
String getMethod()返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT
String getRequestURI()从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分
String getContextPath()返回指示请求上下文的请求 URI 部分。
String getQueryString()返回包含在路径后的请求 URL 中的查询字符串,得到完整的查询字符串
Enumeration getParameterNames()返回一个 String 对象的枚举,包含在该请求中包含的参数的名称,得到所有的key,以 Enum的方式来表示.
String getParameter(String name)以字符串形式返回请求参数的值,或者如果参数不存在则返回null ,根据key来拿到value
String[] getParameterValues(String name)返回一个字符串对象的数组,包含所有给定的请求参数的值,如果参数不存在则返回 null
Enumeration getHeaderNames()返回一个枚举,包含在该请求中包含的所有的头名
String getHeader(String name)以字符串形式返回指定的请求头的值
String getCharacterEncoding()返回请求主体中使用的字符编码的名称
String getContentType()返回请求主体的 MIME 类型,如果不知道类型则返回 null
int getContentLength()以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知则返回 -1
InputStream getInputStream()用于读取请求的 body 内容. 返回一个 InputStream 对象

注意:

通过这些方法可以获取到一个请求中的各个方面的信息.  请求对象是服务器收到的内容, 不应该修改。 因此上面的方法也都只是 "读" 方法, 而不是 "写" 方法。
②对其中几个易混进行讲解:
(1)

 (2)

(3)

(4)

 ③我们用代码来演示一些部分方法:

代码如下:

public class showGetAPI extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("<h3>首行部分 </h3>");
        //返回请求协议的名称和版本,并换行
        stringBuilder.append(req.getProtocol());
        stringBuilder.append("<br>");
        //返回请求的 HTTP 方法的名称
        stringBuilder.append(req.getMethod());
        stringBuilder.append("<br>");
        //从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分
        stringBuilder.append(req.getRequestURI());
        stringBuilder.append("<br>");
        //返回指示请求上下文的请求 URI 部分。
        stringBuilder.append(req.getContextPath());
        stringBuilder.append("<br>");
        //返回包含在路径后的请求 URL 中的查询字符串,得到完整的查询字符串
        stringBuilder.append(req.getQueryString());
        stringBuilder.append("<br>");

        stringBuilder.append("<h3>header 部分 </h3>");
        //返回一个枚举,包含在该请求中包含的所有的头名
        Enumeration<String> headerNames =  req.getHeaderNames();
        while (headerNames.hasMoreElements()){
            String headerName = headerNames.nextElement();
            String headerValue = req.getHeader(headerName);
            stringBuilder.append(headerName + ": " + headerValue + "<br>");
        }

        resp.setContentType("text/html; charset=utf-8");
        resp.getWriter().write(stringBuilder.toString());//将整个字符串拼接内容进行打印

    }
}

结果如下:

 ④最常用的其实是getParameter这个方法,用来获取querystring中的详细内容

(GET 请求中的参数一般都是通过 query string 传递给服务器的. 形如

https://v.bitedu.vip/personInf/student?userId=1111&classId=100
此时浏览器通过 query string 给服务器传递了两个参数, userId 和 classId, 值分别是 1111 和 100在服务器端就可以通过 getParameter 来获取到参数的值. )
我们用代码来进行演示:
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("/GetParameter")
public class GetParameter extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        //为这个GET设置两个String类型的参数
        String userName=req.getParameter("userName");
        String password=req.getParameter("password");
        resp.getWriter().write("userName="+userName+","+"passwprd="+password);
    }
}

 当没有 query string的时候, getParameter 获取的值为 null.

这个时候如果我们这样访问127.0.0.1:8080/untitled20/GetParameter?userName=张三&password=123456就可以得到下面的。

 此时说明服务器已经获取到客户端传递过来的参数.

⑤POST请求body格式

POST 请求的参数一般通过 body 传递给服务器 . body 中的数据格式有很多种。这里主要说三种
(1)x-www-form-urlencoded
如果请求是这种格式,服务器获取参数的方式和GET一样,也是getParameter。那么如何在前端构造一个这样的格式请求呢?
1.form表单
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("/postParameter")
public class PostParameter extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        String userName=req.getParameter("userName");
        String password=req.getParameter("password");
        resp.getWriter().write("userName="+userName+","+"password="+password);
    }
}

html代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>PostParameter</title>
</head>
<body>
<form action="postParameter" method="post">
  <input type="text" name="userName">
  <input type="text" name="password">
  <input type="submit" name="提交">
</form>
</body>
</html>

如何建立连接?


结果展示:

当你在网页输入PostParameterhttp://127.0.0.1:8080/untitled20/TestPost.html

时,会出现

输入后提交会出现:

 2.postman比较简单,这里就不再统一说了
(2)json


所以,我们可以使用第三方的库来直接处理json格式数据.这里主要使用的库叫做Jackson (Spring官方推荐的库),通过mavenjackson这个库,给下载到本地并引入到项目中。

a.在中央仓库中找到Jackon相关API,并且在pom.xml中引入jackson依赖:

在pom.xml中引入依赖:

  <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.12.7</version>
            </dependency>

b.在浏览器前端代码中,通过js构造出body为json格式的请求

其中用ajax来构造post请求,使用contentType来说明请求的类型,data属性来设置body的内容。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--    构造json格式的请求-->
<input type="text" id="userId">
<input type="text" id="classId">
<input type="button" id="submit" value="提交" >

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>

      let userIdInput = document.querySelector('#userId');
        let classIdInput = document.querySelector('#classId');
        let button = document.querySelector('#submit');
        button.onclick = function() {
            $.ajax({
                type: 'post',
                url: 'postJson',
                contentType: 'application/json',
                data: JSON.stringify({
                    userId: userIdInput.value,
                    classId: classIdInput.value
                }),
                success: function(body) {
                    console.log(body);
                }
            });
        }
    </script>
</body>
</html>

其中值得注意的进一步说明:

c.在Java后端代码中,通过Jackon来进行处理,需要使用Jackon,把请求body中的数据读取出来,并且解析成Java中的对象

代码:

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 userId;
    public String classId;//这个类的定义是都行. 但是你前端传的数据类型要匹配,虽然在这个地方可以用int,但是理论上我们不确定 jackson 里是否是帮咱们做类型转换了
}
@WebServlet("/postJson")//注意此注解只能够在servlet类中使用,要挨着servlet这个类
public class PostJsonSelvlet extends HttpServlet {
    //1.创建一个json核心对象
    private ObjectMapper objectMapper=new ObjectMapper();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf8");
        //2.读取body请求的内容,使用ObjectMapper对象的readValue方法来解析
        //readValue的作用就是把json格式的字符串转换成java对象
        //readValue中有两个参数,第一个参数的含义是对哪个字符串进行转换
        //第二个参数,表示需要将请求的json格式数据转换成哪一个java对象
        User user=objectMapper.readValue(req.getInputStream(),User.class);
        System.out.println(user.userId);
        System.out.println(user.classId);
        resp.getWriter().write("userId = " + user.userId + ", classId = " + user.classId);
    }
}

结果显示如下:

 d.readValue是怎么完成转换的?
1.先把getInputStream对应的流对象里面的数据都读取出来;
2.针对这个json字符串进行解析,从字符串=>键值对;
3遍历这个键值对,依次获取到每一个key 。根据这个key 的名字,和User类里面的属性名字对比一下,看有没有匹配的名字!!如果发现匹配的属性,则把当前key对应的value赋值到该User类的属性中(赋值的过程中同时会进行类型转换),如果没有匹配的属性,就跳过,取下一个key.
4.当把所有的键值对都遍历过之后,此时User对象就被构造的差不多了. 

(3)form-data这里也不详细讲了。

           2.3HTTPServletResponse

Servlet 中的 doXXX 方法的目的就是根据请求计算得到相应, 然后把响应的数据设置到
HttpServletResponse 对象中.
然后 Tomcat 就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式, 转成一个字符串, 并通过 Socket 写回给浏览器.
①核心API:
注意: 响应对象是服务器要返回给浏览器的内容, 这里的重要信息都是程序猿设置的. 因此上面的方法都是 "写" 方法.
注意: 对于状态码/响应头的设置要放到 getWriter / getOutputStream 之前. 否则可能设置失效.

 ②实例一:设置状态码:

实现一个程序 , 用户在浏览器通过参数指定要返回响应的状态码 .(使用的是GET,因为更简单)
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 setStatus extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf8");
        int status=500;
        resp.setStatus(status);
        resp.getWriter().write("响应状态码是"+status);
    }
}

展示结果:

用post,只需要把java代码中的doGet改为doPost,然后再添加一下下面的代码即可。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>status</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
  $.ajax({
  type:'post',
  url:'status',
   success: function (body) {
                console.log(body);
            }
  });
</script>
</body>
</html>

 展示结果:

③实例二:自动刷新

这是用GET的代码:

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 autoRefresh extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf8");
        resp.setHeader("Refresh","1");
        resp.getWriter().write("这是一个时间戳"+System.currentTimeMillis());

    }
}

展示结果:

每秒会自动刷新

 如果用post的话只需要把方法改doPost,然后加上下面这段

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>status</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
  $.ajax({
  type:'post',
  url:'refresh',
   success: function (body) {
                console.log(body);
            }
  });
</script>
</body>
</html>

 输出结果在控制台上依旧可以看见:

同样每次刷新,时间戳就会发生变化。

④实例三:重定向

实现一个程序, 返回一个重定向 HTTP 响应, 自动跳转到另外一个页面.

GET代码:

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("/redirect")
public class RediractServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //实现重定向,让浏览器自动跳转到搜狗浏览器
        resp.setStatus(302);
        resp.setHeader("Location","https://www.baidu.com");
        //另一种更简单的重定向写法
        //resp.sendRedirect("https://www.baidu.com");
    }
}

结果如下:会自动跳转到百度

下期我们会讲一个实例,以及servlet收尾部分内容~

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

张洋洋~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值