目录
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 服务器,可以接受到这个请求。
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 。
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=" 提 交 ">
<input type="reset" value=" 重 置 ">
</div>
</div>
</form>
</body>
</html>
可以发现,点击“提交”之后,会从html页面变换到另外一个页面,并且不支持刷新。
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 库进⼀步解析。因此,引入 JackSon Databind 添加 Maven Repository: com.fasterxml.jackson.core » jackson-databind » 2.13.2.1 (mvnrepository.com)
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");
}
}