Servlet的基本使用
步骤
1.IDEA创建一个maven
项目
2.在中央仓库引入依赖~
3.创建目录结构 webapp/WEB-INF/web.xml
4.编写代码
1)创建一个类,继承自HttpServlet
2) 重写HttpServlet doGet/doPost方法
3)在方法里面根据请求计算响应,直接在响应对象中构造一个"hello world"
4)给类上面加一个注解@WebServlet,把这个类和一个具体的HTTP请求的路径关联起来~
5.打包,使用maven
使用packaging标签修改打包类型,build标签finalName标签指定打包后的名称
6.部署,把war包拷贝到Tomcat的webapps目录中
Tomcat会自动的对这个war包进行解压缩,得到一个同名目录
7.验证
http://服务器的ip:8080/ContextPath/[WebServlet注解中描述的路径]
smart Tomcat插件
插件 plugin
.
计算机中有很多的程序,都是基于“插件体系”
最早的知名的插件体系的程序 编辑器中的Vim
和Emacs搭配一些额外的插件
包括知名的eclipse.
现在的eclipse 只是一个“平台”,搭配上不同的插件之后,就可以成为Java开发工具/C++开发工具等
为了更好地快速完成Servlet程序的部署,就有一个插件可以完成-----即Smart Tomcat
安装smart Tomcat
在弹出的窗口中点击Plugins(插件),然后搜索smart Tomcat,点击Install,进行下载安装。
当idea中出现如下日志时,就表明插件运行成功。
Smart Tomcat这个插件的运行原理,是临时创建了一个单独的目录,将目前的代码拷贝了一个临时副本过去,让Tomcat运行,因此在原始的webapps目录中,看不到war包。
常见问题
部署程序到Tomcat后,我们进行访问时,可能会出现以下情况:
1.出现404
可能原因:路径URl输入错误。
2.出现405
可能原因:请求方法和代码中重写方法不匹配。
3.出现500
可能原因:内部服务器代码运行出现异常。
4.出现空白页面
5.无法访问该网站。
Servlet工作原理深入了解
当Web服务器接收到一个HTTP请求时,它会先判断请求内容,如果是静态
网页数据,Web服务器会自行处理,如果是动态
数据内容,Web服务器会将请求转交给Tomcat
(也就是其中的Servlet),然后Servlet会找到对应的实例来处理该请求,并将结果返回给Web服务器,再由Web服务器返回给客户端。
对于一个相同 的Sevlet,Tomcat会在第一次收到HTTP请求时建立一个Servlet实例,然后创建一个线程,当第二次收到该HTTP请求的时候,这个时候不会再建立相同的Servlets实例,而是启动第二个线程来服务客户端的 请求。
所以Servlet是运行在多线程的环境
中的。多线程的方式不仅可以提高Web应用程序的执行效率,也可以降低Web服务器的系统负担。
客户端与服务器的详细交互过程:
处理请求过程:
当用户在浏览器中输入URl
,浏览器就会构造一个HTTP请求的报文,然后该报文就会经过网络协议栈依次封装,经过传输层,套上TCP
报头,经过网络层,套上ip
报头,经过数据链路层,套上以太网数据帧的帧头和帧尾,经过物理层,转化为0101的光电信号
,然后通过网络进行转发和传输,最终到达服务器的物理层,然后经过物理层,将光电信号转换为数字信号
,然后到达数据链路层,解析以太网数据帧,取出里面的载核,然后经过网络层,经过ip协议进行解析,取出载核,然后在传输层经过TCP协议解析,然后在应用层得到一个HTTP请求的报文,然后Tomcat
进行解析请求,生成一个HttpServletRequest对象,然后根据对象中的ContextPath
信息,确定是交给哪个Web应用,然后根据ServletPath
,确定交给哪一个类,然后根据报文中的方法确定执行哪个方法,最终执行到具体的代码。
伪代码描述Tomcat核心逻辑
伪代码:大概的表示某种逻辑,语法不一定严谨。
Tomcat初始化流程
//假设Tomcat就是一个类
class Tomcat {
// 用来存储所有的 Servlet 对象
//首先会用一个List来记录所有的Servlet实例,我们自己写类时都会重写doGet或者doPost等方法,这些方法的调用就是依靠这些实例,这里用数组来储存这些Servlet实例
private List<Servlet> instanceList = new ArrayList<>();
//Tomcat启动时就会调用这个start方法
public void start() {
// 根据约定,读取 WEB-INF/web.xml 配置文件;
// 并解析被 @WebServlet 注解修饰的类
// 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类.
//数组来储存所有的Servlet类对象,这些类对象就是webapps目录下的配置文件产生,也有根据我们自己写的类所生成的对象。
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
//创建Socket绑定端口号,开始启动服务器
ServerSocket serverSocket = new ServerSocket(8080);
// 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
ExecuteService pool = Executors.newFixedThreadPool(100);
//循环调用accept方法,每次accept返回的结果都会放到线程池里面,用doHttpRequest处理请求。
while (true) {
Socket socket = ServerSocket.accept();
// 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
pool.execute(new Runnable() {
doHttpRequest(socket);
});
}
//Servlet开始销毁,调用destroy方法
// 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
for (Servlet ins : instanceList) {
ins.destroy();
}
}
public static void main(String[] args) {
new Tomcat().start();
}
}
这里只是伪代码,一些大体流程,真实的Tomcat执行流程会比这个逻辑更复杂一点。
总结:
- Tomcat中内置了main方法,我们启动 Tomcat的时候,是从Tomcat的main方法开始执行
- 首先会进行类的加载,在加载的过程中,会获取到被
@WebServlet
注解修饰的类,也就是我们自己写的类,这些类会被集中管理 - 然后进行类的实例化,通过反射的方式来创建。
- 创建实例后,会调用其中的
init
方法进行初始化,这个方法是HttpServlet自带的方法。 - 然后会循环用
accpet
将其放入线程中,doHttpReuqest
处理请求。 - 最后实例销毁,在销毁之前,会调用
dextory
方法进一步收尾。
Servlet本身是运行在多线程的环境下的。
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 页面,表示服务器内部错误
}
}
}
总结:
- Tomcat会从Socket中读取Http请求,此时读取到的请求是一个字符串,然后会按照HTTP协议的格式解析成一个
HttpServletRequest
对象。 - Tomcat会根据URL中的路径
path
判断当前的请求是动态还是静态的。如果是静态,会直接在本地找到对应的文件内容通过Socket返回,如果是动态的,会执行Servlet相关的逻辑。 - 然后根据URL中的
ContextPath
和ServletPath
确定要调用哪个Servlet实例的service
方法。 - 调用
service
方法,开始处理请求。
service
方法就是我们自己定义的类和其中重写的doGet或者doPost等方法。
逻辑如下:
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);
}
......
}
}
这里调用doXXX方法的时候,
触发了多态
机制.我们自己写的类,都是继承了HttpServlet类,而HttpServlet类又是继承了Servlet,所以我们自己写的类就是Servlet的子类。
这就满足了多态的继承关系。
在Tomcat启动时,Tomcat会根据注解的描述,创建我们所写的类的实例,然后将其放入Servlet数组中集中管理。我们通过URL从数组中拿出该实例时,是通过servlet ins这样的父类引用获取到的。然后通过ins.doGet
的方式调用方法的时候。
这就满足了 多态的父类引用指向子类对象
这一条件。