Servlet 执行原理和API 详情

41 篇文章 2 订阅

目录

1. Serlvet 运行原理

1.1 Tomcat 执行流程

 a. Tomcat 初始化流程

b. Tomcat处理请求流程

c. Servlet 中 service ⽅法的实现:

2. Servlet API详解

2.1 HttpServlet

2.1.1 核心方法

2.1.2 处理GET请求

2.2.3 处理POST请求

2.2 HttpServletRequest

2.2.1 核心方法

2.2.2 打印 Header 信息  

2.2.3 获取 GET 请求中的参数

2.2.4 获取 POST 请求中的参数

2.2.5 获取 JSON 格式参数 

3.HttpServletResponse

3.1 设置状态码  

 3.2 ⾃动刷新

3.3 重定向


1. Serlvet 运行原理

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("/hello")   //设置url映射   http://ip:port/hello
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置返回的类型和编码格式
        resp.setContentType("text/html; charset=UTF-8");
        //返回的数据
        resp.getWriter().println("<h1>你好.servlet.</h1>");
    }
}

输出:http://localhost:8080/first-servlet

 在 Servlet 的代码中并没有写 main ⽅法, 对应的 doGet 代码是如何被调⽤的呢?

1.1 Tomcat 执行流程

当浏览器给服务器发送请求的时候,Tomcat 作为 HTTP 服务器,可以接受到这个请求。

Tomcat 初始化 / 处理请求 :

 a. Tomcat 初始化流程

class Tomcat {
    // ⽤来存储所有的 Servlet 对象
    private List<Servlet> instanceList = new ArrayList<>();
    
    public static void main(String[] args) {
        new Tomcat().start();
   }
    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);
       }
        // 调⽤每个 Servlet 对象的 init() ⽅法,这个⽅法在对象的⽣命中只会被调⽤这 ⼀次;
        for (Servlet ins : instanceList) {
            ins.init();
       }
        // 利⽤我们之前学过的知识,启动⼀个 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);
       });
       }
        // 调⽤每个 Servlet 对象的 destroy() ⽅法,这个⽅法在对象的⽣命中只会被调⽤这⼀次;
        for (Servlet ins : instanceList) {
            ins.destroy();
       }
   }
}

1. Tomcat 的代码中内置了main方法,启动Tomcat时,就是从Tomcat 的 main 方法开始执行的。

2. 被 @WebServlet 注解修饰的类会在 Tomcat  启动的时候就会被获取到,并集中管理。

3. Tomcat 通过映射 的语法机制来创建 被 @WebServlet 注解修饰的类的实例。

4. 实例被创建完了之后, 会点调⽤其中的 init ⽅法进⾏初始化 。
5. 实例被销毁之前, 会调⽤其中的 destory ⽅法进⾏收尾⼯作。
6. Tomcat 内部也是通过 Socket API 进⾏⽹络通信.
7. Tomcat 为了能同时相应多个 HTTP 请求, 采取了多线程的⽅式实现. 因此 Servlet 是运⾏在 多线程环境下的。

b. Tomcat处理请求流程

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 ⻚⾯,表示服务器内部错误
       }
   }
}

 1. Tomcat 从 Socket 中读到的 HTTP 请求是⼀个字符串, 然后会按照 HTTP 协议的格式解析成⼀个 HttpServletRequest 对象。

2. Tomcat 会根据 URL 中的 path 判定这个请求是请求⼀个静态资源还是动态资源. 如果是静态资源, 直接找到对应的⽂件把⽂件的内容通过 Socket 返回. 如果是动态资源, 才会执⾏到 Servlet 的相关逻辑 。

3. Tomcat 会根据 URL 中的 Context Path 和 Servlet Path 确定要调⽤哪个 Servlet 实例的 service ⽅法。

4. 通过 service ⽅法, 就会进⼀步调⽤到我们之前写的 doGet 或者 doPost 。

c. Servlet 中 service ⽅法的实现:

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);
       }
       ......
   }
}
1. Servlet 的 service ⽅法内部会根据当前请求的⽅法, 决定调⽤其中的某个 doXXX ⽅法.
2. 在调⽤ doXXX ⽅法的时候, 就会触发 多态 机制, 从⽽执⾏到我们⾃⼰写的⼦类中的 doXXX ⽅法.

2. Servlet API详解

2.1 HttpServlet

 先创建类, 继承⾃ HttpServlet, 并重写其中的某些⽅法。

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

2.1.1 核心方法

 实际开发过程中,主要重写 doXXX ⽅法, 很少会重写 init / destory / service 。

