首先来看看,最初碰到这个概念时的困惑:
学SpringBoot的时候看到说它默认使用嵌入式Servlet 容器Tomcat, 然后看到有人问到Servlet 的原理是什么,以及有大佬的知乎回答说建议要**精通Servlet **,看来这两个名称相似的概念是有所区别的:
Servlet 与 Servlet 容器的关系有点像枪和子弹的关系,枪是为子弹而生,而子弹又让枪有了杀伤力。虽然它们是彼此依存的,但是又相互独立发展,这一切都是为了适应工业化生产的结果。从技术角度来说是为了解耦,通过标准化接口来相互协作。
来自: 许令波:《Servlet 工作原理解析》
我们常见的Servlet 容器便有:Tomcat,Jetty等。
再来看Servlet 原理:
Servlet 教程|菜鸟教程左边的目录可以看到一系列可以实现的功能:实现客户端和服务器的HTTP请求和响应,过滤器,异常处理,Cookit和Session, 包括数据库访问,文件上传,重定向,发送电子邮件等等。
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
可见,Servlet 在客户端和服务端间的交互通讯中起了重要作用,虽然现在基本上不会有人使用存粹的Servlet 来实现整个Web应用,不过它却是Java web开发技术的基础,所以很有必要了解,理解,掌握,乃至精通。
热身
在实现中Servlet 主要使用了javax.Servlet 和javax,Servlet .http两个包,写好的程序需要运行在带有支持Java Servlet 规范的解释器的Web服务器,即我们开头所说的Servlet 容器。
准备
平时用的是Jetty,所以在IDEA上重新装了Tomcat来体验:IntelliJ IDEA配置Tomcat(完整版教程)
第一个Servlet 程序
<servlet>
<servlet-name>HelloWorldservlet-name>
<servlet-class>HelloWorldservlet-class>
servlet>
<servlet-mapping>
<servlet-name>HelloWorldservlet-name>
<url-pattern>/HelloWorldurl-pattern>
servlet-mapping>
@WebServlet("/HelloWorld")
public class HelloWorld extends HttpServlet {
private String message;
......
}
获取表单数据
此处已经使用了HTTP请求,主要为了展示三个函数的用法,用来获取请求request中的参数:
HTTP请求和HTTP响应消息
先要知道请求的结构组成,这两种请求的结构相似:
状态行 | HTTP/1.1 200 OK |
---|---|
头部字段 | Content-Type: text/html Header2: … … HeaderN: … |
空格 | |
内容实体 |
...
…
|
编码功能实现包括:
1. 获取HTTP请求头部消息 2. 设置HTTP响应的头部消息 3. 设置HTTP响应的状态码 |
---|
Servlet 过滤器
有两个问题可以提出来:
过滤器的作用可以总结为以下两点,分别针对请求和响应
- 在客户端的请求访问后端资源之前,拦截这些请求。
- 在服务器的响应发送回客户端之前,处理这些响应。
用图来简单表示:
过滤器的作用可就广泛了:最常见的身份验证,消息加密,消息审核,数据压缩,图像转换等等。
1. 实现过滤器功能 2. 多个过滤器 |
---|
.xml里的配置也可以用注解实现(其实看起来也不是很简洁…):
@WebFilter(filterName = "LogFilter", urlPatterns = "/*", initParams = {
@WebInitParam(name = "Site", value = "菜鸟教程")
})
public class LogFilter implements Filter {
......
}
多个过滤器时,需要注意过滤器的执行顺序:
Filter的执行顺序与在web.xml配置文件中的配置顺序一致,一般把Filter配置在所有的Servlet之前。
不过如果是注解怎么确定顺序呢?
Servlet3.0之前使用web.xml配置按照mapping的顺序即先映射的先过滤;
Servlet3.0后使用注解则按照类名的自然顺序,即类名的字母顺序来排因为容器加载时按此顺序加载
异常处理
那么,Servlet 是怎么处理异常的呢?
比如如果搜索了不存在的页面,如果不进行异常处理,出现的是默认的404丑丑的界面,而通过异常处理,可以转向我们自己定制的404页面
本质上还是进行一个ErrorHandler的Servlet 的类定义,添加映射。然后再配置一下进行错误页面的定义,其中可以包含具体的状态码的处理( 如404 Not Found 未找到,或 403 Forbidden 禁止),或者具体的异常的处理(如ServletException 或 IOException),或者索性所有错误统一处理(java.lang.Throwable异常处理)
Cookie
Cookie的存放位置:客户端计算机的文本文件上。
一般使用Cookie的流程如下:
(存疑:如何防止使用Cookie伪造HTTP请求呢?)
编码功能实现包括:
1. 对表单提交后的请求,返回一个带cookies的响应,并在页面显示cookies消息 2. 发出一个页面请求,返回一个读取cookies的响应,并显示在页面中 3. 删除Cookie可以通过设置过期时间为0的方法实现: setMaxAge() |
---|
Session跟踪
Servlet Session跟踪可以使得服务器识别客户端,来保持一段会话。
个人理解就是用户在某网站里面有一系列操作(如点击各个链接或者做一系列功能/点击按钮)时,因为每次都要往服务器发送请求,有一些需要根据个人信息来判别,这里就需要服务器有能力唯一识别出这个用户。
听起来和Cookie有点像?使用Cookie也能实现,不过可能浏览器会禁止使用Cookie,所以这种方法不是很靠谱。
实际上有三种方式能可以让 Session 正常工作:
- 基于 URL Path Parameter,默认就支持
- 基于 Cookie,如果你没有修改 Context 容器个 cookies 标识的话,默认也是支持的
- 基于 SSL,默认不支持,只有 connector.getAttribute(“SSLEnabled”) 为 TRUE 时才支持
通过实现HttpServlet 接口,使得在用户跨多个页面发出请求时可以识别用户,也能存储有关的用户信息。
服务器需要在向客户端发送响应之前,调用 request.getSession()方法。
编码功能实现包括:
1. 实现一个记录本页面访问次数的计时器,通过获取request里的httpsession对象,设施它的属性来改变 |
---|
(存疑:实现里面貌似只是单页面的操作,怎么跨页面呢?)1.设置全局变量,可以控制Serlvet内的操作,init()里面初始化,用isNew()来判断是否为新用户 2.跨页面如何处理,毕竟此时访问的是不同的Serlvet?
数据库访问
此处就是使用了JDBC访问数据库,然后用doGet()返回了
重定向
两种方法:
需要了解的是:
Servlet的生命周期:
一些重要的类
和Servlet直接相关的类有三个:ServletConfig,ServletRequest,ServletResponse。
这三个类都是通过容器传递给Servlet的。
ServletConfig: 获取Servlet运行时需要的配置属性; Servlet在init的时候由容器传递过来。
ServletContext: 表示应用程序,Tomcat为每1个Web应用程序创建1个该实例,即启动时创建,服务器关闭时销毁。有什么作用呢? 可以在一个web项目中共享数据,配置web公共信息等,每个Servlet可以访问到它。
我们一般使用的HttpServlet是继承的GenericServlet类(该类实现了Servlet, ServletConfig接口)
值得一提的是该类有2个init()函数:第1个带参数的,是为了在类中全局设置ServletConfig的实例;第二是没有参数的,是为了继承该类后,用来重写初始化,实现我们自己的功能的;
ServletContextListener是一个接口,继承了EventListener类。
里面只有两个方法:
ServletContextListener在Spring中如何应用呢?
《JavaWeb——Servlet》中末尾有解析:
一般我们用这句代码可以显示地实例化一个Spring IOC容器
ApplicationContext applicationContext =new ClassPathXmlApplicationContext("***.xml");
ContextLoaderListener类实现了ServletContextListener接口。
- 当Servlet容器启动时,ServletContext对象被初始化.
- 然后Servlet容器调用web.xml中注册的监听器的public void contextInitialized(ServletContextEvent event)方法.
- 该方法中,调用了this.initWebApplicationContext(event.getServletContext())方法,在这个方法中实例化了Spring IOC容器。即ApplicationContext对象。
- 因此,当ServletContext创建时我们可以创建applicationContext对象,当ServletContext销毁时,我们可以销毁applicationContext对象。这样applicationContext就和ServletContext“共生死了”。
涉及Tomcat的部分
(这部分看了很多遍,暂时先粗略总结一下,参考: 许令波:《Servlet 工作原理解析》)
我们的Servlet是在Tomcat(Servlet容器)中运行的,所以不得不谈一下请求是如何由Tomcat接收,然后到达我们的程序,以及响应如何从Tomcat发出去的。
Tomcat中的容器:
启动过程:
什么要将 Servlet 包装成 StandardWrapper 而不直接是 Servlet 对象。这里 StandardWrapper 是 Tomcat 容器中的一部分,它具有容器的特征,而 Servlet 为了一个独立的 web 开发标准,不应该强耦合在 Tomcat 中。
到这里的时候,Servlet被包装成StandardWrapper并且作为子容器被添加到了Context中,相关的属性也被解析到了Context中,所以之前说过,Context容器直接管理运行Serlvet。
创建Servlet对象:(这里实在有点晕…)
初始化Servlet:
到这里就完成了Servlet运行之前的准备工作了,其中的步骤简化了很多细节的地方,主要梳理了一下主线。然后简要说明一下Servlet是如何被调用的:
Servlet工作过程:
这就是我们熟悉的处理部分了。
Servlet 的确已经能够帮我们完成所有的工作了,但是现在的 web 应用很少有直接将交互全部页面都用 servlet 来实现,而是采用更加高效的 MVC 框架来实现。这些 MVC 框架基本的原理都是将所有的请求都映射到一个 Servlet,然后去实现 service 方法,这个方法也就是 MVC 框架的入口。
整体将相关内容过了一遍,如果有新的体会,再继续更新。