目录
为什么需要动态资源
Servlet
大体的开发流程
Tomcat内部的大概过程
Tomcat的定位
浏览器中输入URL并回车,直到看到网页内容,期间发生了什么?
接受请求
根据请求计算响应
返回响应
DNS的请求过程
业务根据请求,填充响应的过程
结合网络知识
Servlet的使用
HttpServletRequest req对象的简单使用
读取请求头:
读取请求体:
HttpResponseServlet Resp对象的使用
HttpServlet是什么?
Servlet的生命周期
Servlet的日常开发(基于场景描述)
为什么需要动态资源
在某些资源路径固定的情况下,我们需要对应不同的环境、条件,产生不同的内容。
如:各个网站的个人中心页面,博客详情页;
Servlet
Servlet 就是 Tomcat 这个 HTTP 服务器提供给 Java 的一组 API, 来完成构建动态页面这个任务。
大体的开发流程
- 通过继承HTTPServlet抽象类,完成我们自己的类(每个都是一个独立的Web资源)
- 重写其中的一些方法(get、post、...),输出资源内容的过程
- 把动态资源和路径建立绑定关系:可以通过web.xml,或者使用Java中的注解语法完成@WebServlet。
整个过程都需要遵守标准,否则Tomcat不认。
整个过程都需要遵守标准,否则Tomcat不认。
不需要自己实例化HTTPServlet对象(Tomcat内部在实例化);
也不会调用我们写好的get、post方法(Tomcat内部会在合适的时机去调用);
HTTP请求<->HttpServletRequest(这里的方法以getXXX为主)
HttpServletResponse<->HTTP响应(这里的方法以setXXX为主)
通过web.xml实现绑定:
package com.wh.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
//1、声明这个类是一个web资源,via让类继承HTTPServlet这个抽象类
//2、看我们的动态资源支持那些HTTP方法(支持get方法via重写doGet方法)
public class FirstServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//3、读取request对象中的信息(都是来自HTTP请求中的信息)
//4、根据当下的条件
//5、准备相应数据 , 通过填写response对象来做到
//设置状态码是201
resp.setStatus(201);
//字符集编码
resp.setContentType("text/plain;charset=utf-8");
PrintWriter writer=resp.getWriter();
writer.printf("你好世界");
//通过web.xml实现绑定
}
}
web.xml:
MyFirstServlet com.wh.servlet.FirstServlet MyFirstServlet /first
状态码:
使用Java中的注解语法完成@WebServlet:
package com.wh.servlet;
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.io.PrintWriter;
//通过使用@WebServlet注解修饰类,来实现绑定
@WebServlet("/second")
public class SecondServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(202);
resp.setContentType("text/css");
resp.setContentType("text/plain;charset=utf-8");
PrintWriter writer=resp.getWriter();
writer.printf("你好中国");
}
}
- 通过继承HTTPServlet,表示这个类已经是一个有效的Web资源(动态)了。
- 重写doGet方法,表示这个资源支持HTTP的 GET方法访问。隐含着:其他的HTTP方法,是不支持的,如果非要使用别的方法(非405)访问这个资源,会405。
resp.setStatus(202); resp.setContentType("text/plain;charset=utf-8"); PrintWriter writer=resp.getWriter(); writer.printf("你好中国");根据请求或者其他条件构造响应的过程。就是动态资源书写的过程。
//通过使用@WebServlet注解修饰类,来实现绑定 @WebServlet("/second")最后一步,把动态资源和路径绑定,才能对外提供服务。
Tomcat内部的大概过程
1、Tomcat收到了一个HTTP请求
2、Tomcat根据HTTP协议的规定,解析请求,得到请求方法,资源路径,请求头等等
3、Tomcat根据资源路径(Context Path+Servlet Path):
先根据Context Path,决定这个资源是交给哪个Webapp处理(隐含:Tomcat支持多个webapp同时存在)。
确定是哪个webapp后,再根据Servlet Path,找到对应的资源进行处理。
如果是动态资源,根据路径得到一个类名(com.wh.servlet.FirstServlet),如果这个类还没有实例化对象,则实例化其对象,否则直接获取其对象(单例模式)——拿到对象根据请求方法,调用该对象doGet方法或者doPost或者其他方法。
doGet方法执行结束之后,resp对象被填充信息(202,text/csss,你好中国),Tomcat根据这个对象,构造http响应,发送给浏览器。
如果是静态资源,就根据对应的路径去查找对应的文件,并相应文件内容。
如果动态或者静态资源都没有找到,就会404。
Tomcat的定位
1、HTTP服务器(读取+解析HTTP请求;构造+发送HTTP相应)
2、Servlet容器(遵守Servlet标准,可以按照标准,调用对应方法处理请求)
当浏览器给服务器发送请求的时候,通过HTTP协议和HTTP进行交流,Tomcat(HTTP服务器)接收这个请求。然后Tomcat会调用Servlet里我们实现的类,Servlet的类会读取数据库的数据。如图:
浏览器中输入URL并回车,直到看到网页内容,期间发生了什么?
只站在应用层的角度:
接受请求
用户在浏览器输入一个URL(如果是域名,通过DNS服务器,将域名换成ip的过程),浏览器构建一个HTTP请求。
这个HTTP请求经过网络协议栈逐层封装称二进制的比特流,通过物理层的硬件设备转换称某些物理信号传输出去。
这些物理信号通过互联网的网络设备,到达目标主机。
服务器主机接收到物理信号,通过网络协议栈逐层分用解析,还原HTTP请求,交给Tomcat进程进行处理(通过端口号进行确定)。
Tomcat通过Socket读取解析HTTP请求,根据请求中的Context Path确定webapp,再通过Servlet Path确定一个具体的类,根据当前请求方法(GET,POST...),决定调用这个类中的哪个方法(doGet,doPost...)。此时类的对应方法的第一个参数HttpServletRequest就包含了这个HTTP请求的详细信息。
根据请求计算响应
在Servlet类中,我们重写的方法中执行代码,这些代码会根据请求中的一些信息,来给HttpServletRequest对象设置一些属性。例如这些:
resp.setStatus(202);//状态码
resp.setContentType("text/plain;charset=utf-8");//字符集编码
PrintWriter writer=resp.getWriter();
writer.printf("你好中国");//内容
返回响应
对应类的重写方法(doGet,doPost...)执行完毕之后,Tomcat会自动把HttpServletRequest这个我们刚设置好的对象转换成一个符合HTTP协议的字符串,通过Socket把这个响应发出去。
然后响应数据在服务器的主机上通过网络协议栈层次封装,得到一个二进制bit流,通过物理层硬件设备转换成物理信号传输出去。
这些物理信号通过互联网的一系列网络设备,最终到达浏览器所在的主机。
浏览器主机收到这些物理信号,通过网络协议栈逐层进行分用解析,还原HTTP响应,交给浏览器处理。
浏览器也通过Socket读到这个响应(一个字符串),按照HTTP响应的格式来解析这个响应,把body中的数据按要求显示在浏览器的界面上。如果这个响应中还包含html或者js等,可能还会触发产生新的HTTP请求。
DNS的请求过程
1、浏览器可能有本地DNS缓存
2、如果缓存中没有,去调用OS系统调用,去要DNS换IP
OS读取本地hosts文件(俗称固定host)
OS会发送DNS请求,给本地DNS服务器。
如果本地DNS有记录,直接返回,如果没有,再去它的上一级要记录结果。最终这个过程走到了权威DNS(根DNS:全球一共13台)。
业务根据请求,填充响应的过程
1、直接填充
2、配合数据库中的数据进行填充(比如验证登录)
3、还有其他的存储/接口服务(比如Redis、ES(搜索引擎),MongoDB等)
结合网络知识
1、纯TCP的角度也可以讲述:要发送HTTP请求,收到HTTP响应,首先得有TCP连接。可以展开TCP三次握手,通信,四次挥手。
2、站在发送端主机的角度:应用层->传输层->网络层->数据链路层->物理层(网卡),按照各种协议封装;
站在接收端主机的角度:物理层(网卡)->数据链路层->网络层->传输层->应用层,按照协议解包+分用;
3、只针对网络层的路由(路由表的作业(把长期目标->短期目标)、ARP表(把局域网IP换成MAC地址))。
4、NAT服务器怎么做IP和端口(port)替换映射的。
Servlet的使用
HttpServletRequest类的方法了解(配合HTTP请求格式理解)
HttpServletRequest对象是对HTTP请求的对象化
HttpServletRequest req对象的简单使用
HTTP请求中有哪些信息?
请求方法,资源路径URL,请求版本,请求头们,请求体(如果存在的话)
这些信息都可以通过req对象获取(有的是直接获取,有些被Tomcat加工成更方便我们的格式)
import com.sun.xml.internal.ws.util.StringUtils;
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("/req-info")
public class GetRequestInfoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 请求方法
String method = req.getMethod();
System.out.println("HTTP 方法: " + method);
System.out.println();
System.out.println();
String requestURI = req.getRequestURI();
System.out.println("requestURI: " + requestURI);
StringBuffer requestURL = req.getRequestURL();
System.out.println("requestURL: " + requestURL.toString());
String pathInfo = req.getPathInfo();
System.out.println("pathInfo: " + pathInfo);
String contextPath = req.getContextPath();
System.out.println("contextPath: " + contextPath);
String servletPath = req.getServletPath();
System.out.println("servletPath: " + servletPath);
String pathTranslated = req.getPathTranslated();
System.out.println("pathTranslated: " + pathTranslated);
String queryString=req.getQueryString();
System.out.println("queryString: " + queryString);
System.out.println();
System.out.println();
String serverName = req.getServerName();
System.out.println("serverName: " + serverName);
int serverPort = req.getServerPort();
System.out.println("serverPort: " + serverPort);
String scheme = req.getScheme();
System.out.println("schema: " + scheme);
String protocol = req.getProtocol();
System.out.println("protocol: " + protocol);
}
}
读取请求头:
//请求头们(本质就是一组Key-value:不同之处Key是可以重复的)
Enumeration headerNames = req.getHeaderNames();
//Enumeration类似Iterator(hasNext()+next())
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String value = req.getHeader(name);
System.out.println(name + " += " + value);
}
读取请求体:
准备携带请求体的POST请求
准备一个html,里面有form表单,通过form表单来发送HTTP POST请求;
准备一个动态资源,仅支持POST方法,打印读取到的请求体(这个动态资源不能用GET方法访问)。
HttpResponseServlet Resp对象的使用
读取响应:
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.io.OutputStream;
import java.io.PrintWriter;
@WebServlet("/set-resp")
public class setRespServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置响应状态
resp.setStatus(204);
//设置响应头
//1、标准响应头:比如Content-Type(PS:Content-Length 不需要设置)
//2、非标准响应头:填什么都可以(中文不行)
resp.setHeader("Content-Type","text/plain;charset=utf-8");
resp.setHeader("X-My-Class","Java");
//响应体内容是字符(使用较多的)
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/plain");
PrintWriter writer=resp.getWriter();//设置相应字符集要在getWriter()之前
writer.print("你好");
//响应体内容是字节流
OutputStream outputStream=resp.getOutputStream();
outputStream.write('H');
outputStream.write('e');
}
HttpServlet是什么?
Servlet标准,大概规定的是一个请求-响应服务器模型(不仅仅针对HTTP协议)
interface Servlet-> abs class HttpServlet->MyServlet
Servlet的生命周期
被抽象成三个阶段:
1、初始化阶段 init(...);有且仅有一次
2、处理 请求-响应阶段 service(req,resp);每次请求-响应都会执行一次
3、销毁阶段 destroy();有且仅有一次
重写构造方法(必须是无参)、init()、doGet()、doPost()
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("/lifecycle")
public class LifecycleServlet extends HttpServlet {
public LifecycleServlet() {
System.out.println("Lifecycle 的构造方法被调用");
}
@Override
public void init() throws ServletException {
System.out.println("Lifecycle.init() 被调用");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Lifecycle.doGet() 被调用");
}
@Override
public void destroy() {
System.out.println("Lifecycle.destroy() 被调用");
}
}
通过打印+调试:
1、init()、service()、destroy()的被调用次数以及被调用时机
2、类是被Tomcat的代码实例化的
3、这里写的方法是被Tomcat调用的
4、对象是单例模式管理的
5、Tomcat使用的是懒汉模式创建的单例对象
6、diGet、doPost等方法都是在HttpServlet中重写Service然后调用的
7、对象是运行在Tomcat启动管理的线程池中的
8、javax.servlet.*下的类是官方提供的;org.apache.catalina.*下的类是Tomcat提供的
这里写的Servlet下的方法是运行在多线程环境下+类的对象是单例的 (写代码时要避免使线程不安全的操作和代码)
Servlet的日常开发(基于场景描述)
1、@WebServlet(...)这里填写的资源路径的问题:
必须以/开头;
路径不能重复出现;
正常情况有:"/hello" "/hello/world" "/hello/html" "/hello.jpg"...
两个特殊情况:
通配符("*.do"):只要路径后缀名是.do的URL都归我管的;
"/":所有的URL都归我管的;
" "(空格):代表根URL;



