第5章〓Java Servlet基础 学习目的与要求 本章主要介绍Java Servlet的基础知识,包括部署Servlet、Servlet的生命周期、通过JSP页面访问Servlet、重定向与转发等。通过本章的学习,要求读者熟练掌握Servlet对象的创建与运行,理解Servlet的生命周期与工作原理。 本章主要内容 Servlet对象的创建与运行 Servlet的生命周期 通过JSP页面访问Servlet doGet()和doPost()方法 重定向与转发 在Java Servlet中使用session 基于Servlet的MVC模式 Java Servlet的核心思想就是在Web服务器端创建用来响应客户端请求的对象,该对象被称为一个Servlet对象。JSP技术以Java Servlet为基础,当客户端请求一个JSP页面时,Web服务器(例如Tomcat服务器)会自动生成一个对应的Java文件,编译该Java文件,并用编译得到的字节码文件在服务器端创建一个Servlet对象。实际的Web应用需要Servlet对象具有特定的功能,这就需要Web开发人员编写创建Servlet对象的类。对于如何编写Servlet类以及如何使用Servlet类,将在本章中重点介绍。 按照1.2.2节的操作步骤创建Web项目ch5,并为ch5添加Tomcat依赖。本章涉及的Java源文件保存在ch5项目的src目录中,涉及的JSP页面保存在ch5项目的web目录中。 5.1Servlet类与Servlet对象 编写一个Servlet类很简单,只要继承jakarta.servlet.http包中的HttpServlet类,并重写响应HTTP请求的方法即可。HttpServlet类实现了Servlet接口,实现了响应用户请求的接口方法。HttpServlet类的一个子类习惯地称为一个Servlet类,这样的子类创建的对象又习惯地称为Servlet对象。 【例51】一个简单的Servlet类。 例51的FirstServlet.java的代码如下: package servlet; import java.io.IOException; import java.io.PrintWriter; import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; public class FirstServlet extends HttpServlet{ private static final long serialVersionUID = 1L; public void init(ServletConfig config) throws ServletException{ super.init(config); } public void service(HttpServletRequest request,HttpServletResponse response) throws IOException{ //设置响应的内容类型 response.setContentType("text/html;charset=UTF-8"); //取得输出对象 PrintWriter out = response.getWriter(); out.println("htmlbody"); //在浏览器中显示"第一个Servlet类" out.println("第一个Servlet类"); out.println("/body/html"); } } 在编写Servlet类时必须有包名,也就是说必须在包中编写Servlet类。在本章中新建一个Web项目ch5,所有的Servlet类都放在src目录下的servlet包中。 编写完Servlet类的源文件,是不是就可以运行Servlet对象了呢?不可以,需要在部署Servlet以后才可以运行Servlet对象。 扫一扫 视频讲解 5.2Servlet对象的创建与运行 如果想让Web服务器使用Servlet类编译后的字节码文件创建Servlet对象处理用户请求,必须先为Web服务器部署Servlet。部署Servlet目前有两种方式,一种是在web.xml中部署Servlet,另一种是基于注解的方式部署Servlet。 5.2.1在web.xml中部署Servlet web.xml文件由Web服务器负责管理,该文件是Web应用的部署描述文件,包含如何将用户请求URL映射到Servlet。因此,用户可以在Web项目的web\WEBINF\web.xml文件中部署自己的Servlet。 部署Servlet 为了在web.xml文件中部署5.1节中的FirstServlet,需要在web.xml文件中找到<webapp></webapp>标记,然后在<webapp></webapp>标记中添加如下内容: servlet servlet-namefirstServlet/servlet-name servlet-classservlet.FirstServlet/servlet-class /servlet servlet-mapping servlet-namefirstServlet/servlet-name url-pattern/firstServlet/url-pattern /servlet-mapping 运行Servlet Servlet第一次被访问时,需要将Web项目发布到Tomcat Web服务器。发布后,可以在浏览器的地址栏中输入“http://localhost:8080/ch5/firstServlet”来运行Servlet,运行效果如图5.1所示。 图5.1第一个Servlet的运行效果 web.xml文件中与Servlet部署有关的标记及其说明 1) 根标记<webapp> 在XML文件中必须有一个根标记,web.xml的根标记是<webapp>。 2) <servlet>标记及其子标记 在web.xml文件中可以有若干<servlet>标记,该标记的内容由Web服务器负责处理。在<servlet>标记中有两个子标记<servletname>和<servletclass>,其中<servletname>子标记的内容是Web服务器创建的Servlet对象的名称。在web.xml文件中虽然可以有若干<servlet>标记,但是要求它们的<servletname>子标记的内容互不相同。<servletclass>子标记的内容指定Web服务器用哪个类来创建Servlet对象,如果Servlet对象已经创建,那么Web服务器就不再使用指定的类创建。 3) <servletmapping>标记及其子标记 在web.xml文件中出现一个<servlet>标记就会对应地出现一个<servletmapping>标记。在<servletmapping>标记中有两个子标记<servletname>和<urlpattern>,其中<servletname>子标记的内容是Web服务器创建的Servlet对象的名称(该名称必须和<servlet>标记的子标记<servletname>的内容相同); <urlpattern>子标记用来指定用户用怎样的模式请求Servlet对象,比如<urlpattern>子标记的内容是/firstServlet,用户需要请求服务器运行Servlet对象firstServlet为其服务,那么可以在浏览器的地址栏中输入“http://localhost:8080/ch5/firstServlet”。 一个Web项目的web.xml文件负责管理该Web项目的所有Servlet对象,当Web项目需要提供更多的Servlet对象时,只要在web.xml文件中添加<servlet>和<servletmapping>标记即可。 5.2.2基于注解的方式部署Servlet 从5.2.1节可知,每开发一个Servlet,都要在web.xml文件中部署Servlet才能够使用,这样会给Web项目的维护带来非常大的麻烦。从Servlet 3.0开始提供了@WebServlet注解,使得用户不再需要在web.xml文件中进行Servlet的部署描述,简化了开发流程。本书中后续的Servlet都是基于注解的方式进行部署。 注解虽然方便了开发人员,但是在后期会让维护和调试成本增加。为了方便后期维护,建议开发人员在部署Servlet时把@WebServlet的urlPatterns属性的值设置为Servlet类的名称。例如: @WebServlet(name = "secondServlet", urlPatterns = {"/secondServlet"}) public class SecondServlet extends HttpServlet { } @WebServlet @WebServlet用于将一个类声明为Servlet对象,该注解将会在部署时被Web容器处理,Web容器根据具体属性将相应的类部署为Servlet对象。该注解的常用属性如表5.1所示。 表5.1@WebServlet注解的常用属性 属性名类型描述 nameString指定Servlet的name属性,等价于<servletname>。如果没有显式指定,则该Servlet的取值即为类的全名 valueString[]该属性等价于urlPatterns属性,这两个属性不能同时使用 urlPatternsString[]指定一组Servlet的URL匹配模式,等价于<urlpattern>标记 loadOnStartupint指定Servlet的加载顺序,等价于<loadonstartup>标记 initParamsWebInitParam[]指定一组Servlet初始化参数,等价于<initparam>标记 以上所有属性都为可选属性,但value或urlPatterns通常是必需的,且二者不能共存,如果同时指定,通常忽略value的取值。用户可使用简化注解,例如@WebServlet("/secondServlet"),其中“/secondServlet”为请求的URL,即urlPatterns属性的值。 【例52】基于注解的Servlet类——SecondServlet。 例52的SecondServlet.java的代码如下: package servlet; import java.io.IOException; import java.io.PrintWriter; import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; //建议urlPatterns的值和类名一样,以方便维护,可使用@WebServlet("/secondServlet")简化注解 @WebServlet(name = "secondServlet", urlPatterns = {"/secondServlet"}) public class SecondServlet extends HttpServlet { private static final long serialVersionUID = 1L; public void init(ServletConfig config) throws ServletException { } protected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { //设置响应的内容类型 response.setContentType("text/html;charset=UTF-8"); //取得输出对象 PrintWriter out = response.getWriter(); out.println("htmlbody"); //在浏览器中显示"第二个Servlet类" out.println("第二个Servlet类"); out.println("/body/html"); } } 在SecondServlet.java的代码中使用“@WebServlet(name="secondServlet", urlPatterns= {"/secondServlet"})”部署以后,就不必在web.xml中部署相应的<servlet>和<servletmapping>元素了,Web容器会在部署时根据指定的属性将该类发布为Servlet对象。“@WebServlet(name="secondServlet", urlPatterns={"/secondServlet"})”等价的 web.xml 部署形式如下: servlet servlet-namesecondServlet/servlet-name servlet-classservlet.SecondServlet/servlet-class /servlet servlet-mapping servlet-namesecondServlet/servlet-name url-pattern/secondServlet/url-pattern /servlet-mapping @WebInitParam @WebInitParam注解通常不单独使用,而是配合@WebServlet和@WebFilter(在第6章中讲解)使用。它的作用是为Servlet或Filter指定初始化参数,这等价于web.xml中<servlet>的<initparam>子标记。@WebInitParam注解的常用属性如表5.2所示。 表5.2@WebInitParam注解的常用属性 属性名类 型是否可选描述 nameString否指定参数的名称,等价于<paramname> valueString否指定参数的值,等价于<paramvalue> @WebInitParam注解的示例代码如下: @WebServlet(name="thirdServlet", urlPatterns={"/thirdServlet"},initParams={@WebInitParam(name="firstParam", value="one"),@WebInitParam(name="secondParam", value="two")}) 5.2.3实践环节——@WebServlet的应用 首先将web.xml文件中有关Servlet部署的代码删除,然后使用注解的方式部署例51的Servlet,并运行部署后的Servlet。 扫一扫 视频讲解 5.3Servlet的生命周期 一个Servlet对象的生命周期主要由以下3个过程组成。 (1) 初始化Servlet对象: 当Servlet对象第一次被请求加载时,服务器会创建一个Servlet对象,该Servlet对象调用init()方法完成必要的初始化工作。 (2) service()方法响应请求: 创建的Servlet对象调用service()方法响应客户的请求。 (3) Servlet对象死亡: 当服务器关闭时,Servlet对象调用destroy()方法使自己消亡。 从上面3个过程来看,init()方法只能被调用一次,即在Servlet第一次被请求加载时调用。当客户端请求Servlet服务时,服务器将启动一个新的线程,在该线程中Servlet对象调用service()方法响应客户端的请求。那么当多客户端请求Servlet服务时服务器会怎么处理?服务器会为每个客户端启动一个新的线程,在每个线程中Servlet对象调用service()方法响应客户端的请求。也就是说,每个客户端请求都会导致service()方法被调用执行,分别运行在不同的线程中。 【例53】Servlet接口的init()、service()和destroy()方法。 例53的ThirdServlet.java的代码如下: package servlet; import java.io.IOException; import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebInitParam; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @WebServlet(name = "thirdServlet", urlPatterns = {"/thirdServlet"}, initParams={@WebInitParam(name = "firstParam", value = "one"), @WebInitParam(name = "secondParam", value = "two")}) public class ThirdServlet extends HttpServlet { private static final long serialVersionUID = 1L; private String first = null; private String second = null; private static int count = 0; public void init(ServletConfig config) throws ServletException { //获取firstParam参数的值 first = config.getInitParameter("firstParam"); second = config.getInitParameter("secondParam"); System.out.println("第一个参数值:" + first); System.out.println("第二个参数值:" + second); } protected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { count ++ ; System.out.println("您是第" + count + "个客户端请求该Servlet!"); } public void destroy() { } } 在ThirdServlet的init()方法中,通过ServletConfig的config对象调用getInitParameter()方法来获取参数的值。当请求3次该Servlet以后,在IntelliJ IDEA的控制台中打印出如图5.2所示的结果。 图5.2请求3次thirdServlet的结果 从图5.2可以看出,不管请求几次thirdServlet,它的init()方法只执行一次,而service()方法每请求一次就执行一次。 扫一扫 视频讲解 5.4通过JSP页面访问Servlet 用户可以通过JSP页面的表单或超链接请求某个Servlet。通过JSP页面访问Servlet的好处是,JSP页面负责页面的静态信息处理,动态信息处理由Servlet完成。 通过表单访问Servlet 假设在JSP页面中有如下表单: form action="isLogin" method="post" … /form 那么该表单的处理程序(action)就是一个Servlet,在为该Servlet部署时,@WebServlet 的urlPatterns属性值为“{"/isLogin"}”。 通过超链接访问Servlet 在JSP页面中可以单击超链接访问Servlet对象,也可以通过超链接向Servlet提交信息。例如“<a href="loginServlet?user=taipingle&&pwd=zhenzuile">查看用户名和密码</a>”,“查看用户名和密码”这个超链接将user=taipingle和pwd=zhenzuile两个信息提交给Servlet处理。 【例54】编写JSP页面login.jsp,在该页面中通过表单向urlPatterns为“{"/loginServlet"}”的Servlet(由LoginServlet类负责创建)提交用户名和密码,Servlet负责判断输入的用户名和密码是否正确,并把判断结果返回。页面的运行效果如图5.3所示。 图5.3通过JSP页面访问Servlet login.jsp的代码如下: %@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"% !DOCTYPE html html head meta charset="UTF-8" titlelogin.jsp/title /head body form action="loginServlet" method="post" table tr td用户名:/td tdinput type="text" name="user"//td /tr tr td密 码:/td tdinput type="password" name="pwd"//td /tr tr tdinput type="submit" value="提交"//td tdinput type="reset" value="重置"//td /tr /table /form /body /html LoginServlet.java的代码如下: package servlet; import java.io.IOException; import java.io.PrintWriter; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @WebServlet(name = "loginServlet", urlPatterns = {"/loginServlet"}) public class LoginServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); String name = request.getParameter("user"); //获取客户提交的信息 String password = request.getParameter("pwd"); //获取客户提交的信息 out.println("htmlbody"); if(name == null || name.length() == 0){ out.println("请输入用户名"); } else if(password == null || password.length() == 0){ out.println("请输入密码"); } else if(name.length() 0 && password.length() 0){ if(name.equals("zhangsan") && password.equals("lisi")){ out.println("信息输入正确"); }else{ out.println("信息输入错误"); } } out.println("/body/html"); } } 扫一扫 视频讲解 5.5doGet()和doPost()方法 当服务器接收到一个Servlet请求时会产生一个新线程,在这个线程中让Servlet对象调用service()方法为请求做出响应。service()方法首先检查HTTP请求类型(get或post),并在service()方法中根据用户的请求方式对应地调用doGet()或doPost()方法。 当HTTP请求类型为get方式时,service()方法调用doGet()方法响应用户请求; 当HTTP请求类型为post方式时,service()方法调用doPost()方法响应用户请求,因此在Servlet类中没有必要重写service()方法,直接继承即可。 在Servlet类中通过重写doGet()或doPost()方法来响应用户的请求,这样可以增加响应的灵活性,同时减轻服务器的负担。 在一般情况下,如果不论用户的请求类型是get还是post,服务器的处理过程完全相同,那么可以只在doPost()方法中编写处理过程,而在doGet()方法中调用doPost()方法; 或只在doGet()方法中编写处理过程,而在doPost()方法中调用doGet()方法。 【例55】编写JSP页面inputLader.jsp,在该页面中使用表单向urlPatterns为“{"/getLengthOrAreaServlet"}”的Servlet提交矩形的长与宽。Servlet(由GetLengthOrAreaServlet负责创建)的处理手段依赖表单提交数据的方式,当提交方式为get时,Servlet计算矩形的周长; 当提交方式为post时,Servlet计算矩形的面积。页面的运行效果如图5.4所示。 图5.4计算矩形的面积和周长 inputLader.jsp的代码如下: %@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"% !DOCTYPE html html head meta charset="UTF-8" titleinputLader.jsp/title /head body h2输入矩形的长和宽,提交给Servlet(post方式)求面积:/h2 form action="getLengthOrAreaServlet" method="post" 长:input type="text" name="length"/br/ 宽:input type="text" name="width"/br/ input type="submit" value="提交"/ /form br/ h2输入矩形的长和宽,提交给Servlet(get方式)求周长:/h2 form action="getLengthOrAreaServlet" method="get" 长:input type="text" name="length"/br/ 宽:input type="text" name="width"/br/ input type="submit" value="提交"/ /form /body /html GetLengthOrAreaServlet.java的代码如下: package servlet; import java.io.IOException; import java.io.PrintWriter; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @WebServlet(name = "getLengthOrAreaServlet", urlPatterns = {"/getLengthOrAreaServlet"}) public class GetLengthOrAreaServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); String l = request.getParameter("length"); String w = request.getParameter("width"); out.println("htmlbody"); double m = 0,n = 0; try{ m = Double.parseDouble(l); n = Double.parseDouble(w); out.println("矩形的周长是:" + ( m + n ) * 2); }catch(NumberFormatException e){ out.println("请输入数字字符!"); } out.println("/body/html"); } protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); String l = request.getParameter("length"); String w = request.getParameter("width"); out.println("htmlbody"); double m = 0, n = 0; try{ m = Double.parseDouble(l); n = Double.parseDouble(w); out.println("矩形的面积是:" + m * n); }catch(NumberFormatException e){ out.println("请输入数字字符!"); } out.println("/body/html"); } } 扫一扫 视频讲解 5.6重定向与转发 重定向是将用户从当前JSP页面或Servlet定向到另一个JSP页面或Servlet,以前的request中存放的信息全部失效,并进入一个新的request作用域; 转发是将用户对当前JSP页面或Servlet的请求转发给另一个JSP页面或Servlet,以前的request中存放的信息不会失效。 5.6.1重定向 在Servlet中通过调用HttpServletResponse类中的sendRedirect(String location)方法来实现重定向,重定向的目标页面或Servlet(由参数location指定)无法从以前的request对象中获取用户提交的数据。 5.6.2转发 使用jakarta.servlet.RequestDispatcher对象可以将用户对当前JSP页面或Servlet的请求转发给另一个JSP页面或Servlet,实现转发需要以下两个步骤。 获取RequestDispatcher对象 在当前JSP页面或Servlet中使用request对象调用public RequestDispatcher getRequestDispatcher(String url)方法返回一个RequestDispatcher对象,其中参数url就是要转发的JSP页面或Servlet的地址。例如: RequestDispatcher dis=request.getRequestDispatcher("dologin"); 使用RequestDispatcher对象调用forward()方法实现转发 在获取RequestDispatcher对象之后,就可以使用该对象调用public void forward(ServletRequest request,ServletResponse response)方法将用户对当前JSP页面或Servlet的请求转发给RequestDispatcher对象所指定的JSP页面或Servlet。例如: dis.forward(request,response); 转发是服务器行为,重定向是客户端行为。其具体工作流程如下。 转发过程: 客户浏览器发送HTTP请求,Web服务器接受此请求,调用内部的一个方法在容器内部完成请求处理和转发动作,将目标资源发送给客户。在这里转发的路径必须是同一个Web容器下的URL,其不能转发到其他的Web路径上,中间传递的是自己容器内的request。在客户浏览器的地址栏中显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。 重定向过程: 客户浏览器发送HTTP请求,Web服务器接受后发送302状态码响应及对应的新的location给客户浏览器,客户浏览器发现是302响应,则自动发送一个新的HTTP请求,请求URL是新的location地址,服务器根据此请求寻找资源并发送给客户。在这里location可以重定向到任意URL,既然是浏览器重新发出了请求,则就没有什么request传递的概念了。在客户浏览器的地址栏中显示的是其重定向的路径,客户可以观察到地址的变化。重定向行为是浏览器至少做了两次访问请求。 【例56】编写JSP页面redirectForward.jsp,在该JSP页面中通过表单向urlPatterns为“{"/redirectForwardServlet"}”的Servlet(由RedirectForwardServlet负责创建)提交用户名和密码。如果用户输入的数据不完整,redirectForwardServlet将用户重定向到redirectForward.jsp页面; 如果用户输入的数据完整,redirectForwardServlet将用户对redirectForward.jsp页面的请求转发给urlPatterns为“{"/showServlet"}”的Servlet(由ShowServlet负责创建),showServlet显示用户输入的信息。 redirectForward.jsp的代码如下: %@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"% !DOCTYPE html html head meta charset="UTF-8" titleredirectForward.jsp/title /head body form action="redirectForwardServlet" method="post" table tr td用户名:/td tdinput type="text" name="user"//td /tr tr td密 码:/td tdinput type="password" name="pwd"//td /tr tr tdinput type="submit" value="提交"//td tdinput type="reset" value="重置"//td /tr /table /form /body /html RedirectForwardServlet.java的代码如下: package servlet; import java.io.IOException; import jakarta.servlet.RequestDispatcher; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @WebServlet(name = "redirectForwardServlet", urlPatterns = {"/redirectForwardServlet"}) public class RedirectForwardServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { String name = request.getParameter("user"); String password = request.getParameter("pwd"); if (name == null || name.length() == 0) { //使用response调用sendRedirect()方法重定向到redirectForward.jsp response.sendRedirect("redirectForward.jsp"); } else if (password == null || password.length() == 0) { response.sendRedirect("redirectForward.jsp"); } else if (name.length() 0 && password.length() 0) { //转发 RequestDispatcher dis = request.getRequestDispatcher("showServlet"); dis.forward(request, response); } } } ShowServlet.java的代码如下: package servlet; import java.io.IOException; import java.io.PrintWriter; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @WebServlet(name = "showServlet", urlPatterns = {"/showServlet"}) public class ShowServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); String name = request.getParameter("user"); String password = request.getParameter("pwd"); out.println("您的用户名是:" + name); out.println("br您的密码是:" + password); } } 5.6.3实践环节——登录验证 编写登录页面login_1.jsp,在该JSP页面中通过表单向urlPatterns为“{"/loginServlet_1"}”的Servlet(由LoginServlet_1类负责创建)提交用户名和密码。如果用户输入的数据不完整,loginServlet_1将用户重定向到login_1.jsp页面; 如果用户输入的数据完整并正确(用户名为“zhangsan”,密码为“123”),loginServlet_1将用户的请求转发给loginSuccess_1.jsp页面,loginSuccess_1.jsp页面显示用户输入的信息。 扫一扫 视频讲解 5.7在Java Servlet中使用session 在Servlet中获取当前请求的会话对象可以通过调用HttpServletRequest的getSession()方法实现。例如: HttpSession session = request.getSession(true); //若存在会话返回该会话,否则新建一个会话 或 HttpSession session = request.getSession(false); //若存在会话返回该会话,否则返回null 在通常情况下,通过第一种方式获取session,即指定getSession()的参数为true,true是默认值,也就是request.getSession(true)等同于request.getSession()。 【例57】编写JSP页面useSession.jsp,在该页面中通过表单向名为useSession的Servlet对象(由UseSessionServlet类负责创建)提交用户名,useSession将用户名存入用户的session对象中,然后用户请求另一个Servlet对象showName(由ShowNameServlet类负责创建),showName从用户的session对象中取出存储的用户名,并显示在浏览器中。页面的运行效果如图5.5所示。 图5.5在Servlet中使用session useSession.jsp的代码如下: %@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"% !DOCTYPE html html head meta charset="UTF-8" titleuseSession.jsp/title /head body form action="sendMyName" method="post" table tr td用户名:/td tdinput type="text" name="user"//td /tr tr tdinput type="submit" value="提交"//td /tr /table /form /body /html UseSessionServlet.java的代码如下: package servlet; import java.io.*; import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; @WebServlet(name = "useSession", urlPatterns = {"/sendMyName"}) public class UseSessionServlet extends HttpServlet { private static final long serialVersionUID = 1L; public void init(ServletConfig config) throws ServletException { super.init(config); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); String name = request.getParameter("user"); if (null == name || name.trim().length() == 0) { response.sendRedirect("useSession.jsp"); } else { HttpSession session = request.getSession(true); session.setAttribute("myName", name); out.println("htmbody"); out.println("您请求的Servlet对象是:" + getServletName()); out.println("br您的会话ID是:" + session.getId()); out.println("br请单击请求另一个Servlet:"); out.println("bra href='showMyName'请求另一个Servlet/a"); out.println("/body/htm"); } } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } } ShowNameServlet.java的代码如下: package servlet; import java.io.*; import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; @WebServlet(name = "showName", urlPatterns = {"/showMyName"}) public class ShowNameServlet extends HttpServlet { private static final long serialVersionUID = 1L; public void init(ServletConfig config) throws ServletException { super.init(config); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); HttpSession session = request.getSession(true); String name = (String) session.getAttribute("myName"); out.println("htmbody"); out.println("您请求的Servlet对象是:" + getServletName()); out.println("br您的会话ID是:" + session.getId()); out.println("br您的会话中存储的用户名是:" + name); out.println("/body/htm"); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } } 扫一扫 视频讲解 5.8基于Servlet的MVC模式 本节将重点介绍基于Servlet的MVC模式。 5.8.1MVC模式 MVC的概念 MVC是Model、View、Controller的缩写,分别代表Web应用程序中的3种职责。 (1) 模型: 用于存储数据以及处理用户请求的业务逻辑。 (2) 视图: 向控制器提交数据,显示模型中的数据。 (3) 控制器: 根据视图提出的请求判断将请求和数据交给哪个模型处理以及将处理后的有关结果交给哪个视图更新显示。 基于Servlet的MVC模式的具体实现 基于Servlet的MVC模式的具体实现如下。 (1) 模型: 一个或多个JavaBean对象,用于存储数据(实体模型,由JavaBean类创建)和处理业务逻辑(业务模型,由一般的Java类创建)。 (2) 视图: 一个或多个JSP页面,向控制器提交数据和为模型提供数据显示,JSP页面主要使用 HTML标记和JavaBean标记来显示数据。 (3) 控制器: 一个或多个Servlet对象,根据视图提交的请求进行控制,即将请求转发给处理业务逻辑的JavaBean,并将处理结果存放到实体模型JavaBean中,输出给视图显示。 基于Servlet的MVC模式的工作流程如图5.6所示。 图5.6JSP中的MVC模式 5.8.2使用JSP、Servlet和JavaBean实现MVC 【例58】使用MVC模式实现简单的用户登录验证程序,其中包括实体模型User、业务模型UserCheck、控制器LoginCheckServlet和两个视图页面,即登录页面和登录成功页面。 定义实体模型来表示数据 User类用于创建实体模型存储用户信息,代码如下: package dto; public class User { private String name; private String pwd; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } } 定义业务模型来处理业务 UserCheck类用于判断用户名和密码是否正确,代码如下: package service; import dto.User; public class UserCheck { //验证登录 public boolean validate(User user) { if (user != null && user.getName().equals("JSPMVC")) { if (user.getPwd().equals("MVC")) { return true; } return false; } return false; } } 编写Servlet处理请求 LoginCheckServlet用于完成请求控制,代码如下: package servlet; import java.io.IOException; import jakarta.servlet.RequestDispatcher; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import service.UserCheck; import dto.User; @WebServlet(name = "loginCheckServlet", urlPatterns = {"/loginCheckServlet"}) public class LoginCheckServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { String name = request.getParameter("name"); String pwd = request.getParameter("pwd"); User user = new User(); //实例化实体模型user user.setName(name); //把数据存储在模型user中 user.setPwd(pwd); //把数据存储在模型user中 UserCheck uc = new UserCheck(); //实例化业务模型userCheck if (uc.validate(user)) { //把装有数据的实体模型user存储在request范围内 request.setAttribute("user", user); RequestDispatcher dis = request .getRequestDispatcher("loginSuccess.jsp"); dis.forward(request, response); } else { response.sendRedirect("loginCheck.jsp"); } } } 编写视图 登录页面loginCheck.jsp的代码如下: %@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"% !DOCTYPE html html head meta charset="UTF-8" titleloginCheck.jsp/title /head body form action="loginCheckServlet" method="post" table tr td用户名:/td tdinput type="text" name="name"//td /tr tr td密 码:/td tdinput type="password" name="pwd"//td /tr tr tdinput type="submit" value="提交"//td tdinput type="reset" value="重置"//td /tr /table /form /body /html 登录成功页面loginSuccess.jsp的代码如下: %@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"% !DOCTYPE html html head meta charset="UTF-8" titleloginSuccess.jsp/title /head body jsp:useBean id="user" type="dto.User" scope="request"/ 恭喜jsp:getProperty property="name" name="user"/登录成功! /body /html 5.8.3模型周期 在基于Servlet的MVC模式中,控制器Servlet创建的实体模型JavaBean也涉及生命周期,生命周期分别为request、session和application。下面以例58中的实体模型user来讨论这3种生命周期的用法。 request周期的模型 使用request周期的模型一般需要以下几个步骤: 1) 创建模型并把数据保存到模型中 在Servlet中需要如下代码: User user = new User(); //实例化模型user user.setName(name); //把数据存储在模型user中 user.setPwd(pwd); //把数据存储在模型user中 2) 将模型保存到request对象中并转发给JSP视图 在Servlet中需要如下代码: request.setAttribute("user", user);//把装有数据的模型user输出给视图页面loginSuccess.jsp RequestDispatcher dis=request.getRequestDispatcher("loginSuccess.jsp"); dis.forward(request, response); request.setAttribute("user", user)这句代码指定了查找JavaBean的关键字,并决定了JavaBean的生命周期为request。 3) 视图的更新 Servlet所转发的页面,比如loginSuccess.jsp页面,必须使用useBean标记获取Servlet所创建的JavaBean对象(视图不负责创建JavaBean)。在JSP页面中需要使用如下代码: jsp:useBean id="user" type="dto.User" scope="request"/ jsp:getProperty property="name" name="user"/ 标记中的id就是Servlet所创建的模型JavaBean,它和request对象中的关键字对应。因为在视图中不创建JavaBean对象,所以在useBean标记中使用type属性而不使用class属性。useBean标记中的scope必须和存储模型时的范围(request)一致。 session周期的模型 使用session周期的模型一般需要以下几个步骤: 1) 创建模型并把数据保存到模型中 在Servlet中需要如下代码: User user = new User(); //实例化模型user user.setName(name); //把数据存储在模型user中 user.setPwd(pwd); //把数据存储在模型user中 2) 将模型保存到session对象中并转发给JSP视图 在Servlet中需要如下代码: session.setAttribute("user", user);//把装有数据的模型user输出给视图页面loginSuccess.jsp RequestDispatcher dis=request.getRequestDispatcher("loginSuccess.jsp"); dis.forward(request, response); session.setAttribute("user", user)这句代码指定了查找JavaBean的关键字,并决定了JavaBean的生命周期为session。 3) 视图的更新 Servlet所转发的页面,比如loginSuccess.jsp页面,必须使用useBean标记获取Servlet所创建的JavaBean对象(视图不负责创建JavaBean)。在JSP页面中需要使用如下代码: jsp:useBean id="user" type="dto.User" scope="session"/ jsp:getProperty property="name" name="user"/ 标记中的id就是Servlet所创建的模型JavaBean,它和session对象中的关键字对应。因为在视图中不创建JavaBean对象,所以在useBean标记中使用type属性而不使用class属性。useBean标记中的scope必须和存储模型时的范围(session)一致。 注意: 对于生命周期为session的模型,Servlet不仅可以使用RequestDispatcher对象转发给JSP页面,还可以使用response的重定向方法(sendRedirect())定向到JSP页面。 application周期的模型 使用application周期的模型一般需要以下几个步骤: 1) 创建模型并把数据保存到模型中 在Servlet中需要如下代码: User user=new User (); //实例化模型user user.setName(name); //把数据存储在模型user中 user.setPwd(pwd); //把数据存储在模型user中 2) 将模型保存到application对象中并转发给JSP视图 在Servlet中需要如下代码: application.setAttribute("user", user); //把装有数据的模型user输出给视图页面loginSuccess.jsp RequestDispatcher dis=request.getRequestDispatcher("loginSuccess.jsp"); dis.forward(request, response); application.setAttribute("user", user)这句代码指定了查找JavaBean的关键字,并决定了JavaBean的生命周期为application。 3) 视图的更新 Servlet所转发的页面,比如loginSuccess.jsp页面,必须使用useBean标记获取Servlet所创建的JavaBean对象(视图不负责创建JavaBean)。在JSP页面中需要使用如下代码: jsp:useBean id="user" type="dto.User" scope="application"/ jsp:getProperty property="name" name="user"/ 标记中的id就是Servlet所创建的模型JavaBean,它和application对象中的关键字对应。因为在视图中不创建JavaBean对象,所以在useBean标记中使用type属性而不使用class属性。useBean标记中的scope必须和存储模型时的范围(application)一致。 注意: 对于生命周期为session或application的模型,Servlet不仅可以使用RequestDispatcher对象转发给JSP页面,还可以使用response的重定向方法(sendRedirect())定向到JSP页面。 5.8.4实践环节——四则运算 模仿例58,使用基于Servlet的MVC模式设计一个Web应用,要求如下: 用户通过JSP页面inputNumber.jsp输入两个操作数,并选择一种运算符,单击“提交”按钮后,调用HandleComputer.java这个Servlet。在HandleComputer中首先获取用户输入的数字和运算符并将这些内容存入实体模型(由Computer.java创建)中,然后调用业务模型(由CalculateBean.java创建)进行计算并把结果存入实体模型中,在showResult.jsp中调用JavaBean显示计算的结果。 5.9本章小结 本章使用了Servlet的注解机制部署Servlet,简化了Servlet的开发流程,使得web.xml部署描述文件从Servlet 3.0开始不再是必选的。 习 题 5