这些⽅法的调⽤时机, 就称为 "Servlet ⽣命周期". (也就是描述了⼀个 Servlet 实例从⽣到死的过程).

2.1.2 处理GET请求

@WebServlet("/hello")   //设置url映射   http://ip:port/hello
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置返回的类型和编码格式
        resp.setContentType("text/html; charset=UTF-8");
        //返回的数据
        resp.getWriter().println("<h1>你好.servlet.</h1>");
    }
}

数据提交的两种方式:

1. form 表单提交

2. ajax 提交

form 表单提交 :

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String result = "未知错误";
        // 1.得到参数
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        // 2.业务处理
        // 非空效验
        if (null != username && null != password && !username.equals("") && !password.equals("")) {
            if (username.equals("admin") && password.equals("admin")) {
                result = "恭喜:登录成功!";
            } else {
                result = "登录失败,用户名或密码输入错误,请检查!";
            }
        } else {
            result = "非法参数!";
        }

        resp.setContentType("text/html; charset=utf-8");

        // 3.将结果返回给前端
        resp.getWriter().write(result);
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户登录系统</title>
</head>
<body>
<form action="login" method="post">
    <div style="margin-top:50px;margin-left:40%;">
        <h1 style="padding-left:50px;">用户登录</h1>
            姓名:<input type="text" name="username">
        <p>
            密码:<input type="password" name="password">
        <p>
        <div style="padding-left:50px;">
            <input type="submit" value=" 提 交 "> &nbsp;&nbsp;&nbsp;
            <input type="reset" value=" 重 置 ">
        </div>
    </div>
</form>
</body>
</html>

 可以发现,点击“提交”之后,会从html页面变换到另外一个页面,并且不支持刷新。

 

 ajax 提交:

使⽤ Ajax 不需要刷新整个⻚⾯。

2.2.3 处理POST请求

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
    resp.setContentType("text/html; charset=utf-8");
    resp.getWriter().write("POST 响应");
}
@WebServlet("/hello")   //设置url映射   http://ip:port/hello
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

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

2.2 HttpServletRequest

2.2.1 核心方法

都是 "读" ⽅法, ⽽不是 "写" ⽅法,是服务器接收到的内容,不可以修改。  

2.2.2 打印 Header 信息  

@WebServlet("/myreq")
public class MyreqServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        StringBuilder builder = new StringBuilder();
        // 得到请求的协议和版本号
        builder.append("协议:" + req.getProtocol() + "<br>");
        resp.getWriter().write(builder.toString());
    }
}

@WebServlet("/myreq")
public class MyreqServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        StringBuilder builder = new StringBuilder();
        // 得到请求的协议和版本号
        builder.append("协议:" + req.getProtocol() + "<br>");
        // 得到请求的类型
        builder.append("Method:" + req.getMethod() + "<br>");
        resp.getWriter().write(builder.toString());
        // 得到 uri
        builder.append("URI:" + req.getRequestURI() + "<br>");
        builder.append("URL:" + req.getRequestURL() + "<br>");
        builder.append("ContextPath:" + req.getContextPath() + "<br>");
        builder.append("QueryString:" + req.getQueryString() + "<br>");
        //hr表示水平分隔符
        builder.append("<hr><h2>请求头</h2>");
        // 得到所有的请求类型(headerNames)和请求值
        Enumeration<String> headerNames = req.getHeaderNames();
        while (headerNames.hasMoreElements()) { // 判断还有没有 head name
            String headName = headerNames.nextElement();
            // 得到 head value
            String headValue = req.getHeader(headName);
            builder.append(headName + ": " + headValue + "<br>");
        }
        builder.append("<hr>");
        builder.append("参数:" + req.getParameter("name") + "<br>");
        resp.getWriter().write(builder.toString());
    }
}

 

2.2.3 获取 GET 请求中的参数

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

比如:http://localhost:8080/first-servlet/myreq?name=mysql&password=123

表示浏览器用过query string 给服务器传递了两个参数,nameId 和 passwordId,值分别是mysql,123 。在服务器端可以通过getParameter 来获取到参数的值。 

 此时说明服务器已经获取到客户端传递过来的参数 。getParameter 的返回值类型为 String. 必要的时候需要⼿动把 String 转成 int 。

 

 

jQuery.ajax({
            url:"login", // 设置请求地址
            type:"POST", // 设置请求方法类型
            contentType:"application/x-www-form-urlencoded; charset=utf-8", // 请求类型
            // dataType:"", // 响应的类型
            data:{"username":username.val(),"password":password.val()}, // 请求参数
            success:function(data){
                alert(data);
            }
        });

