前言
tomcat是一个web容器(作为服务器,处理动态资源),web容器就是一个servlet容器,而servlet容器就是实现了servletAPI规范的一组服务。
Servlet API规范中,定义了其生命周期的三个方法:init(),service(),destroy(),还有过滤器filter,监听器listener。这些servletAPI规范定义在javax.servlet-api包中,它是单独一个java包,需要下载才能使用。
而servlet容器实现了这些servlet API规范,负责servlet的创建、执行和销毁。
一、Servlet
上面说到servlet,那么什么是servlet呢?为什么需要servlet呢?
servlet就是一个java应用程序,实现了servletAPI规范的java应用程序就是一个servlet,主要是用来处理来自web浏览器或者其他HTTP客户端的请求,常常跟我们的业务程序或者我们的数据对接,相当于一个中间层的作用,动态地生成内容给服务器;要部署和运行servlet,需要使用web容器(servlet容器),web容器本质上就是与servlet交互的web服务器组件,web容器负责管理servlet的生命周期,将URL映射到特定的servlet(具体在web.xml中配置)并确保URL请求者具有正确的权限。如下图:
servlet应用程序主要的任务就是
- 读取Web服务器(如tomcat)发送过来的数据(表单数据、HTTP请求数据-cookie等)
- 处理接收到的数据,可能调用其他业务服务或者直接访问数据库,生成需要的响应结果
- 将指定格式的响应结果返回给Web服务器(文本文件、二进制文件、Excel、cookie参数等)。
如何创建这个servlet应用程序呢
- 首先,需要使用servletAPI提供的jar包,也就是遵循servlet规范来开发servlet应用程序,使用servletAPI包有两种方式:
- 1.使用JDK版本为SE标准版,则需要下载并依赖引入servlet-api包(具体方式可是通过maven的pom.xml依赖引入等)
- 2.使用JDK版本为EE企业版,则不需下载,直接引用rt.jar下的servlet-api包即可
- 然后,创建servlet应用程序,有三种方式:
- 1.创建一个servlet实现类,直接实现javax.servlet-api包下的Servlet接口,并重写实现接口的所有方法:
- init(ServletConfigservletConfig):servlet初始化时只执行一次。
- service(ServletRequestservletRequest,ServletResponseservletResponse):servlet处理请求时执行。
- getServletConfig():运行时,获取servlet初始化和启动的相关参数。
- getServletInfo():运行时,获取运行servlet应用程序的相关信息,如作者,版本等。
- destroy():servlet停止的时候执行。
- 2.创建一个子类,继承GenericServlet抽象类;该类也是实现类Servlet接口,只不过也实现了ServletConfig接口,并提供获取getServletName()、getServletContext()、getInitParameterNames()等方法,必须需要自己重写实现的方法就是service()方法,简化了开发Servlet,只关注我们自己的业务服务即可。
- 3.创建一个子类,继承HttpServlet抽象类;该类继承了GenericServlet抽象类,在其基础上进一步拓展HTTP的内容,但是不需要重写任何方法,HttpServlet类已经帮我们完成重写,我们可以选择覆盖它重写的方法。该类增加了在Servlet处理HTTP请求时根据方法划分的doGet(),doHead(),doPost(),doPut(),doDelete(),doOptions(),doTrace()等主要方法,在service()方法中根据HTTP请求方法来调用,这也就是该类叫HttpServlet的原因了。
- 再然后(可选),可以编写一些过滤器Filter,主要用于我们可以动态拦截HTTP请求及HTTP响应,原理就是Servlet容器将这些过滤器实例一并纳入Servlet实例的生命周期中,当Servlet实例在运行时,在web.xml中配置了过滤器,会在servlet实例执行service()方法之前先调用filter实例的doFilter()方法,doFilter()方法中的FilterChain对象确定是否继续执行servlet实例的service()方法,FilterChain对象是一个filter链,也有一个doFilter()方法,在上一个filter实例中调用了FilterChain.doFilter方法,下一个Filter实例的doFilter()方法才会执行,到了最后一个 Filter.doFilter 方法中调用的 FilterChain.doFilter 方法就会激活 Servlet实例的service 方法,如果在任何一个Filter.doFilter 方法中没有调用FilterChain.doFilter方法,则不会调用servlet的service()方法,这就起到了过滤的作用;总的来说就是Servlet容器将多个Filter实例组合成一个Filter链,来控制对servlet实例的过滤作用。具体使用:
-
- 创建一个子类,实现servlet-api下的Filter接口,并重写其中的三个方法:过滤器实例化时只执行一次的初始方法init(),过滤器实例运行时每次请求都会执行的过滤方法doFilter(),过滤器实例从Servlet容器移除之前执行的销毁方法destroy()。或者继承GenericFilter,HttpFilter抽象类,与编写servlet实例一样。
- 在Servlet容器的web.xml中配置该过滤器映射,使用<filter>标签指定过滤器,使用<filter-mapping>拦截请求强行执行指定的过滤器doFilter()方法。
- 若使用多个过滤器,则实现多个Filter子类,在web.xml中配置多个过滤器实例,若存在过滤条件相同的多个过滤器,其执行顺序就按在web.xml配置<filter-mapping>的顺序先后执行。
- 最后,就是处理这些HTTP请求了,我们处理的业务逻辑都是分布在service方法里面,HTTP服务器发送的请求会帮我们创建两个重要的实例:ServletRequest(经常使用其子类HttpServletRequest)和ServletResponse(经常使用其子类HttpServletResponse)
-
- 1.先利用HttpServletRequest实例调用其方法(具体方法参考该接口的方法),获取HTTP请求头信息及body内容
- 2.进一步调用自己的业务逻辑去处理这些请求
- 3.最后将处理的结果数据,封装到HttpServletResponse实例中(具体方法参考该接口的方法)并想用返回。
-
- 最最后(可选),Servlet的异常处理,当抛出异常的时候,我们可以自定义一个Servlet程序去单独处理这些异常并在web.xml中配置使用即可。
注意:上面中需要在web.xml中配置的Servlet,Filter,initParam等,都可以在servlet-api下annotation包中找到相应的注解,可以直接使用之间完成在web.xml的配置,省去很多麻烦。
如何运行Servlet应用程序(生命周期)
上面已经创建好servlet应用程序了,根据上面应用图,我们需要一个HTTP服务器,例如tomcat服务器,这个tomcat的使用后面再讲,这里只需要注意一点就是,如何你的运行环境使用的JDK版本是SE标准版本,需要将javax.servlet-api包配置到系统的CLASSPATH环境变量中,不然你的servlet应用程序将无法编译,如果使用的是JDK版本是EE企业版本则不需要,该版本包含了servlet的包。运行时的示例图(生命周期):
注意:servlet实例的生命周期是由Servlet容器控制,并不是完成一次请求处理的线程来控制的;init()方法在servlet实例创建的时候只完成一次,当处理HTTP请求的线程完成任务时,只是service()方法执行完了,destroy()方法并不会执行,由Servlet容器控制。
具体流程如下:
- 用户请求访问指定URL
- 浏览器为此URL生成HTTP请求
- 浏览器将该HTTP请求发送到相应的web服务器
- HTTP请求由web服务器接收并转发到servlet容器
- Servlet容器创建一个线程处理该HTTP请求
- servlet容器将此HTTP请求解析并映射到特定的servlet
- servlet被动态检索并加载到容器的地址空间中,开始创建servlet实例
- 容器调用servlet的init()方法
- 仅当servlet实例首次被创建并加载到内存时,才会调用此方法
- 可以将自定义的初始化参数传递给该方法,以便可以自定义servlet配置
- 容器调用Servlet的service()方法
- 调用此方法处理HTTP请求
- servlet可以读取HTTP请求中提供的数据
- servlet还可以为客户端指定HTTP响应
- 生成的servlet实例会继续保留在容器的地址空间中,并用于处理从客户端收到的任何其他HTTP请求
- 后面的每次处理HTTP请求都是直接调用service()方法
- 容器可以基于算法,如servlet实例空闲过多,会将一些servlet实例从内存中卸载
- 容器会调用servlet实例的destroy()方法,servlet实例被清理前的方法处理。
- 最后该servlet实例会被GC清理。
Servlet的应用发展历史
- 浏览器的B/S模式飞速发展,出现了名为applet的浏览器java插件
- 由于浏览器的限制,applet放在浏览器上的java插件也收到诸多限制于不便
- 于是servlet出现了,被称为service端的applet,因此称之为servlet
- 后来SUN公司发现Servlet编程非常繁琐,存在大量冗余代码,且在Servlet中嵌入前端代码,导致前端页面风格实现非常苦难
- 之后,SUN借鉴了微软的ASP,引入JSP,前端jsp页面中嵌入后端代码,后端开发人员需要在前端页面中写servlet代码,出现了JSP时代
- 后来Servlet引入了MVC思想,诞生了JSP+servlet+javaBean的模式
- JSP(V):将后端代码封装在标签中,使用大量的标签,JSP只用来写前端代码而不要有后端代码
- Servlet(C):Servlet完成Controller的功能再加上部分代码逻辑
- JavaBean(M):Servlet将数据发送给Model,对model进行封装逻辑处理,最后将Model数据加载渲染到JSP上。
- 之后,出现了一些MVC框架:Struts2,SpringMVC等
- 最后基本就只剩下Spring MVC框架稳定下来,还有继续使用。
- 最最后Springboot等框架直接将servlet给雪藏了,直接内嵌Servlet容器(如Tomcat,jetty,undertow),只需开发SpringBoot应用即可,就是今天所说的微服务。
- 因此,总的来说,Servlet虽然现在都“看不到”其曾经的身影了,但是还是一直被使用着,servlet是web开发基础的基础,只是被各大框架给封装“雪藏”了。