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 等等。
项目结构
在src下新建包和一个普通的java类
public class Servlet01 { }实现Servlet:
继承HttpServlet重写service(HttpServletRequest req, HttpServletResponse resp)方法设置注解 /** * 1.继承HttpServlet * 2.重写service方法(HttpServletRequest) * 3.添加@WebServlet("/路径") * 注意:不要忘了 / */ @WebServlet("/ser01") public class Servlet01 extends HttpServlet { @Override public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("Hello world"); resp.getWriter().write("hello javaEE"); } }到此,需要编写和配置的地方已经完成,项目已经完整了,但是如果需要外界能够访问, 还需要将项目发布到服务器上并运行服务器。
设置项目的tomcat配置 设置项目的站点名(项目对外访问路径) 启动服务器 访问并查看结果 在项目正确发布到服务器上之后,用户即可通过浏览器访问该项目中的资源。注意 url 的 格式正确,tomcat 的端口为 8080。 访问路径:localhost(ip地址):8080(端口号)/servlet01(站点名:项目对外访问路径)/ser01(请求资源路径) 后台结果 至此,一个简单的servlet实现了Servlet没有 main()方法,不能独立运行,它的运行完全由 Servlet 引擎来控制和调度。 所谓生命周期,指的是servlet 容器何时创建 servlet 实例、何时调用其方法进行请求的处理、 何时并销毁其实例的整个过程。
实例和初始化时机 当请求到达容器时,容器查找该 servlet 对象是否存在,如果不存在,则会创建实例并进行初始化。就绪/调用/服务阶段 有请求到达容器,容器调用 servlet 对象的 service()方法,处理请求的方法在整个生命周期中可以被多次调用;HttpServlet 的 service()方法,会依据请求方式来调用 doGet()或者 doPost()方法。但是, 这两个 do 方法默认情况下,会抛出异常,需要子类去 override。销毁时机 当容器关闭时(应用程序停止时),会将程序中的 Servlet 实例进行销毁。 上述的生命周期可以通过 Servlet 中的生命周期方法来观察。在 Servlet 中有三个生命周 期方法,不由用户手动调用,而是在特定的时机有容器自动调用,观察这三个生命周期方法 即可观察到 Servlet 的生命周期。 @WebServlet("/ser02") public class Servlet02 extends HttpServlet { /** * 系统方法,服务器自动调用 * 当第一次请求时(Servlet实例不存在时)执行,只会执行一次 */ @Override public void init(ServletConfig config) throws ServletException { System.out.println("init() 第一次请求时被调用"); } /** * 系统方法 * 每当有请求访问时,都会执行。执行多次 * */ @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("service() 方法"); } /** * 当servlet实例被销毁时(服务器关闭)执行,只会执行一次 */ @Override public void destroy() { System.out.println("destroy()方法 servlet实例被销毁时调用"); } }第一次请求该资源时,先调用ain 请求该资源 第一次请求该资源时,容器中该servlet对象不存在,会调用init()方法对该类创建实例并初始化。 init()方法执行完毕后,会接着执行service()方法。
在浏览器中多次访问该资源 会发现init()执行第一次后不会再执行了,但是service方法执行了多次。 接下来关闭服务器 服务器关闭后,destory方法被执行了一次。
Servlet 的生命周期,简单的概括这就分为四步:servlet 类加载–>实例化–>服务–>销毁。 下面我们描述一下 Tomcat 与 Servlet 是如何工作的,看看下面的时序图
HttpServletRequest 对象:主要作用是用来接收客户端发送过来的请求信息,例如:请求的参数,发送的头信息等都属于客户端发来的信息,service()方法中形参接收的是 HttpServletRequest 接口的实例化对象,表示该对象主要应用在 HTTP 协议上,该对象是由 Tomcat 封装好传递过来。
get请求方式会走对应的doGet方法,post请求则会走其对应的doPost方法。
@WebServlet("/ser03") public class Servlet03 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取请求方式 String method = req.getMethod(); System.out.println("请求方式"+method); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("Post ...."); } }而doGet和doPost往往不会被用,经常用的是service()方法。
访问路径
http://localhost:8080/servlet01/ser04?uname=zs&pwd=123获取请求参数
//获取指定参数 //获取用户名 String uname = req.getParameter("uname"); //获取密码 String pwd = req.getParameter("pwd"); System.out.println("用户名:"+uname+" "+"密码:"+pwd);方式一:
// 设置请求乱码问题 (只针对POST有效) req.setCharacterEncoding("utf-8");这种方式只针对 POST 有效(必须在接收所有的数据之前设定)。而在tomcat8及以后的版本,get请求方式不会出现乱码问题 方式二:
new String(request.getParameter(name).getBytes("ISO-8859-1"),"UTF-8");特点:一次只能处理一个条数据,麻烦。
通过该对象可以在一个请求中传递数据,作用范围:在一次请求中有效,即服务器跳转有效。
// 设置域对象内容 request.setAttribute(String name, String value); // 获取域对象内容 request.getAttribute(String name); // 删除域对象内容 request.removeAttribute(String name);request 域对象中的数据在一次请求中有效,则经过请求转发,request 域中的数据依然存在,则在请求转发的过程中可以通过 request 来传输/共享数据。
若用户名和密码均为admin,则跳转至登录成功页面,显示欢迎XXX登录成功。若登录失败,则跳转至登录失败页面,显示登录失败信息。
登录页面
<form action="ser04" method="get"> <p> 用户名:<input type="text" name="uname" /> </p> <p> 密码:<input type="text" name="pwd" /> </p> <p> <input type="submit" value="提交" /> </p> </form>登录成功
<p> 欢迎<span style="color: blue"></span>登录成功 </p>登录失败
<span style="color: red">登录失败!${msg}</span>Servlet
/** * HttpServletRequest对象 * 1. 常用方法 * 获取请求路径 * 2. 获取请求的参数(重要!!!) * request.getParameter("参数名"); * 3. 请求乱码问题 * 乱码原因:request有默认的解析编码,ISO-8859-1,这种编码格式不支持中文,传递中文参数必定乱码。 * 解决方案: * 1. 只针对POST请求的乱码问题 (处理所有参数) * request.setCharacterEncoding("UTF-8"); * 2. 任意请求方式都有效 (一次处理一个参数) * new String(request.getParameter("参数名").getBytes("ISO-8859-1"), "UTF-8"); * 目前Tomcat8及以上版本GET请求不会出现乱码,所以目前只需要处理POST请求的参数乱码即可。 * 4. 请求转发 * 一种跳转方式。 * 1. 服务端跳转。 * 2. 地址栏不会发生改变 * 3. 只有一次请求 * 4. 数据可以共享 * 5. request作用域 * 只在一次请求中有效,只在请求转发跳转有效 * request.setAttribute("参数名","参数值"); 设置作用域(任意类型的数据) * request.getAttribute("参数名"); 获取指定作用域的值 * request.removeAttribute("参数名"); 移除指定作用域的值 * */ @WebServlet("/ser04") public class Servlet04 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置请求乱码问题 (只针对POST有效) req.setCharacterEncoding("utf-8"); /** * 获取请求的参数 * 1. 获取指定参数名的参数值 * request.getParameter("参数名") * 注: * 1. 表单提交 * 参数名表示的是表单元素的name属性值 * 2. 地址栏输入 * 参数名表示的是"?键=值&键=值"中的键 * 2. 获取指定参数名的所有参数值 * request.getParameterValue("参数名") */ //获取指定参数 //获取用户名 String uname = req.getParameter("uname"); //获取密码 String pwd = req.getParameter("pwd"); System.out.println("用户名:"+uname+" "+"密码:"+pwd); // 获取多个参数getParameterValues("参数名") 会返回一个数组,可以通过数组下标取元素 //若用户名和密码均为admin,则登录成功,跳转至success.jsp,显示欢迎XXX登录成功。否则跳转至err.jsp,显示登录失败 if ("admin".equals(uname)&&"admin".equals(pwd)){ /** * 设置请求域:设置数据存放在作用域中,让客户端能够获取数据 */ req.setAttribute("uname",uname); //转发 req.getRequestDispatcher("success.jsp").forward(req,resp); }else { /** * 设置请求域:设置数据存放在作用域中,让客户端能够获取数据 */ req.setAttribute("msg","用户名或密码错误!"); //转发 req.getRequestDispatcher("err.jsp").forward(req,resp); } } }登录成功 登录失败
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());注意:两者不能同时使用。
在响应中,如果我们响应的内容中含有中文,则有可能出现乱码。这是因为服务器响应的数据也会经过网络传输,服务器端有一种编码方式,在客户端也存在一种编码方式,当两端使用的编码方式不同时则出现乱码。
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");同样也可以使用一句替代
// 设置客户端与服务端的编码 response.setContentType("text/html;charset=UTF-8");总结:要想解决响应的乱码,只需要保证使用支持中文的编码格式。并且保证服务器端 和客户端使用相同的编码方式即可。
/** * 响应乱码解决 * getWriter() * 服务器默认编码是ISO-8859-1,响应中文必定乱码,客户端与服务端的编码格式不一致 * getOutputStream() * 传输的是字节,如果客户端与服务端的编码格式一致时,不会乱码,否则乱码。 * * * 乱码原因: * 1. 编码格式不支持中文 * 2. 客户端与服务端的编码格式不一致 * 解决方案: * 1. 设置编码格式支持中文 * 2. 设置客户端与服务端编码格式一致 * * * 1.设置服务端的编码格式 * response.setCharacterEncoding("UTF-8"); * 2. 设置客户端的编码格式 * response.setHeader("content-type","text/html;charset=UTF-8"); * 或者 * 同时设置客户端与服务端的编码格式 * response.setContentType("text/html;charset=UTF-8"); * */重定向是一种服务器指导,客户端的行为。客户端发出第一个请求,被服务器接收处理后,服务器会进行响应,在响应的同时,服务器会给客户端一个新的地址(下次请求的地址 response.sendRedirect(url);),当客户端接收到响应后,会立刻、马上、自动根据服务器给的新地址发起第二个请求,服务器接收请求并作出响应,重定向完成。 从描述中可以看出重定向当中有两个请求存在,并且属于客户端行为。
// 重定向跳转到index.jsp response.sendRedirect("index.jsp");Cookie是浏览器提供的⼀种技术,通过服务器的程序能将⼀些只须保存在客户端,或者在客户端进⾏处理的数据,放在本地的计算机上,不需要通过⽹络传输,因⽽提⾼⽹⻚处理的效率,并且能够减少服务器的负载,但是由于 Cookie 是服务器端保存在客户端的信息, 所以其安全性也是很差的。例如常⻅的记住密码则可以通过 Cookie 来实现。
在服务器端只提供了⼀个 getCookies()的⽅法⽤来获取客户端回传的所有 cookie 组成的⼀个数组,如果需要获取单个 cookie 则需要通过遍历,getName()获取 Cookie 的名称,getValue()获取 Cookie的值
/** * Session对象 * 1. Session对象的获取 * request.getSession(); * 如果session对象存在,则直接获取;如果session对象不存在,则新建session * 2. JSESSIONID标识符 * 当请求到达服务器时,如果使用了session,服务器会去获取一个JSESSIONID的cookie对象 * 如果cookie对象不存在,则新建session对象,并设置sessionId,将sessionId回传给客户端中,设置对应的cookie * 如果cookie对象存在,服务器会比较客户端传递的sessionID是否与服务端的一致 * 如果不一致,新建session对象,并设置sessionId,将sessionId回传给客户端中,重新设置对应的cookie * 如果一致,则获取到当前session对象 * Session底层是依赖cookie的,默认关闭浏览器失效。 * 3. session作用域 * 在一次会话有效,可以有多次请求和响应,在会话中数据共享。 * setAttribute() * getAttribute() * removeAttribute() * * */ @WebServlet("/sess02") public class Session02 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取session对象 HttpSession session = req.getSession(); System.out.println("Session02 ID "+session.getId()); /** * 获取作用域 */ // request作用域 System.out.println(req.getAttribute("name")); // session作用域 System.out.println(session.getAttribute("name2")); } }除了 Cookie 的名称和内容外,我们还需要关⼼⼀个信息,到期时间,到期时间⽤来指定该 cookie 何时失效。默认为当前浏览器关闭即失效。我们可以⼿动设定 cookie 的有效时间(通过到期时间计算),通过 setMaxAge(int time);⽅法设定 cookie 的最⼤有效时间,以秒为单位。
/** * Cookie的失效时间 * cookie默认是关闭浏览器失效(与服务器是否关闭无关) * * 通过maxAge设置cookie的失效时间 * 1. 正整数 * 表示cookie存活指定秒数。 * 2. 负整数 * 表示cookie只在浏览器中存活,浏览器关闭即失效.默认值是-1 * 3. 零 * 表示删除cookie */ @WebServlet("/cook03") public class Cookie03 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获得cookie对象 Cookie cookie=new Cookie("uname1","zhangsan"); //设置cookie有效时长 单位:秒 cookie.setMaxAge(20); //发送cookie resp.addCookie(cookie); //创建cookie对象 Cookie cookie2=new Cookie("uname2","lisi"); //负整数:表示cookie只在浏览器中存活,浏览器关闭即失效.默认值是-1 cookie2.setMaxAge(-1); //发送cookie resp.addCookie(cookie2); //创建cookie对象 Cookie cookie3 = new Cookie("uname3", "wnagwu"); //零:表示即刻删除 cookie3.setMaxAge(0); //发送cookie resp.addCookie(cookie3); //删除已有的cookie对象 Cookie cookie4 = new Cookie("uname", null); cookie4.setMaxAge(0); resp.addCookie(cookie4); } }Cookie的setPath设置cookie的路径,这个路径直接决定服务器的请求是否会从浏览器中加载某些cookie。
/** * Cookie的路径 * 1. 在当前项目下的资源可以获取 * 2. 在当前服务器下的资源可以获取 设置路径为"/" * 3. 在指定项目下的资源可以访问 * 4. 在指定路径的资源可以访问 * * 总结:当访问的路径中包含cookie的path时,就能获取到当前cookie对象 */ @WebServlet("/cook05") public class Cookie05 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 在当前项目下的资源可以获取 Cookie cookie01 = new Cookie("a1","aa"); cookie01.setPath("/s02"); resp.addCookie(cookie01); // 2. 在当前服务器下的资源可以获取 设置路径为"/" Cookie cookie02 = new Cookie("a2","bb"); cookie02.setPath("/"); resp.addCookie(cookie02); // 3. 在指定项目下的资源可以访问 Cookie cookie03 = new Cookie("a3","cc"); cookie03.setPath("/s01"); resp.addCookie(cookie03); // 4. 在指定路径的资源可以访问 Cookie cookie04 = new Cookie("a4","dd"); cookie04.setPath("/s02/cook"); resp.addCookie(cookie04); } }Session 的作⽤就是为了标识⼀次会话,或者说确认⼀个⽤户;并且在⼀次会话(⼀个⽤户的多次请求)期间共享数据。我们可以通过 request.getSession()⽅法,来获取当前会话的 session 对象。
Session 既然是为了标识⼀次会话,那么此次会话就应该有⼀个唯⼀的标志,这个标志就是 sessionId。
//获取Session对象 HttpSession session = req.getSession(); session.getId();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");获取 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();ServletContext 也可当做域对象来使⽤,通过向 ServletContext 中存取数据,可以使得整个应⽤程序共享某些数据。当然不建议存放过多数据,因为 ServletContext 中的数据⼀旦存储进去没有⼿动移除将会⼀直保存。
/** * ServletContext对象 * * * Servlet的三大域对象 * 1. request作用域 * 在一次请求中有效,在请求转发有效 * 2. session作用域 * 在一次会话中有效,可以有多次请求,无论是请求转发或重定向都有效。session销毁后失效。 * 3. servletContext作用域 * 在整个应用程序中有效,服务器关闭失效。 * */ @WebServlet("/serv1") public class ServletContext01 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { /** * ServletContext对象的获取 */ // 通过request对象 ServletContext servletContext = req.getServletContext(); // 通过session对象获取 ServletContext servletContext1 = req.getSession().getServletContext(); // 通过servletConfig对象 ServletContext servletContext2 = getServletConfig().getServletContext(); // 直接获取 ServletContext servletContext3 = getServletContext(); /** * 常用方法 */ // 获取项目的真实路径 String realPath = servletContext.getRealPath("/"); System.out.println("获取项目的真实路径:" + realPath); // 获取服务器的版本信息 String serverInfo = req.getServletContext().getServerInfo(); System.out.println("获取服务器的版本信息:" + serverInfo); /** * servletContext作用域 * 在整个应用程序中有效,服务器关闭失效 * setAttribute(); * getAttribute(); * removeAttribute(); */ } }在上⽹的时候我们常常遇到⽂件上传的情况,例如上传头像、上传资料等;当然除了上传,遇⻅下载,的情况也很多,接下来看看我们 servlet 中怎么实现⽂件的上传和下载。
1.前端页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>文件上传</title> </head> <body> <form action="upload" method="post" enctype="multipart/form-data"> <input type="file" name="myPart"/> <input type="submit" value="上传" /> </form> </body> </html>2.后端页面
/** * 文件上传 */ @MultipartConfig @WebServlet("/upload") public class UploadService extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //设置请求编码格式 req.setCharacterEncoding("utf-8"); //文件上传 //得到part对象 Part part = req.getPart("myPart"); //得到上传文件的名称 String fileName = part.getSubmittedFileName(); //得到项目存放的真实路径 String realPath = req.getServletContext().getRealPath("/upload/"); //文件上传 part.write(realPath+fileName); } }