Servlet全面学习:
HTTP协议:
HTTP 协议(Hypertext Transfer Protocol, 超⽂本传输协议),是⼀个客户端请求和响应的标准协议,这个协议详细规定了浏览器和万维⽹服务器之间互相通信的规则。⽤户输⼊地址和端⼝号之后就可以从服务器上取得所需要的⽹⻚信息。通信规则规定了客户端发送给服务器的内容格式,也规定了服务器发送给客户端的内容格式。客户端发送给服务器的格式叫"请求协议";服务器发送给客户端的格式叫"响应协议"。
浏览器中的书写格式:
服务器端资源需要通过浏览器进⾏,此时由浏览器将我们给出的请求解析为满⾜ HTTP 协议的格式并发出。我们发出的请求格式需要按照浏览器规定的格式来书写,在浏览器中书写格式如下:
当浏览器获取到信息以后,按照特定格式解析并发送即可。接收到服务器端给出的响应时,也按照HTTP 协议进⾏解析获取到各个数据,最后按照特定格式展示给⽤户。
HTTP协议的特点:
1. ⽀持客户/服务器模式。
2. 简单快速:客户向服务器请求服务时,只需传送请求⽅法和路径。请求⽅法常⽤的 有 GET、POST。每种⽅法规定了客户与服务器联系的类型不同。由于 HTTP 协议简单,使得HTTP服务器的程序规模⼩,因⽽通信速度很快。
3. 灵活:HTTP 允许传输任意类型的数据对象。传输的类型由Content-Type加以标记。
4. ⽆连接:⽆连接是表示每次连接只处理⼀个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采⽤这种⽅式可以节省传输时间。HTTP1.1 版本后⽀持可持续连接。通过这种连接,就有可能在建⽴⼀个 TCP 连接后,发送请求并得到回应,然后发送更多的请求并得到更多的回应.通过把建⽴和释放 TCP 连接的开销分摊到多个请求上,则对于每个请求⽽⾔,由于 TCP ⽽造成的相对开销被⼤⼤地降低了。⽽且, 还可以发送流⽔线请求,也就是说在发送请求 1 之后的回应到来之前就可以发送请求 2.也可以认为,⼀次连接发送多个请求,由客户机确认是否关闭连接,⽽服务器会认为这些请求分别来⾃不同的客户端。
5. ⽆状态:HTTP 协议是⽆状态协议。⽆状态是指协议对于事务处理没有记忆能⼒。缺少状态意味着如果后续处理需要前⾯的信息,则它必须重传,这样可能导致每次连接传送 的数据量增⼤。另⼀⽅⾯,在服务器不需要先前信息时它的应答就较快。
HTTP之URL:
HTTP(超⽂本传输协议)是⼀个基于请求与响应模式的、应⽤层的协议,常基于 TCP 的连接⽅式,绝⼤多数的 Web 开发,都是构建在 HTTP 协议之上的 Web 应⽤。HTTP URL (URL 是⼀种特殊类型的 URI,包含了⽤于查找某个资源的⾜够的信息)的格式 如下:
http://host[:port]/[abc_path]
http://IP(主机名/域名):端⼝/访问的资源路径
http 表示要通过 HTTP 协议来定位⽹络资源;
host 表示合法的 Internet 主机域名或 者 IP 地址;
port 指定⼀个端⼝号,为空则使⽤缺省端⼝ 80;
abs_path 指定请求资源的 URI; 如果 URL 中没有给出 abs_path,那么当它作为请求 URI 时,必须以“/”的形式给出,通常 这个⼯作浏览器⾃动帮我们完成。
HTTP请求:
HTTP 请求由三部分组成,分别是:请求⾏、请求头、请求正⽂。
通过chrome浏览器, F12 —> Network查看。
- Get 请求(没有请求体)
- Post 请求
HTTP响应:
在接收和解释请求消息后,服务器返回⼀个 HTTP 响应消息。HTTP 响应也是由三个部分组成,分别是:状态⾏、消息报头、响应正⽂。
消息头:
HTTP 消息由客户端到服务器的请求和服务器到客户端的响应组成。请求消息和响应消息都是由开始⾏(对于请求消息,开始⾏就是请求⾏,对于响应消息,开始⾏就是状态⾏), 消息报头(可选),空⾏(只有 CRLF 的⾏),消息正⽂(可选)组成。
每⼀个报头域都是由 名字+":"+空格+值 组成,消息报头域的名字是⼤⼩写⽆关的。
请求头
请求报头允许客户端向服务器端传递请求的附加信息以及客户端⾃身的信息。
Referer:该请求头指明请求从哪⾥来 。
如果是地址栏中输⼊地址访问的都没有该请求头 地址栏输⼊地址,通过请求可以看到,此时多了⼀个 Referer 的请求头,并且后⾯的值 为该请求从哪⾥发出。⽐如:百度竞价,只能从百度来的才有效果,否则不算;通常⽤来做统计⼯作、 防盗链。
响应头:
响应报头允许服务器传递不能放在状态⾏中的附加响应信息,以及关于服务器的信息和 对 Request-URI 所标识的资源进⾏下⼀步访问的信息。
Location:Location响应报头域⽤于重定向接受者到⼀个新的位置。
Location响应报头域,常⽤在更换域名的时候。
response.sendRedirect("http://www.baidu.com");
Refresh:⾃动跳转(单位是秒),可以在⻚⾯通过meta标签实现,也可在后台实现。
<meta http-equiv="refresh" content="3;url=http://www.baidu.com">
Tomcat服务器:
什么是tomcat:
Tomcat 是⼀个符合 JavaEE WEB 标准的最⼩的 WEB 容器,所有的 JSP 程序⼀定要有 WEB 容器的⽀持才能运⾏,⽽且在给定的 WEB 容器⾥⾯都会⽀持事务处理操作。
Tomcat 是由 Apache 提供的(www.apache.org)提供的可以⽤安装版和解压版,安装版可以在服务中出现⼀个 Tomcat 的服务,免安装没有,开发中使⽤免安装版。 Tomcat 简单的说就是⼀个运⾏Java 的⽹络服务器,底层是 Socket 的⼀个程序,它也是 JSP 和 Servlet 的⼀个容器。 Tomcat 是Apache 软件基⾦会(Apache Software Foundation)的 Jakarta 项⽬中的⼀个核⼼项⽬,由Apache、Sun和其他⼀些公司及个⼈共同开发⽽成。
由于有了 Sun 的参与和⽀持,最新的 Servlet 和 JSP 规范总是能在 Tomcat 中得到体现。因为Tomcat 技术先进、性能稳定,⽽且免费,因⽽深受 Java 爱好者的喜爱并得到了部分软件开发商的认可,成为⽬前⽐较流⾏的 Web 应⽤服务器。
Tomcat 服务器是⼀个免费的开放源代码的 Web 应⽤服务器,属于轻量级应⽤服务器, 在中⼩型系统和并发访问⽤户不是很多的场合下被普遍使⽤,是开发和调试 JSP 程序的⾸选。 对于⼀个初学者来说,可以这样认为,当在⼀台机器上配置好 Apache 服务器,可利⽤它响应 HTML(标准通⽤标记语⾔下的⼀个应⽤)⻚⾯的访问请求。实际上 Tomcat 部分是 Apache 服务器的扩展,但它是独⽴运⾏的,所以当你运⾏ tomcat 时,它实际上作为⼀个与 Apache 独⽴的进程单独运⾏的。
当配置正确时,Apache 为 HTML ⻚⾯服务,⽽ Tomcat 实际上是在运⾏ JSP ⻚⾯和 Servlet。另外,Tomcat 和 IIS 等 Web 服务器⼀样,具有处理 HTML ⻚⾯的功能,另外它还是 ⼀个 Servlet 和 JSP 容器,独⽴的 Servlet 容器是 Tomcat 的默认模式。不过,Tomcat 处理静态 HTML 的能⼒不如 Apache服务器。⽬前 Tomcat 最新版本为 9.0。
安装Tomcat:
运⾏ Tomcat 需要 JDK 的⽀持【Tomcat 会通过 JAVA_HOME 找到所需要的 JDK】。 安装就是解压缩过程。启动 Tomcat,能访问则算安装好了
Tomcat目录结构:
-
bin:启动和关闭 tomcat 的 bat ⽂件
-
conf:配置⽂件server.xml 该⽂件⽤于配置 server 相关的信息,⽐如 tomcat 启动的端⼝号,配置主机(Host) ;web.xml ⽂件配置与 web 应⽤(web 应⽤相当于⼀个 web站点);tomcat-user.xml 配置⽤户名密码和相关权限
-
lib:该⽬录放置运⾏ tomcat 运⾏需要的 jar 包
-
logs:存放⽇志,当我们需要查看⽇志的时候,可以查询信息
-
webapps:放置我们的 web 应⽤
-
work ⼯作⽬录:该⽬录⽤于存放 jsp 被访问后⽣成对应的 server ⽂件和.class ⽂件
Servlet的实现:
Servlet 是 Server 与 Applet 的缩写,是服务端⼩程序的意思。使⽤ Java 语⾔编写的服务器端程序,可以像⽣成动态的 WEB ⻚,Servlet 主要运⾏在服务器端,并由服务器调⽤执⾏, 是⼀种按照 Servlet标准来开发的类。 是 SUN 公司提供的⼀⻔⽤于开发动态 Web 资源的技术。(⾔外之意:要实现 web开发,需要实现 Servlet 标准)
Servlet 本质上也是 Java 类,但要遵循 Servlet 规范进⾏编写,没有 main()⽅法,它的创建、使⽤、销毁都由 Servlet 容器进⾏管理(如 Tomcat)。(⾔外之意:写⾃⼰的类,不⽤写 main ⽅法,别⼈⾃动调⽤)
Servlet 是和 HTTP 协议是紧密联系的,其可以处理 HTTP 协议相关的所有内容。这也是 Servlet 应⽤⼴泛的原因之⼀。
提供了 Servlet 功能的服务器,叫做 Servlet 容器,其常⻅容器有很多,如 Tomcat, Jetty, WebLogic Server, WebSphere, JBoss 等等。
创建web项目:
servlet的实现:
新建类:
-
点击 “src” —> “new” —> “package”,创建⼀个⽂件包
-
在包下⾯创建 Java 类⽂件,点击包名 —> “New” —> “Java Class”
-
创建⼀个普通的 Java 类
实现servlet规范:
实现 Servlet 规范,即继承 HttpServlet 类,并到如响应的包,该类中已经完成了通信的规则,我们只需要进⾏业务的实现即可。
重写service方法:
满⾜ Servlet 规范只是让我们的类能够满⾜接收请求的要求,接收到请求后需要对请求进⾏分析,以及进⾏业务逻辑处理,计算出结果,则需要添加代码,在规范中有⼀个叫做 service的⽅法,专⻔⽤来做请求处理的操作,业务代码则可以写在该⽅法中。
设置注解:
在完成好了⼀切代码的编写后,还需要向服务器说明,特定请求对应特定资源。
开发servlet项⽬,使⽤@WebServlet将⼀个继承于javax.servlet.http.HttpServlet 的类定义为Servlet组件。在Servlet3.0中 , 可以使⽤@WebServlet注解将⼀个继承于javax.servlet.http.HttpServlet的类标注为可以处理⽤户请求的 Servlet。
⽤注解配置 Servlet
@WebServlet(name="Servlet01",value="/ser01")
@WebServlet(name="Servlet01",urlPatterns = "/ser01")
也可以配置多个访问路径
@WebServlet(name="Servlet01",value={"/ser01",'/ser001'})
@WebServlet(name="Servlet01",urlPatterns={"/ser01",'/ser001'})
发布项⽬并启动服务:
到此,需要编写和配置的地⽅已经完成,项⽬已经完整了,但是如果需要外界能够访问, 还需要将项⽬发布到服务器上并运⾏服务器。
访问并查看结果:
在项⽬正确发布到服务器上之后,⽤户即可通过浏览器访问该项⽬中的资源。注意 url 的 格式正确,tomcat 的端⼝为 8080。
浏览器访问地址:http://localhost:8080/s01/ser01
Servlet的⼯作流程:
-
通过请求头获知浏览器访问的是哪个主机
-
再通过请求⾏获取访问的是哪个⼀个web应⽤
-
再通过请求⾏中的请求路径获知访问的是哪个资源
-
通过获取的资源路径在配置中匹配到真实的路径,
-
服务器会创建servlet对象,(如果是第⼀次访问时,创建servlet实例,并调⽤init⽅法进⾏初始化操作)
-
调⽤service(request, response)⽅法来处理请求和响应的操作
-
调⽤service完毕后返回服务器 由服务器讲response缓冲区的数据取出,以http响应的格式发送给浏览器
Servlet的⽣命周期:
Servlet没有 main()⽅法,不能独⽴运⾏,它的运⾏完全由 Servlet 引擎来控制和调度。 所谓⽣命周期,指的是 servlet 容器何时创建 servlet 实例、何时调⽤其⽅法进⾏请求的处理、 何时并销毁其实例的整个过程。
实例和初始化时机
当请求到达容器时,容器查找该 servlet 对象是否存在,如果不存在,则会创建实例并进⾏初始化。
就绪/调⽤/服务阶段
有请求到达容器,容器调⽤ servlet 对象的 service()⽅法,处理请求的⽅法在整个⽣命周期中可以被多次调⽤; HttpServlet 的 service()⽅法,会依据请求⽅式来调⽤ doGet()或者 doPost()⽅法。但是, 这两个 do ⽅法默认情况下,会抛出异常,需要⼦类去 override。
销毁时机
当容器关闭时(应⽤程序停⽌时),会将程序中的 Servlet 实例进⾏销毁。
上述的⽣命周期可以通过 Servlet 中的⽣命周期⽅法来观察。在 Servlet 中有三个⽣命周 期⽅法,不由⽤户⼿动调⽤,⽽是在特定的时机有容器⾃动调⽤,观察这三个⽣命周期⽅法 即可观察到Servlet 的⽣命周期。
init ⽅法,在 Servlet 实例创建之后执⾏(证明该 Servlet 有实例创建了)
service ⽅法,每次有请求到达某个 Servlet ⽅法时执⾏,⽤来处理请求(证明该Servlet 进⾏服务了)
destroy ⽅法,Servlet 实例销毁时执⾏(证明该 Servlet 的实例被销毁了)
Servlet 的⽣命周期,简单的概括这就分为四步:servlet 类加载–>实例化–>服务–>销毁。
下⾯我们描述⼀下 Tomcat 与 Servlet 是如何⼯作的,看看下⾯的时序图:
-
Web Client 向 Servlet 容器(Tomcat)发出 Http 请求
-
Servlet 容器接收 Web Client 的请求
-
Servlet 容器创建⼀个 HttpServletRequest 对象,将 Web Client 请求的信息封装到这个对象 中
-
Servlet 容器创建⼀个 HttpServletResponse 对象
-
Servlet 容器调HttpServlet 对象service ⽅法,把 Request 与 Response 作为参数,传给HttpServlet
-
HttpServlet 调⽤ HttpServletRequest 对象的有关⽅法,获取 Http 请求信息
-
HttpServlet 调⽤ HttpServletResponse 对象的有关⽅法,⽣成响应数据
-
Servlet 容器把 HttpServlet 的响应结果传给 Web Client
HttpServletRequest对象:
HttpServletRequest 对象:主要作⽤是⽤来接收客户端发送过来的请求信息,例如:请求的参数,发送的头信息等都属于客户端发来的信息,service()⽅法中形参接收的是 HttpServletRequest 接⼝的实例化对象,表示该对象主要应⽤在 HTTP 协议上,该对象是由 Tomcat 封装好传递过来。
HttpServletRequest 是 ServletRequest 的⼦接⼝,ServletRequest 只有⼀个⼦接⼝,就是HttpServletRequest。既然只有⼀个⼦接⼝为什么不将两个接⼝合并为⼀个?
从⻓远上讲:现在主要⽤的协议是 HTTP 协议,但以后可能出现更多新的协议。若以后想要⽀持这种新协议,只需要直接继承 ServletRequest 接⼝就⾏了。
在 HttpServletRequest 接⼝中,定义的⽅法很多,但都是围绕接收客户端参数的。但是怎么拿到该对象呢?不需要,直接在 Service ⽅法中由容器传⼊过来,⽽我们需要做的就是取出对象中的数据,进⾏分析、处理。
接收请求:
方法:
获取请求参数:
请求乱码问题:
由于现在的 request 属于接收客户端的参数,所以必然有其默认的语⾔编码,主要是由于在解析过程中默认使⽤的编码⽅式为 ISO-8859-1(此编码不⽀持中⽂),所以解析时⼀定会出现乱码。要想解决这种乱码问题,需要设置 request 中的编码⽅式,告诉服务器以何种⽅式来解析数据。或者在接收到乱码数据以后,再通过相应的编码格式还原。
⽅式⼀:
request.setCharacterEncoding("UTF-8");
这种⽅式只针对 POST 有效(必须在接收所有的数据之前设定)
⽅式⼆:
new String(request.getParameter(name).getBytes("ISO-8859-1"),"UTF-8");
借助了String 对象的⽅法,该种⽅式对任何请求有效,是通⽤的。
Tomcat8起,以后的GET⽅式请求是不会出现乱码的。
请求转发:
请求转发,是⼀种服务器的⾏为,当客户端请求到达后,服务器进⾏转发,此时会将请求对象进⾏保存,地址栏中的 URL 地址不会改变,得到响应后,服务器端再将响应发送给客户端,从始⾄终只有⼀个请求发出。
实现⽅式如下,达到多个资源协同响应的效果。
request.getRequestDispatcher(url).forward(request,response);
request作用域:
通过该对象可以在⼀个请求中传递数据,作⽤范围:在⼀次请求中有效,即服务器跳转有效。
request 域对象中的数据在⼀次请求中有效,则经过请求转发,request 域中的数据依然存在,则在请求转发的过程中可以通过 request 来传输/共享数据。
HttpServletResponse**对象:
Web服务器收到客户端的http请求,会针对每⼀次请求,分别创建⼀个⽤于代表请求的 request 对象和代表响应的 response 对象。
request 和 response 对象代表请求和响应:获取客户端数据,需要通过 request 对象;向客户端输出数据,需要通过 response 对象。
HttpServletResponse 的主要功能⽤于服务器对客户端的请求进⾏响应,将 Web 服务器处理后的结果返回给客户端。service()⽅法中形参接收的是 HttpServletResponse 接⼝的实例化对象,这个对象中封装了向客户端发送数据、发送响应头,发送响应状态码的⽅法。
响应数据:
接收到客户端请求后,可以通过 HttpServletResponse 对象直接进⾏响应,响应时需要获取输出流。
有两种形式:
getWriter() 获取字符流**(只能响应回字符)**
getOutputStream() 获取字节流**(能响应⼀切数据)**
响应回的数据到客户端被浏览器解析。
注意:两者不能同时使⽤。
响应乱码问题:
在响应中,如果我们响应的内容中含有中⽂,则有可能出现乱码。这是因为服务器响应的数据也会经过⽹络传输,服务器端有⼀种编码⽅式,在客户端也存在⼀种编码⽅式,当两端使⽤的编码⽅式不同时则出现乱码。
**getWriter()**的字符乱码
对于 getWriter()获取到的字符流,响应中⽂必定出乱码,由于服务器端在进⾏编码时默认会使⽤ISO-8859-1 格式的编码,该编码⽅式并不⽀持中⽂。
要解决该种乱码只能在服务器端告知服务器使⽤⼀种能够⽀持中⽂的编码格式,⽐如我们通常⽤的"UTF-8"。
response.setCharacterEncoding("UTF-8");
此时还只完成了⼀半的⼯作,要保证数据正确显示,还需要指定客户端的解码⽅式。
response.setHeader("content-type", "text/html;charset=UTF-8")
两端指定编码后,乱码就解决了。⼀句话:保证发送端和接收端的编码⼀致
以上两端编码的指定也可以使⽤⼀句替代,同时指定服务器和客户端
response.setContentType("text/html;charset=UTF-8");
**getOutputStream()**字节乱码:
对于 getOutputStream()⽅式获取到的字节流,响应中⽂时,由于本身就是传输的字节, 所以此时可能出现乱码,也可能正确显示。当服务器端给的字节恰好和客户端使⽤的编码⽅式⼀致时则⽂本正确显示,否则出现乱码。⽆论如何我们都应该准确掌握服务器和客户端使⽤的是那种编码格式,以确保数据正确显示。
response.setHeader("content-type","text/html;charset=UTF-8");
ServletOutputStream out = response.getOutputStream();
response.setHeader("content-type","text/html;charset=UTF-8");
out.write("<h2>你好</h2>".getBytes("UTF-8"));
// 设置客户端与服务端的编码
response.setContentType("text/html;charset=UTF-8");
总结:要想解决响应的乱码,只需要保证使⽤⽀持中⽂的编码格式。并且保证服务器端 和客户端使⽤相同的编码⽅式即可。
重定向:
重定向是⼀种服务器指导,客户端的⾏为。客户端发出第⼀个请求,被服务器接收处理后,服务器会进⾏响应,在响应的同时,服务器会给客户端⼀个新的地址(下次请求的地址response.sendRedirect(url);),当客户端接收到响应后,会⽴刻、⻢上、⾃动根据服务器给的新地址发起第⼆个请求,服务器接收请求并作出响应,重定向完成。
从描述中可以看出重定向当中有两个请求存在,并且属于客户端⾏为。
请求转发与重定向的区别:
请求转发和重定向⽐较:
Cookie对象:
Cookie是浏览器提供的⼀种技术,通过服务器的程序能将⼀些只须保存在客户端,或者在客户端进行处理的数据,放在本地的计算机上,不需要通过网络传输,因而提高网页处理的效率,并且能够减少服务器的负载,但是由于 Cookie 是服务器端保存在客户端的信息, 所以其安全性也是很差的。例如常见的记住密码则可以通过 Cookie 来实现。
有⼀个专⻔操作Cookie的类 javax.servlet.http.Cookie。随着服务器端的响应发送给客户端,保存在浏览器。当下次再访问服务器时把Cookie再带回服务器。
Cookie 的格式:键值对⽤“=”链接,多个键值对间通过“;”隔开。
Cookie的创建的发送:
通过 new Cookie(“key”,“value”);来创建⼀个 Cookie 对象,要想将 Cookie 随响应发送到客户端,需要先添加到 response 对象中,response.addCookie(cookie);此时该 cookie 对象则随着响应发送⾄了客户端。在浏览器上可以看⻅。
// 创建Cookie对象
Cookie cookie = new Cookie("uname","zhangsan");
// 发送Cookie对象
response.addCookie(cookie);
Cookie的获取:
在服务器端只提供了⼀个 getCookies()的⽅法⽤来获取客户端回传的所有 cookie 组成的⼀个数组,如果需要获取单个 cookie 则需要通过遍历,getName()获取 Cookie 的名称,getValue()获取 Cookie的值。
// 获取Cookie数组
Cookie[] cookies = request.getCookies();
// 判断数组是否为空
if (cookies != null && cookies.length > 0) {
// 遍历Cookie数组
for (Cookie cookie : cookies){
System.out.println(cookie.getName());
System.out.println(cookie.getValue());
}
}
Cookie设置到期时间:
除了 Cookie 的名称和内容外,我们还需要关⼼⼀个信息,到期时间,到期时间⽤来指定该 cookie 何时失效。默认为当前浏览器关闭即失效。我们可以⼿动设定 cookie 的有效时间(通过到期时间计算),通过 setMaxAge(int time);⽅法设定 cookie 的最⼤有效时间,以秒为单位。
到期时间的取值
负整数
若为负数,表示不存储该 cookie。
cookie 的 maxAge 属性的默认值就是-1,表示只在浏览器内存中存活,⼀旦关闭浏览器窗⼝,那么 cookie 就会消失。
正整数
若⼤于 0 的整数,表示存储的秒数。
表示 cookie 对象可存活指定的秒数。当⽣命⼤于 0 时,浏览器会把 Cookie 保存到硬盘上,就算关闭浏览器,就算重启客户端电脑,cookie 也会存活相应的时间。
零
若为 0,表示删除该 cookie。
cookie ⽣命等于 0 是⼀个特殊的值,它表示 cookie 被作废!也就是说,如果原来浏览器已经保存了这个 Cookie,那么可以通过 Cookie 的 setMaxAge(0)来删除这个 Cookie。 ⽆论是在浏览器内存中,还是在客户端硬盘上都会删除这个 Cookie。
设置Cookie对象指定时间后失效
// 创建Cookie对象
Cookie cookie = new Cookie("uname","zhangsan");
// 设置Cookie 3天后失效
cookie.setMaxAge(3 * 24 * 60 * 60);
// 发送Cookie对象
response.addCookie(cookie);
Cookie的注意点:
-
Cookie保存在当前浏览器中。
在⼀般的站点中常常有记住⽤户名这样⼀个操作,该操作只是将信息保存在本机上,换电脑以后这些信息就⽆效了。⽽且 cookie 还不能跨浏览器。
-
Cookie存中⽂问题
Cookie 中不能出现中⽂,如果有中⽂则通过 URLEncoder.encode()来进⾏编码,获取时通过URLDecoder.decode()来进⾏解码。
String name = "姓名";
String value = "张三";
// 通过 URLEncoder.encode()来进⾏编码
name = URLEncoder.encode(name);
value = URLEncoder.encode(value);
// 创建Cookie对象
Cookie cookie = new Cookie(name,value);
// 发送Cookie对象
response.addCookie(cookie);
// 获取时通过 URLDecoder.decode()来进⾏解码
URLDecoder.decode(cookie.getName());
URLDecoder.decode(cookie.getValue());
-
同名Cookie问题
如果服务器端发送重复的Cookie那么会覆盖原有的Cookie。
-
浏览器存放Cookie的数量
不同的浏览器对Cookie也有限定,Cookie的存储有是上限的。Cookie是存储在客户端(浏览器)的,⽽且⼀般是由服务器端创建和设定。后期结合Session来实现回话跟踪。
Cookie的路径:
Cookie的setPath设置cookie的路径,这个路径直接决定服务器的请求是否会从浏览器中加载某些cookie。
情景⼀:当前服务器下任何项⽬的任意资源都可获取Cookie对象
/* 当前项⽬路径为:s01 */
Cookie cookie = new Cookie("xxx","XXX");
// 设置路径为"/",表示在当前服务器下任何项⽬都可访问到Cookie对象
cookie.setPath("/");
response.addCookie(cookie);
情景⼆:当前项⽬下的资源可获取Cookie对象 (默认不设置Cookie的path)
/* 当前项⽬路径为:s01 */
Cookie cookie = new Cookie("xxx","XXX");
// 设置路径为"/s01",表示在当前项⽬下任何项⽬都可访问到Cookie对象
cookie.setPath("/s01"); // 默认情况,可不设置path的值
response.addCookie(cookie);
情景三:指定项⽬下的资源可获取Cookie对象
/* 当前项⽬路径为:s01 */
Cookie cookie = new Cookie("xxx","XXX");
// 设置路径为"/s02",表示在s02项⽬下才可访问到Cookie对象
cookie.setPath("/s02"); // 只能在s02项⽬下获取Cookie,就算cookie是s01产⽣的,s01也不
能获取它
response.addCookie(cookie);
情景四:指定⽬录下的资源可获取Cookie对象
/* 当前项⽬路径为:s01 */
Cookie cookie = new Cookie("xxx","XXX");
// 设置路径为"/s01/cook",表示在s02/cook⽬录下才可访问到Cookie对象
cookie.setPath("/s01/cook");
response.addCookie(cookie);
如果我们设置path,如果当前访问的路径包含了cookie的路径(当前访问路径在cookie路径基础上要比cookie的范围小)cookie就会加载到request对象之中。
HttpSession对象:
HttpSession对象是 javax.servlet.http.HttpSession 的实例,该接⼝并不像 HttpServletRequest 或HttpServletResponse 还存在⼀个⽗接⼝,该接口只是⼀个纯粹的接⼝。这因为 session 本身就属于HTTP 协议的范畴。
对于服务器⽽⾔,每⼀个连接到它的客户端都是⼀个 session,servlet 容器使⽤此接⼝创建 HTTP 客户端和 HTTP 服务器之间的会话。会话将保留指定的时间段,跨多个连接或来⾃⽤户的⻚⾯请求。⼀个会话通常对应于⼀个⽤户,该⽤户可能多次访问⼀个站点。可以通过此接⼝查看和操作有关某个会话的信息,⽐如会话标识符、创建时间和最后⼀次访问时间。在整个 session 中,最重要的就是属性的操作。
session ⽆论客户端还是服务器端都可以感知到,若重新打开⼀个新的浏览器,则⽆法取得之前设置的 session,因为每⼀个 session 只保存在当前的浏览器当中,并在相关的⻚⾯取得。
Session 的作⽤就是为了标识⼀次会话,或者说确认⼀个⽤户;并且在⼀次会话(⼀个⽤户的多次请求)期间共享数据。我们可以通过 request.getSession()⽅法,来获取当前会话的 session 对象。
// 如果session对象存在,则获取;如果session对象不存在,则创建
HttpSession session = request.getSession();
标识符JSESSIONID:
Session 既然是为了标识⼀次会话,那么此次会话就应该有⼀个唯⼀的标志,这个标志就是sessionId。
每当⼀次请求到达服务器,如果开启了会话(访问了 session),服务器第⼀步会查看是否从客户端回传⼀个名为 JSESSIONID 的 cookie,如果没有则认为这是⼀次新的会话,会创建 ⼀个新的 session 对象,并⽤唯⼀的 sessionId 为此次会话做⼀个标志。如果有 JESSIONID 这 个cookie回传,服务器则会根据 JSESSIONID 这个值去查看是否含有id为JSESSION值的session 对象,如果没有则认为是⼀个新的会话,重新创建⼀个新的 session 对象,并标志此次会话; 如果找到了相应的 session 对象,则认为是之前标志过的⼀次会话,返回该 session 对象,数据达到共享。这⾥提到⼀个叫做 JSESSIONID 的 cookie,这是⼀个⽐较特殊的 cookie,当⽤户请求服务器时,如果访问了 session,则服务器会创建⼀个名为 JSESSIONID,值为获取到的 session(⽆论是获取到的还是新创建的)的 sessionId 的 cookie 对象,并添加到 response 对象中,响应给客户端,有效时间为关闭浏览器。所以 Session 的底层依赖 Cookie 来实现。
session对象:
Session ⽤来表示⼀次会话,在⼀次会话中数据是可以共享的,这时 session 作为域对象存在,可以通过 setAttribute(name,value) ⽅法向域对象中添加数据,通过 getAttribute(name) 从域对象中获取数据,通过 removeAttribute(name) 从域对象中移除数据。
// 获取session对象
HttpSession session = request.getSession();
// 设置session域对象
session.setAttribute("uname","admin");
// 获取指定名称的session域对象
String uname = (String) request.getAttribute("uname");
// 移除指定名称的session域对象
session.removeAttribute("uname");
数据存储在 session 域对象中,当 session 对象不存在了,或者是两个不同的 session 对象时,数据也就不能共享了。这就不得不谈到 session 的⽣命周期。
session对象的销毁:
默认时间到期:
当客户端第⼀次请求 servlet 并且操作 session 时,session 对象⽣成,Tomcat 中 session 默认的存活时间为 30min,即你不操作界⾯的时间,⼀旦有操作,session 会重新计时。那么 session 的默认时间可以改么?答案是肯定的。
可以在 Tomcat 中的 conf ⽬录下的 web.xml ⽂件中进⾏修改。
<!-- session 默认的最⼤不活动时间。单位:分钟。 -->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
自己设定到期时间:
当然除了以上的修改⽅式外,我们也可以在程序中⾃⼰设定 session 的⽣命周期,通过session.setMaxInactiveInterval(int) 来设定 session 的最⼤不活动时间,单位为秒。
// 获取session对象
HttpSession session = request.getSession();
// 设置session的最⼤不活动时间
session.setMaxInactiveInterval(15); // 15秒
当然我们也可以通过 getMaxInactiveInterval() ⽅法来查看当前 Session 对象的最⼤不活动时间。
// 获取session的最⼤不活动时间
int time = session.getMaxInactiveInterval();
立刻失效:
或者我们也可以通过 session.invalidate() ⽅法让 session ⽴刻失效
// 销毁session对象
session.invalidate();
关闭浏览器:
从前⾯的 JESSIONID 可知道,session 的底层依赖 cookie 实现,并且该 cookie 的有效时间为关闭浏览器,从⽽ session 在浏览器关闭时也相当于失效了(因为没有 JSESSION 再与之对应)。
关闭服务器:
当关闭服务器时,session 销毁。
Session 失效则意味着此次会话结束,数据共享结束。
ServletContext对象:
每⼀个 web 应⽤都有且仅有⼀个ServletContext 对象,⼜称 Application 对象,从名称中可知,该对象是与应⽤程序相关的。在 WEB 容器启动的时候,会为每⼀个 WEB 应⽤程序创建⼀个对应的ServletContext 对象。
该对象有两⼤作⽤,第⼀、作为域对象⽤来共享数据,此时数据在整个应⽤程序中共享; 第⼆、该对象中保存了当前应⽤程序相关信息。例如可以通过 getServerInfo() ⽅法获取当前服务器信息 ,getRealPath(String path) 获取资源的真实路径等。
ServletContext对象的获取:
获取 ServletContext 对象的途径有很多。⽐如:
- 通过 request 对象获取
ServletContext servletContext = request.getServletContext();
- 通过 session 对象获取
ServletContext servletContext = request.getSession().getServletContext();
- 通过 servletConfifig 对象获取,在 Servlet 标准中提供了 ServletConfifig ⽅法
ServletConfig servletConfig = getServletConfig();
ServletContext servletContext = servletConfig.getServletContext();
- 直接获取,Servlet 类中提供了直接获取 ServletContext 对象的⽅法
ServletContext servletContext = getServletContext();
常⽤⽅法
// 获取项⽬存放的真实路径
String realPath = request.getServletContext().getRealPath("/");
// 获取当前服务器的版本信息
String serverInfo = request.getServletContext().getServerInfo();
ServletContext域对象:
ServletContext 也可当做域对象来使⽤,通过向 ServletContext 中存取数据,可以使得整个应⽤程序共享某些数据。当然不建议存放过多数据,因为 ServletContext 中的数据⼀旦存储进去没有⼿动移除将会⼀直保存。
// 获取ServletContext对象
ServletContext servletContext = request.getServletContext();
// 设置域对象
servletContext.setAttribute("name","zhangsan");
// 获取域对象
String name = (String) servletContext.getAttribute("name");
// 移除域对象
servletContext.removeAttribute("name");
Servlet的三⼤域对象
-
request域对象
在⼀次请求中有效。请求转发有效,重定向失效。
-
session域对象
在⼀次会话中有效。请求转发和重定向都有效,session销毁后失效。
-
servletContext域对象
在整个应⽤程序中有效。服务器关闭后失效。
文件上传和下载:
在上⽹的时候我们常常遇到⽂件上传的情况,例如上传头像、上传资料等;当然除了上传,遇⻅下载的情况也很多,接下来看看我们 servlet 中怎么实现⽂件的上传和下载。
文件上传:
⽂件上传涉及到前台⻚⾯的编写和后台服务器端代码的编写,前台发送⽂件,后台接收并保存⽂件,这才是⼀个完整的⽂件上传。
前台页面:
在做⽂件上传的时候,会有⼀个上传⽂件的界⾯,⾸先我们需要⼀个表单,并且表单的请求⽅式为POST;其次我们的 form 表单的 enctype 必须设为"multipart/form-data",即enctype=“multipart/form-data”,意思是设置表单的类型为⽂件上传表单。默认情况下这个表单类型是 “application/x-www-form-urlencoded”, 不能⽤于⽂件上传。只有使⽤了multipart/form-data 才能完整地传递⽂件数据。
<!--
⽂件上传表单
1. 表单提交类型 method="post"
2. 表单类型 enctype="multipart/form-data"
3. 表单元素类型 ⽂件域设置name属性值
-->
<form method="post" action="uploadServlet" enctype="multipart/form-data">
姓名:<input type="text" name="uname" > <br>
⽂件:<input type="file" name="myfile" > <br>
<button type="submit">提交</button>
</form>
后台实现:
使⽤注解 @MultipartConfifig 将⼀个 Servlet 标识为⽀持⽂件上传。 Servlet 将 multipart/form-data 的 POST 请求封装成 Part,通过 Part 对上传的⽂件进⾏操作。
@WebServlet("/uploadServlet")
@MultipartConfig // 如果是⽂件上传表单,⼀定要加这个注解
public class UploadServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
// 设置请求的编码格式
request.setCharacterEncoding("UTF-8");
// 获取普通表单项 (⽂本框)
String uname = request.getParameter("uname"); // "uname"代表的是⽂本框的
name属性值
// 通过 getPart(name) ⽅法获取Part对象 (name代表的是⻚⾯中file⽂件域的name属
性值)
Part part = request.getPart("myfile");
// 通过Part对象,获取上传的⽂件名
String fileName = part.getSubmittedFileName();
// 获取上传⽂件需要存放的路径 (得到项⽬存放的真实路径)
String realPath = request.getServletContext().getRealPath("/");
// 将⽂件上传到指定位置
part.write(realPath + fileName);
}
}
文件下载:
⽂件下载,即将服务器上的资源下载(拷⻉)到本地,我们可以通过两种⽅式下载。第⼀种是通过超链接本身的特性来下载;第⼆种是通过代码下载。
超链接下载:
当我们在 HTML 或 JSP ⻚⾯中使⽤a标签时,原意是希望能够进⾏跳转,但当超链接遇到浏览器不识别的资源时会⾃动下载;当遇⻅浏览器能够直接显示的资源,浏览器就会默认显示出来,⽐如 txt、png、jpg 等。当然我们也可以通过 download 属性规定浏览器进⾏下载。但有些浏览器并不⽀持。
默认下载
<!-- 当超链接遇到浏览器不识别的资源时,会⾃动下载 -->
<a href="test.zip">超链接下载</a>
指定 download 属性下载
<!-- 当超链接遇到浏览器识别的资源时,默认不会下载。通过download属性可进⾏下载 -->
<a href="test.txt" download>超链接下载</a>
download 属性可以不写任何信息,会⾃动使⽤默认⽂件名。如果设置了download属性的值,则使⽤设置的值做为⽂件名。当⽤户打开浏览器点击链接的时候就会直接下载⽂件。
后台实现下载:
相当于从out文件中的资源,下载到浏览器中
可以查看getServletContext().getRealPath("/");得到的路径。
实现步骤
-
需要通过 response.setContentType ⽅法设置 Content-type 头字段的值, 为浏览器⽆法使⽤某种⽅式或激活某个程序来处理的 MIME 类型,例 如 “application/octet-stream” 或"application/x-msdownload" 等。
-
需要通过 response.setHeader ⽅法设置 Content-Disposition 头的值 为 “attachment;fifilename=⽂件名”
-
读取下载⽂件,调⽤ response.getOutputStream ⽅法向客户端写⼊附件内容。
public class DownloadServlet extends HttpServlet {
protected void service(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
// 设置请求的编码
request.setCharacterEncoding("UTF-8");
// 获取⽂件下载路径
String path = getServletContext().getRealPath("/");
// 获取要下载的⽂件名
String name = request.getParameter("fileName");
// 通过路径得到file对象
File file = new File(path + name);
// 判断file对象是否存在,且是否是⼀个标准⽂件
if (file.exists() && file.isFile()) {
// 设置响应类型 (浏览器⽆法使⽤某种⽅式或激活某个程序来处理的类型)
response.setContentType("application/x-msdownload");
// 设置头信息
response.setHeader("Content-Disposition", "attachment;filename=" +
name);
// 得到输⼊流
InputStream is = new FileInputStream(file);
// 得到输出流
ServletOutputStream os = response.getOutputStream();
// 定义byte数组
byte[] car = new byte[1024];
// 定义⻓度
int len = 0;
// 循环 输出
while ((len = is.read(car)) != -1) {
os.write(car, 0, len);
}
// 关闭流 释放资源
os.close();
is.close();
} else {
System.out.println("⽂件不存在,下载失败!");
}
}
}