一、概述:
1、当我们想要访问一个网站时,只需要在浏览器地址栏里输入网址,然后点击搜索或者链接即可,比如www.baidu.com,然后回车浏览器就可以跳转到百度首页了,那么这个过程底层的步骤是怎么实现的呢?这就涉及到http的请求与响应了:
2、我们访问网站时,大部分场景下都是基于Browser/Server模式(浏览器/服务器模式),简称BS架构,它的特点是,客户端只需要浏览器,应用程序的逻辑和数据都存储在服务器端。浏览器只需要请求服务器,获取网页页面,并把网页页面展示给用户即可,此传输过程遵循Http协议。对于浏览器来说:1.与服务器建立TCP连接; 2 发送HTTP请求; 3 收取HTTP响应,然后把网页在浏览器中显示出来。对于服务器:读取解析请求头,响应回网页(一般是HTML网页),网页中如果嵌入了JavaScript、CSS、图片、视频等其他资源,浏览器会根据资源的URL再次向服务器请求对应的资源,解析之后给用户显示在页面。
二、什么是Servlet?
1、从上文我们可以得知B/S模式下的每一次动态网页交互其实是比较麻烦,需要服务器解析 HTTP 请求的报头,分析用户的请求参数,加载数据库组件……这些基础工作需要耗费大量的时间,如果我们只需要输出一个简单的HTML页面,就不得不编写上千行底层代码,那就根本无法做到高效而可靠地开发,这些种种原因导致使用原生 Java 开发服务器应用是一件不容易的事情。正是基于这种原因,Java 官方后来推出了 Servlet 技术,它对开发动态网站需要使用的原生 Java API 进行了封装,形成了一套新的 API,称为 Servlet API。
2、Servlet也称服务连接器,是用Java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口:
//Java中Servlet接口源码
public interface Servlet {
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletInfo();
public void destroy();
}
而我们一般所说的Servlet是指任何实现了这个Servlet接口的类,这个类总是继承自HttpServlet(间接实现了Servlet接口的抽象类),然后覆写doGet()或doPost()方法。被用来扩展服务器的性能,服务器上驻留着可以通过“请求-响应”编程模型来访问的应用程序。虽然 Servlet 可以对任何类型的请求产生响应,但通常只用来扩展 Web 服务器的应用程序。
3、Servlet动态生成网页的基本过程为:
- 客户端发送请求至服务器;
- 服务器将请求信息发送至 Servlet;
- Servlet 生成响应内容并将其传给服务器。响应内容动态生成,通常取决于客户端的请求;
- 服务器将响应返回给客户端。
@WebServlet("/hello.do")
public class HttpServlteTest extends HttpServlet {
private Set set = new LinkedHashSet<String>();
// 重写doGet方法:处理get方式请求
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
System.out.println("哎哟,被get到了哟!");
int port = req.getServerPort();
System.out.println(port);
PrintWriter writer = resp.getWriter();
String name = req.getParameter("name");
System.out.println("本次报名:" + name);
if (set.add(name)) {
writer.write(name + ",报名成功");
} else {
writer.write(name + ",报名失败,不可重复报名");
}
if (name.equals("蔡徐坤")) {
writer.write(("你干嘛~哎哟"));
}
writer.write(set.toString());
writer.close();
}
}
运行效果:
三、Servlet的本质,它是到底帮我们做了什么?
1、上述代码中我们发现在使用Servlet时会发现我们并不需要自己去编写代码解析请求,而只需要处理业务逻辑功能,这是因为我们覆写的doGet()或doPost()方法传入了HttpServletRequest和HttpServletResponse两个对象,分别代表HTTP请求和响应。这两个对象已经封装好了请求和响应。以发送响应为例,我们只需要设置正确的响应类型,然后获取PrintWriter,写入响应即可。
2、那么Servlet是怎么处理浏览器请求的呢,很显然在我们上面写的Servlet类中并不能体现出来,那么它是依靠什么来实现的呢?答案是Servlet容器,严格来说Servlet API是一套规范,在Servlet类里中根据请求你可以编写代码你想要响应什么,具体的响应过程其实是实现了Servlet API的web服务器来帮你完成的。目前使用最广泛的是Tomcat:由Apache开发的开源免费服务器。我们也俗称这样的服务器为Servlet容器,因为它里面"存放着"Servlet对象,管理我们的Servlet类。
3、所以说啊,我们在写一个Servlet程序的时候肯定是把servlet部署到一个容器中的,容器真正会管理我们的Servlet对象,我们也都知道一个web服务器程序肯定会包括三个步骤:接受请求;处理请求;响应请求,其中接受请求和响应请求在一些方面是其实是有共性的,但是处理请求是看具体业务功能的,得嘞,这部分我们抽出来做成Servlet,在Servlet的类的方法里面写我们的逻辑代码。诶?那好像我们在程序里也没写过main()方法啊,一路走来,我们面向接口编程,现在面向框架编程,就比如先前的JDBC我们连接数据库我们只需要用getConnection()给里面传入数据库连接参数然后得到一个数据库连接对象返回给Connection接口就可以用了,但是底层的校验连接是我们自己实现的吗?显然这不是一个合理的编程思想,于是各大厂商直接附带给我们相应的驱动比如Driver类。
4、其实Tomcat也一样,在Servlet服务器程序中,我们观察自己写的Servlet类,既不需要我写TCP连接数据库,也不需要我解析HTTP请求,更不需要我把结果转成HTTP响应,会发现其实最主要的的在于覆写的doGet()或doPost()方法传入的HttpServletRequest和HttpServletResponse两个形参,这两个形参就是Tomcat事先封装好的对象,req对象和resp对象已经帮我们把这一系列事情搞定了。就像我们从来不会在servlet中写什么监听8080端口的代码,servlet不会直接和客户端打交道,归根到底Tomcat才是与客户端直接打交道的家伙,它监听端口,拿到请求后,根据url等信息,确定要将请求交给哪个servlet去处理,然后调用那个servlet的service方法,service方法返回一个response对象,Tomcat再把这个response返回给客户端。
5、类似Tomcat这样的服务器其实也是Java编写的,启动Tomcat服务器实际上是启动Java虚拟机,执行Tomcat的main()方法,然后由Tomcat负责加载我们的*.war文件(你的Servlet项目),并创建一个HelloServlet实例,最后以多线程的模式来处理HTTP请求。如果Tomcat服务器收到的请求路径是/项目名称/hello.do,就转发到Servlet并传入HttpServletRequest和HttpServletResponse两个对象。
四、Servlet的生命周期
1、我们无法在代码中直接通过new创建Servlet实例,必须由Servlet容器自动创建Servlet实例,Servlet容器只会给每个Servlet类创建唯一实例,Servlet实例创建和使用的过程,被称为Servlet的生命周期。整个生命周期包括:实例化、初始化、服务、销毁。
2、从上文我们知道Servlet接口有5个方法,其中init、service、destroy是生命周期方法。init和destroy各自只执行一次,即servlet创建和销毁时。而service会在每次有新请求到来时被调用。也就是说,我们主要的业务代码需要写在service中。
3、具体过程:实例化:客户端请求到Servlet路径并调用构造方法创建Servlet实例。初始化:通过该Servlet的实例,调用init()方法初始化该Servlet 。服务:通过该Servlet的实例,调用service()方法,如果子类没有重写该方法,则调用HttpServlet父类的service()方法,在父类的该方法中进行请求方式的判断,如果是GET请求,则调用doGet()方法;如果是POST请求,则调用doPost()方法;如果子类没有重写doGet()方法或者其他doxxx()则会调用父类对应的方法报405之类的4开头的错误状态码。销毁:服务器关闭或重启时,会销毁所有的Servlet实例,会调用Servlet实例的destroy()方法。
5.Req/Resp:
Tomcat帮我们将一个HTTP请求封装进了HttpServletRequest,即参数对象req,通过此对象可以拿到HTTP请求的几乎全部信息,常用的方法有:
● getMethod():返回请求方法,例如,"GET","POST";
● getRequestURI():返回请求路径,但不包括请求参数,例如,"/hello";
● getQueryString():返回请求参数,例如,"name=Bob&a=1&b=2";
●getxxx()......
至于Response,Tomcat传给Servlet时,它还是空的对象。Servlet逻辑处理后得到结果,最终通过response.write()方法,将结果写入response内部的缓冲区。Tomcat会在servlet处理结束后,拿到response,遍历里面的信息,组装成HTTP响应发给客户端。写入完毕后必须调用flush()方法,因为大部分Web服务器都基于HTTP/1.1协议,会复用TCP连接。如果没有调用flush(),将导致缓冲区的内容无法及时发送到客户端。
由于HTTP响应必须先发送Header,再发送Body,所以,操作HttpServletResponse对象时,必须先调用设置Header的方法,最后调用发送Body的方法。 常用的设置Header的方法有:
● setStatus(sc):设置响应代码,默认是200;
● setContentType(type):设置Body的类型,例如,"text/html";
● setCharacterEncoding(charset):设置字符编码,例如,"UTF-8";
● setHeader(name, value):设置一个Header的值;
总结:Servlet就是人们制定java应用中使用web服务时的各种规范,统一接口,其他内部实现由厂商自己实现,比如tomcat jetty GlassFish等等。