2.2.4 获取 POST 请求中的参数

POST 请求的参数⼀般通过 body 传递给服务器.
body 中的数据格式有很多种.。如果是采⽤ form 表单的形式, 仍然可以通过 getParameter 获取参数的值。

 

@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 userId = req.getParameter("userId");
        String classId = req.getParameter("classId");
        resp.getWriter().write("userId: " + userId + ", " + "classId: " +
classId);
   }
}

 

<form action="postParameter" method="POST">
    <input type="text" name="userId">
    <input type="text" name="classId">
    <input type="submit" value="提交">
</form>

 

 

通过抓包可以看到, form 表单构造的 body 数据的格式为:

 

2.2.5 获取 JSON 格式参数

使⽤之前 getParameter 的⽅式获取不了 JSON 格式的数据。

@WebServlet("/postjson")
public class JsonPostServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @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");
        System.out.println("username" + ":" + username);
        System.out.println("password" + ":" + password);
        resp.getWriter().println("username" + username);
    }
}
输出:
http://localhost:8080/first-servlet
username:null
password:null

 

 

 

 使⽤ InputStream 获取 JSON 参数

@WebServlet("/postjson")
public class JsonPostServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //设置返回类型和编码
        resp.setContentType("text/html;charset=utf-8");
        //1. 得到数据流
        ServletInputStream inputStream = req.getInputStream();
        //2. 使用数组接受流信息
        byte[] bytes = new byte[req.getContentLength()];
        inputStream.read(bytes);
        //3. 将数组转换成字符串
        String result = new String(bytes,"utf-8");
        System.out.println(result);
        resp.getWriter().println(result);
    }
}

 

服务器拿到的 JSON 数据仍然是⼀个整体的 String 类型, 如果要想获取到 userId 和
classId 的具体值, 还需要搭配 JSON 库进⼀步解析。

 

public class App {
    public static void main(String[] args) throws JsonProcessingException {
        //1、创建一个json对象
        ObjectMapper objectMapper = new ObjectMapper();
        //2. 将对象转换成JSON字符串
        Student student = new Student();
        student.setId(1);
        student.setName("Java");
        student.setPassword("123");
        //将对象转换为string
        String result = objectMapper.writeValueAsString(student);
        System.out.println(result);
    }
}
@Data
class Student {
    private int id;
    private String name;
    private String password;
}

输出:
{"id":1,"name":"Java","password":"123"}
public class App {
    public static void main(String[] args) throws JsonProcessingException {
        //1、创建一个json对象
        ObjectMapper objectMapper = new ObjectMapper();
        //2.将JSON字符串转换为对象
        String jsonStr = "{\"id\":2,\"name\":\"MySQL\",\"password\":\"456\"}";
        //readValue(字符串,转换的目标类型)
        Student lisi = objectMapper.readValue(jsonStr,Student.class);
        System.out.println(lisi);
    }
}
@Data
@ToString
class Student {
    private int id;
    private String name;
    private String password;
}
输出:
Student(id=2, name=MySQL, password=456)

 

    //将对象转换为string
    String result = objectMapper.writeValueAsString(student);

        //2.将JSON字符串转换为对象
        String jsonStr = "{\"id\":2,\"name\":\"MySQL\",\"password\":\"456\"}";

Jackson 库的核⼼类为 ObjectMapper.
其中的 readValue ⽅法把⼀个 JSON 字符串转成 Java 对象.
其中的 writeValueAsString ⽅法把⼀个 Java 对象转成 JSON 格式字符串.

 

 

 

3.HttpServletResponse

响应对象是服务器要返回给浏览器的内容, 都 是 "写" ⽅法 。

 

3.1 设置状态码  

在浏览器通过参数指定要返回响应的状态码.
@WebServlet("/state")
public class StateServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int state = Integer.valueOf(req.getParameter("state"));
        if (state > 0) {
            resp.setStatus(state);
        } else {
            resp.setContentType("text/html; charset=utf-8");
            resp.getWriter().println("<h2>无效参数</h2>");
        }
    }
}

 

 3.2 ⾃动刷新

@WebServlet("/refresh")
public class RefreshServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //告诉浏览器1秒刷新一次
        resp.setHeader("Refresh", "1");
        resp.getWriter().println("" + LocalDateTime.now());
    }
}

 

看到浏览器每秒钟⾃动刷新⼀次.  

3.3 重定向

@WebServlet("/redirectServlet")
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException {
        resp.sendRedirect("http://www.sogou.com");
   }
}

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值