这两天笔者学习了前后端交互的关键点Servlet的相关知识,看到自己写得页面能够和后台交互了,也是有点小激动呢。当然前提要有自己写的页面和Tomcat服务器(7.0以上版本),这样才能够互相对接,就让笔者向大家来介绍如何使用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 等等。
笔者用的是 MyEcplise2018 平台编写Java的代码,所以先来创建一个动态的 Web 项目,其后在 src 目录下创建包和一个普通类,然后用这个普通类继承 HttpServlet 类。
满足 Servlet 规范只是让我们的类能够满足接收请求的要求,接收到请求后需要对请求进行分析,以及进行业务逻辑处理,计算出结果,则需要添加代码,在规范中有一个叫做 service 的方法,专门用来做请求处理的操作,业务代码则可以写在该方法中。
package com.xxxx.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class Servlet01 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("Hello Servlet!"); resp.getWriter().write("Hello World"); } }在完成好了一切代码的编写后,还需要向服务器说明,特定请求对应特定资源。 开发servlet项目,使用@WebServlet将一个继承于 javax.servlet.http.HttpServlet 的类定义为 Servlet 组件。在 Servlet3.0 中 , 可以使用 @WebServlet 注解将一个继承于javax.servlet.http.HttpServlet 的类标注为可以处理用户请求的 Servlet。
package com.xxxx.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/ser01") public class Servlet01 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("Hello Servlet!"); resp.getWriter().write("Hello World"); } }用注解配置 Servlet
@WebServlet(name="Servlet01",value="/ser01")
@WebServlet(name="Servlet01",urlPatterns = "/ser01")
也可以配置多个访问路径
@WebServlet(name="Servlet01",value={"/ser01",'/ser001'})
@WebServlet(name="Servlet01",urlPatterns={"/ser01",'/ser001'})
之后启动服务器,通过浏览器访问 http://localhost:端口号/项目名/WebServlet 里的值即可访问所在的类,这样 Servlet 就实现成功了。
Servlet没有 main()方法,不能独立运行,它的运行完全由 Servlet 引擎来控制和调度。 所谓生命周期,指的是 servlet 容器何时创建 servlet 实例、何时调用其方法进行请求的处理、 何时并销毁其实例的整个过程。
实例和初始化时机当请求到达容器时,容器查找该 servlet 对象是否存在,如果不存在,则会创建实例并进行初始化。
就绪/调用/服务阶段有请求到达容器,容器调用 servlet 对象的 service()方法,处理请求的方法在整个生命周期中可以被多次调用; HttpServlet 的 service()方法,会依据请求方式来调用 doGet()或者 doPost()方法。但是, 这两个 do 方法默认情况下,会抛出异常,需要子类去 override。
销毁时机当容器关闭时(应用程序停止时),会将程序中的 Servlet 实例进行销毁。 上述的生命周期可以通过 Servlet 中的生命周期方法来观察。在 Servlet 中有三个生命周期方法,不由用户手动调用,而是在特定的时机有容器自动调用,观察这三个生命周期方法 即可观察到 Servlet 的生命周期。
init 方法,在 Servlet 实例创建之后执行(证明该 Servlet 有实例创建了)
public void init(ServletConfig config) throws ServletException { System.out.println("实例创建了..."); }service 方法,每次有请求到达某个 Servlet 方法时执行,用来处理请求(证明该Servlet 进行服务了)
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("服务调用了..."); }destroy 方法,Servlet 实例销毁时执行(证明该 Servlet 的实例被销毁了)
public void destroy() { System.out.println("实例销毁了..."); }Servlet 的生命周期,简单的概括这就分为四步:servlet 类加载-->实例化-->服务-->销毁。
下面我们描述一下 Tomcat 与 Servlet 是如何工作的,看看下面的时序图:
Web Client 向 Servlet 容器(Tomcat)发出 Http 请求Servlet 容器接收 Web Client 的请求Servlet 容器创建一个 HttpServletRequest 对象,将 Web Client 请求的信息封装到这个对象中Servlet 容器创建一个 HttpServletResponse 对象Servlet 容器调HttpServlet 对象service 方法,把 Request 与 Response 作为参数,传给 HttpServletHttpServlet 调用 HttpServletRequest 对象的有关方法,获取 Http 请求信息HttpServlet 调用 HttpServletResponse 对象的有关方法,生成响应数据Servlet 容器把 HttpServlet 的响应结果传给 Web ClientHttpServletRequest 对象:主要作用是用来接收客户端发送过来的请求信息,例如:请求的参数,发送的头信息等都属于客户端发来的信息,service()方法中形参接收的是 HttpServletRequest 接口的实例化对象,表示该对象主要应 用在 HTTP 协议上,该对象是由 Tomcat 封装好传递过来。
HttpServletRequest 是 ServletRequest 的子接口,ServletRequest 只有一个子接口,就是 HttpServletRequest。既然 只有一个子接口为什么不将两个接口合并为一个? 从长远上讲:现在主要用的协议是 HTTP 协议,但以后可能出现更多新的协议。若以后想要支持这种新协议,只需要直接继承 ServletRequest 接口就行了。
在 HttpServletRequest 接口中,定义的方法很多,但都是围绕接收客户端参数的。但是怎么拿到该对象呢?不需要,直接在 Service 方法中由容器传入过来,而我们需要做的就是取出对象中的数据,进行分析、处理。
示例
// 获取客户端请求的完整URL (从http开始,到?前面结束) String url = request.getRequestURL().toString(); System.out.println("获取客户端请求的完整URL:" + url); // 获取客户端请求的部分URL (从站点名开始,到?前面结束) String uri = request.getRequestURI(); System.out.println("获取客户端请求的部分URL:" + uri); // 获取请求行中的参数部分 String queryString = request.getQueryString(); System.out.println("获取请求行中的参数部分:" + queryString); // 获取客户端的请求方式 String method = request.getMethod(); System.out.println("获取客户端的请求方式:" + method); // 获取HTTP版本号 String protocol = request.getProtocol(); System.out.println("获取HTTP版本号:" + protocol); // 获取webapp名字 (站点名) String webapp = request.getContextPath(); System.out.println("获取webapp名字:" + webapp);示例
// 获取指定名称的参数,返回字符串 String uname = request.getParameter("uname"); System.out.println("uname的参数值:" + uname); // 获取指定名称参数的所有参数值,返回数组 String[] hobbys = request.getParameterValues("hobby"); System.out.println("获取指定名称参数的所有参数值:" + Arrays.toString(hobbys));由于现在的 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.setAttribute(String name, String value); // 获取域对象内容 request.getAttribute(String name); // 删除域对象内容 request.removeAttribute(String name);request 域对象中的数据在一次请求中有效,则经过请求转发,request 域中的数据依然存在,则在请求转发的过程中可以通过 request 来传输/共享数据。
Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的 request 对象和代表响应的 response 对象。request 和 response 对象代表请求和响应:获取客户端数据,需要通过 request 对象;向客户端输出数据,需要通过 response 对象。
HttpServletResponse 的主要功能用于服务器对客户端的请求进行响应,将 Web 服务器处理后的结果返回给客户端。service()方法中形参接收的是 HttpServletResponse 接口的实例化对象,这个对象中封装了向客户端发送数据、 发送响应头,发送响应状态码的方法。
接收到客户端请求后,可以通过 HttpServletResponse 对象直接进行响应,响应时需要获取输出流。 有两种形式:
getWriter() 获取字符流(只能响应回字符)
getOutputStream() 获取字节流(能响应一切数据)
响应回的数据到客户端被浏览器解析。
注意:两者不能同时使用。
// 字符输出流 PrintWriter writer = response.getWriter(); writer.write("Hello"); writer.write("<h2>Hello</h2>"); // 字节输出流 ServletOutputStream out = response.getOutputStream(); out.write("Hello".getBytes()); out.write("<h2>Hello</h2>".getBytes()); // 设置响应MIME类型 response.setHeader("content-type","text/html"); // html在响应中,如果我们响应的内容中含有中文,则有可能出现乱码。这是因为服务器响应的数据也会经过网络传输, 服务器端有一种编码方式,在客户端也存在一种编码方式,当两端使用的编码方式不同时则出现乱码。
getWriter()的字符乱码
对于 getWriter()获取到的字符流,响应中文必定出乱码,由于服务器端在进行编码时默认会使用 ISO-8859-1 格式的编码,该编码方式并不支持中文。
要解决该种乱码只能在服务器端告知服务器使用一种能够支持中文的编码格式,比如我们通常用的"UTF-8"。
response.setCharacterEncoding("UTF-8");此时还只完成了一半的工作,要保证数据正确显示,还需要指定客户端的解码方式。
response.setHeader("content-type", "text/html;charset=UTF-8");两端指定编码后,乱码就解决了。一句话:保证发送端和接收端的编码一致
// 设置服务端的编码 response.setCharacterEncoding("UTF-8"); // 设置客户端的响应类型及编码 response.setHeader("content-type","text/html;charset=UTF-8"); // 得到字符输出流 PrintWriter writer = response.getWriter(); writer.write("<h2>你好</h2>");以上两端编码的指定也可以使用一句替代,同时指定服务器和客户端
response.setContentType("text/html;charset=UTF-8");对于 getOutputStream() 方式获取到的字节流,响应中文时,由于本身就是传输的字节, 所以此时可能出现乱码, 也可能正确显示。当服务器端给的字节恰好和客户端使用的编码方式一致时则文本正确显示,否则出现乱码。无论 如何我们都应该准确掌握服务器和客户端使用的是那种编码格式,以确保数据正确显示。
指定客户端和服务器使用的编码方式一致。同样也可以使用一句替代
// 设置客户端与服务端的编码 response.setContentType("text/html;charset=UTF-8");总结:要想解决响应的乱码,只需要保证使用支持中文的编码格式。并且保证服务器端和客户端使用相同的编码方式即可。
重定向是一种服务器指导,客户端的行为。客户端发出第一个请求,被服务器接收处理后,服务器会进行响应,在 响应的同时,服务器会给客户端一个新的地址(下次请求的地址 response.sendRedirect(url);),当客户端接收到响应后,会立刻、马上、自动根据服务器给的新地址发起第二个请求,服务器接收请求并作出响应,重定向完成。从描述中可以看出重定向当中有两个请求存在,并且属于客户端行为。
// 重定向跳转到index.jsp response.sendRedirect("index.jsp");通过观察浏览器我们发现第一次请求获得的响应码为 302,并且含有一个 location 头信息。并且地址栏最终看到的地址是和第一次请求地址不同的,地址栏已经发生了变化。
请求转发和重定向比较:
两者都可进行跳转,根据实际需求选取即可。
Cookie是浏览器提供的一种技术,通过服务器的程序能将一些只须保存在客户端,或者在客户端进行处理的数据, 放在本地的计算机上,不需要通过网络传输,因而提高网页处理的效率,并且能够减少服务器的负载,但是由于 Cookie 是服务器端保存在客户端的信息, 所以其安全性也是很差的。例如常见的记住密码则可以通过 Cookie 来实现。
有一个专门操作Cookie的类 javax.servlet.http.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);F12 查看
在服务器端只提供了一个 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 的有效时间(通过到期时间计算),通过 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 中不能出现中文,如果有中文则通过 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的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);/* 当前项目路径为:s01 */ Cookie cookie = new Cookie("xxx","XXX"); // 设置路径为"/s01/cook",表示在s02/cook目录下才可访问到Cookie对象 cookie.setPath("/s01/cook"); response.addCookie(cookie);
cookie的路径指的是可以访问该cookie的顶层目录,该路径的子路径也可以访问该cookie。
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();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 作为域对象存在,可以通过 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 的生命周期。
当客户端第一次请求 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 失效则意味着此次会话结束,数据共享结束。
每一个 web 应用都有且仅有一个ServletContext 对象,又称 Application 对象,从名称中可知,该对象是与应用程序相关的。在 WEB 容器启动的时候,会为每一个 WEB 应用程序创建一个对应的 ServletContext 对象。
该对象有两大作用,第一、作为域对象用来共享数据,此时数据在整个应用程序中共享; 第二、该对象中保存了当前应用程序相关信息。例如可以通过 getServerInfo() 方法获取当前服务器信息 ,getRealPath(String path) 获取资源的真实路径等。
获取 ServletContext 对象的途径有很多。比如:
通过 request 对象获取
ServletContext servletContext = request.getServletContext();通过 session 对象获取
ServletContext servletContext = request.getSession().getServletContext();通过 servletConfig 对象获取,在 Servlet 标准中提供了 ServletConfig 方法
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 = request.getServletContext(); // 设置域对象 servletContext.setAttribute("name","zhangsan"); // 获取域对象 String name = (String) servletContext.getAttribute("name"); // 移除域对象 servletContext.removeAttribute("name");在上网的时候我们常常遇到文件上传的情况,例如上传头像、上传资料等;当然除了上传,遇见下载的情况也很 多,接下来看看我们 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>使用注解 @MultipartConfig 将一个 Servlet 标识为支持文件上传。 Servlet 将 multipart/form-data 的 POST 请求封装成 Part,通过 Part 对上传的文件进行操作。
package com.xxxx.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import java.io.IOException; @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属性的值,则使用设置的值做为文件名。当用户打开浏览器点击链接的时候就会直接下载文件。
实现步骤
需要通过 response.setContentType 方法设置 Content-type 头字段的值, 为浏览器无法使用某种方式或激活某个程序来处理的 MIME 类型,例如 "application/octet-stream" 或 "application/x-msdownload" 等。需要通过 response.setHeader 方法设置 Content-Disposition 头的值 为 "attachment;filename=文件名"读取下载文件,调用 response.getOutputStream 方法向客户端写入附件内容。 package com.xxxx.servlet; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; 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("文件不存在,下载失败!"); } } }总结:前后端交互的基础,大概入门级别的东西就这么多,但是连接前台与后台的桥梁我们已经初步搭建完成了,后面的知识也会越来越深入,大家敬请期待!
如果大家喜欢笔者的学习记录,请给笔者点赞、支持加关注,你们的支持将会变成我写作的动力!
我所学到的任何有价值的知识都是由自学中得来的。