先来写一个helloworld
准备工作:
配置idea-maven-webapp环境
配置好tomcat9
maven导入3.0+的servlet包(不用注解的话,用默认的2.0+也可以)
写demo:
1.先写一个类继承于httpservlet
可以通过注解@WebServlet来指定url
可以使用 * 这种通配符
“ /* ”会覆盖index.jsp,一般会用作ERROR页面
只匹配尾部的话,将“/”去掉,例如“*.a”
会匹配所有以“.a”结尾的地址
值得注意的是,准确地址的优先度比通配符要高,也就是如果有准确地址匹配上了,那么通配符所指向的页面不会出现。
_
2.重写doget和dopost方法
dopost调用父类的dopost即可
doget为我们要写的部分
_
3.response中可以设置编码以及页面的形式
这里设置utf-8,html
_
4.从response中获取输出流,字符流的writer以及字节流的outputsteam
(setWriter,getOutputStream)
通过输出流写信息到网页
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet("/hello")
public class HelloWorld extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
out.println("");
out.println("");
out.println("Hello World! ");
out.println("");
out.println("");
out.println("Hello World!你好,世界~
");
out.println("");
out.println("");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
早期的web应用主要是用于浏览静态页面,所以http服务器只需要向浏览器返回静态html,浏览器负责解析html,将结果呈现给用户。而如今,我们还希望通过一些交互操作获取一些动态结果,因此,也就需要一些扩展机制来让http服务器调用服务端程序。Servlet应运而生。
Servlet是sun公司推出的运行在服务端的java小程序,特别的是,它没有Main方法,因此需要将其部署到servlet容器中(Tomcat or Jetty),进行容器实例化并调用servlet。
浏览器接收到servlet后,并不知道要调用其中的哪个方法,用if-else这种高耦合的方法显然是不现实的,所以就出现了servlet接口,所有实现了这个接口的业务类都是servlet。我们知道有接口,一般都有抽象类,GenericServlet就是一个抽象类,我们可以通过它来扩展Servlet;且servlet经常在http环境下,所以又有一个继承了GenericServlet的HttpServlet抽象类。我们只需要重写两个方法:doGet和doPost。
Tomcat和Jetty就是一个Servlet容器,为了便于使用,它们也同样具有http服务器的功能,我们也叫它们“Web容器”。相比较于JBoss和WebLogic,它们算是轻量级的应用服务器。
Tomcat部署好后,在其examples中有一些servlet的demo,是很好的学习资料。
当http请求到达Tomcat后,Tomcat将http请求的数据字节流解析成一个request对象交给web应用去处理,处理完后得到一个response对象,Tomcat将其转换成http格式的响应数据并发送给浏览器。
http协议是无状态的,请求与请求之间是无关系的,为了让服务器知道请求的来源,出现了Cookie技术。Cookie是http报文的一个请求头,web应用可以将用户的标识信息存储到Cookie中,服务器通过读取Cookie来辨识用户。本质是一份存储在本地的文件,每次请求都需要传递它。
Cookie是明文存储的,为了安全,出现了Session技术,Session是服务器端开辟存储空间,来保存用户的状态,通过信息的Cookie中保存的Session ID找到对应的Session从而对Session进行操作,这不仅安全,而且减少了Cookie中存储信息的量。
Java中的Session是在web应用程序调用HttpServletRequest的getSession的时候,由web容器(Tomcat)创建的。Tomcat中有多种持久化Session的方式,一般会采用redis这种高性能高可用的存储方式。同时,Session还有过期时间,Tomcat会开启后台线程定期轮询,使过期的Session失效。
先来看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();
}
五个方法,最重要的是service方法,我们应该在这个方法里写业务逻辑。
它有两个参数——ServletRequest以及ServletResponse,前者封装请求信息,后者封装响应信息。它们两个是对通信协议的封装!!!我们能够通过它们来获取请求路径、cookie、http头、请求参数等,还有之前提过的session的创建获取。
init是用来初始化资源的,destroy是结束时用来释放资源的。
servletConfig是封装servlet的初始化参数,我们可以在web.xml中对Servlet的参数进行配置,然后通过servletConfig获取到。
当客户请求某个资源时,http服务器会用一个ServletRequest封装客户的请求信息,然后调用Servlet容器的service方法;Servlet容器拿到请求后,根据URL与Servlet的映射关系,找到对应的Servlet,(可以使用@WebServlet注解的方法,也可以在web.xml里配置servlet以及servlet-mapping)。如果Servlet还没有被加载,就用反射创建这个Servlet,并调用Servlet的init方法来完成初始化,接着调用Servlet的service方法来处理请求,把ServletResponse对象返回给http服务器,服务器再把响应发送给客户端。
一个web应用对应一个ServletContext,Servlet容器会在加载Web应用时,为每一个Web应用创建一个唯一的ServletContext来共享数据,它持有者web应用的初始化参数以及目录下的文件资源,还可以通过它来实现servlet请求的转发。
Filter:一般是用来限流或者根据国家的不同修改内容。在Web应用部署完成后,Servlet容器需要实例化Filter并把Filter链接成一个FilterChain。当请求进来的时候,获取第一个Filter并调用doFilter,doFilter会调用下一个Filter。Filter是干预过程的。
Listener:监听各种事件的发生,从而在发生前或发生后进行一些操作。Listener是基于状态的,任何行为改变到同一状态,会触发相同事件。
核心功能:
1.处理socket连接,将字节流转换成request与response
2.加载管理servlet并处理request请求
为了实现这两个功能,Tomcat设计了连接器与容器来做这两件事。
连接器将socket套接字转换成servletRequest提交给容器,
容器处理好请求后,返回servletResponse给连接器,
接着连接器再将servletResponse转换成套接字响应给用户。
Tomcat支持的I/O模型:非阻塞I/O NIO;异步I/ON IO2;Apache可移植运行库APR。
Tomcat支持的应用层协议:HTTP/1.1,HTTP/2,AJP。
Tomcat为了实现支持多种I/O模型以及应用层协议,一个容器可能对接多个连接器。
一个容器和与其对接的多个连接器的集合叫做Service组件,一个Tomcat(Server)中可以配置多个Service组件,
通过不同的端口号来访问部署的不同应用。
连接器的架构十分的巧妙。
1——ProtocalHandler
之前我们提过,tomcat支持多种I/O模型以及应用层协议,而连接器对容器屏蔽了这些协议和I/O模型的区别,传递给容器的都是一个ServletRequest。很明显,这是用了继承的特性来实现的。
ProtocalHandler就是实现这个需求的抽象类,它抽象出许多的I/O模型+协议的子类,以供开发者选择。
Protocal中有两个子组件——EndPoint和Processor,EndPoint传递socket给Processor,Processor将其解析成TomcatRequest发送给Adapter。
- EndPoint是通信监听的端口,负责Socket的接受以及发送,是对传输层(TCP/IP)的抽象,因此它是用来实现TCP/IP协议的。EndPoint也是一个接口,也有对应的抽象实现类,有两个重要的子组件——Acceptor与SocketProcessor。
_
Acceptor用来监听Socket的连接请求;SocketProcessor用来处理接收到的Socket请求,它实现了run方法(重写Runnable接口的),在run方法中调用了Processor组件,为了提高效率,将线程提交到线程池(执行器Executor)。- Processor是用来实现各类应用层协议的,它将Socket转换成TomcatRequest以及TomcatResponse。
连接器在架构的时候,为了连接Servlet以外的的东西,也就是降低与Servlet的耦合,使用了一个Adapter组件,它的功能是负责(tomcatRequest与tomcatResponse)和(ServletRequest与ServletResponse)之间的相互转化。
2——Adapter
连接器在架构的时候,为了降低连接器与Servlet的耦合,也就是连接Servlet以外的的东西,使用了一个Adapter组件,它的功能是负责(tomcatRequest与tomcatResponse)和(ServletRequest与ServletResponse)之间的相互转化。
但tomcatRequest与tomcatResponse并不是标准的ServletRequest与ServletResponse,于是架构师设计了一个CoyotoAdapter,使用了适配器设计模式,实现了它们之间的转换。
Tomcat中有四种容器:Engine,Host,Context,Wrapper。
Engine是引擎,用来管理多个Host;Host是站点,一个站点下可以部署多个Web应用;一个Context对应着一个Web应用,可以包含多个Servlet;一个Wrapper对应着一个Servlet,实现某一个小功能。
这四种容器是父子关系,都实现了Container接口,使用组合模式。
Container接口中封装了对父亲儿子的操作。
Tomcat中通过Mapper组件来完成定位。
Mapper组件中保存了容器组件与访问路径的映射关系,比如host里配置的域名、context里的web应用路径、以及wrapper里的servlet映射的路径。
举例:
假如有用户访问一个 URL,比如图中的http://user.shopping.com:8080/order/buy,Tomcat 如何将这个 URL 定位到一个 Servlet 呢?
首先,根据协议和端口号选定 Service 和 Engine。我们知道 Tomcat 的每个连接器都监听不同的端口,比如 Tomcat 默认的 HTTP 连接器监听 8080 端口、默认的 AJP 连接器监听 8009 端口。上面例子中的 URL 访问的是 8080 端口,因此这个请求会被 HTTP 连接器接收,而一个连接器是属于一个 Service 组件的,这样 Service 组件就确定了。我们还知道一个 Service组件里除了有多个连接器,还有一个容器组件,具体来说就是一个 Engine 容器,因此 Service 确定了也就意味着 Engine 也确定了。
然后,根据域名选定 Host。Service 和 Engine 确定后,Mapper 组件通过 URL 中的域名去查找相应的 Host 容器,具体找哪个host是在Request中确定的,Request中的信息是之前Mapper组件存储进去的。比如例子中的 URL 访问的域名是user.shopping.com,因此 Mapper 会找到 Host2 这个容器。
之后,根据 URL 路径找到 Context 组件。Host 确定以后,Mapper 根据 URL 的路径来匹配相应的 Web 应用的路径,比如例子中访问的是 /order,因此找到了 Context4 这个 Context 容器。
最后,根据 URL 路径找到 Wrapper(Servlet)。Context 确定后,Mapper 再根据 web.xml 中配置的 Servlet 映射路径来找到具体的Wrapper 和 Servlet。
值得注意的是,并不是只有Servlet才会处理请求,中间的每一个中间容器都会进行一些处理,为了满足这个需求,Tomcat使用了责任链Pipline-Valve设计模式。连接器Adapter会调用Engine中的第一个Valve,接着会顺着链调用,直到Basic,接着会调用下一个容器的第一个Valve。在Wrapper的Pipline被调用完成后,会创建并调用Filiter链,最后才会调用Servlet的service方法。
这里的Filter是和Tomcat紧耦合的,而之前讲的Filter是所有Web容器都可以用的拦截器。Tomcat的拦截器是Web容器级别,而Servlet的拦截器是单个Web应用级别,要注意它们之间的不同。