第3章Servlet基础 视频讲解 3.1Servlet和JSP Web服务器接收到HTTP请求,处理完毕后会向客户端返回一个HTTP响应。Web服务器接收客户端的请求有两种: 一种是静态页面请求,客户端请求的页面中没有动态的内容需要处理,这些静态的页面直接作为响应返回。此时只需要能够解析HTTP的Web服务器(如Nginx、Apache、IIS等)即可。第二种是动态请求,客户端所请求的页面,需要在服务器端委托给一些应用程序进行处理,从而形成动态页面,最后作为HTTP响应返回。此时需要服务器不仅能处理HTTP,还需要具备处理这些动态请求的能力,这种服务器称作Web应用服务器。之前示例中使用的Tomcat,就是能够处理Servlet以及JSP动态页面的服务器或者称之为Java Web容器。 Servlet以及JSP页面都是运行在服务器上的程序,并能生成动态的内容返回客户端,那么二者有什么联系和区别呢? 从技术产生的先后顺序看,Servlet技术在前,JSP技术在后。在早期的Web应用系统中,动态请求是由Web服务器转发给CGI(Common Gateway Interface,公共网关接口)程序进行处理的,CGI处理完毕后将结果拼接成HTML格式的文档,并返还给Web服务器,再通过Web服务器将响应返回给用户。CGI程序一般由C、C++、Perl或者其他脚本语言编写,但对于每一个客户端的请求,CGI都开启一个新的进程进行处理,对于服务器而言负担较重,执行效率低。因此Sun公司推出了基于Java的Servlet技术,Servlet本质上来说是一个Java类,可以运行在Tomcat这样的容器中。对于用户的请求,Servlet以线程的形式进行处理,执行效率更高,同时功能更为强大,对于HTML请求数据的提取和处理、会话跟踪、Cookie设置等都有对应的方法。 Servlet虽然在处理请求上非常方便,但是对于响应结果的显示却仍然采用CGI的方法,通过代码打印输出的方式去拼接HTML文档,导致如果想生成较为复杂的页面,代码量将急剧增加,同时也不便于页面整体效果的展示。 因此,Sun公司提出了JSP技术,采用HTML模板+嵌入Java代码以及标签的形式,能够简化响应页面输出的代码量,不过JSP的底层实现仍是基于Servlet。在项目Chapt_01中,已经编写过第一个JSP页面,即index.jsp。当项目运行时,第一次访问index.jsp页面后,该JSP页面编译为对应的Servlet类,如 图31所示。Servlet类存放在Tomcat服务器的work目录下,路径为\apachetomcat9.0.33\work\Catalina\localhost\Chapt_01\org\apache\jsp。可以发现index.jsp已经被转化为一个Java类,同时也生成了对应class字节码文件,若再访问index.jsp页面,则直接读取字节码文件即可。 图31JSP页面编译为对应的Servlet类 JSP页面通过模板的形式方便了页面内容的输出,但如果JSP页面中混杂了过多的Java代码,将处理业务逻辑的部分都放在页面中,同样导致了代码量过大,且不利于开发人员编写和维护。因此由于两种技术各有其长处,JSP技术的出现并没有取代Servlet,二者可以并存合作,在开发中发挥各自的优势。 由于Servlet更偏向于底层的实现,因此本书先讲解Servlet技术的原理,然后再介绍JSP的使用。关于Servlet和JSP在具体开发中的使用场景,在后续章节中还会继续讨论。 3.2Tomcat服务器原理 在学习Servlet之前,先了解作为容器的Tomcat服务器的工作原理。 视频讲解 3.2.1Tomcat体系结构 Tomcat是基于组件的Web应用服务器,在2.1.2小节介绍了Tomcat服务器的目录结构,在安装目录下的conf文件夹中,server.xml文件是整个Tomcat服务器的配置文件。该配置文件给出了整个Tomcat服务器中各组件的设置,每个组件作为XML文件中的标签元素(为了方便讲解,只列出了主要的组件节点),大致结构如下: … … 下面介绍Tomcat服务器中重要组件的作用以及相互之间的关系。 配置文件中的根节点是Server,代表顶级服务器。该节点包含了port="8005" shutdown="SHUTDOWN"两个属性,表示服务器通过8005端口号监听和关闭Tomcat服务器的请求。Server节点包含若干个Listener、GlobaNamingResources和Service等子节点。 (1) Listener节点。该节点表示服务器运行时状态监听的配置,主要监听服务器是否会内存泄露、线程安全以及日志等信息。 (2) GlobaNamingResources节点。该节点表示全局资源的配置,比如指定Tomcat服务器用户信息,这些信息存放在conf目录下的tomcatusers.xml文件中。 (3) Service节点。该节点表示对外提供的应用服务,至少存在一个默认名称为Catalina的Service节点。Service节点又包含若干个Connector和一个Engine组件。 ① Connector: Tomcat服务器的核心组件。其是负责客户端交互的连接器组件,负责接收用户请求并交给Engine组件处理,以及将处理完毕后的响应返还给客户。可以有多个Connector,并设置该Connector来接收客户请求的端口号(如默认的8080),采用的HTTP版本,以及HTTPS转发端口号等。 ② Engine: Tomcat服务器的核心组件。其是负责处理用户请求的组件,有defaultHost和name两个属性值,其中defaultHost表示默认的虚拟主机名称。该组件下又包含若干Host元素和Realm元素,至少有一个Host元素的name属性和Engine的defaultHost值对应。在Host元素下的Context docBase 元素则定义了一个实际的Web项目。Relam元素则用于安全管理的配置,一般与tomcatuesrs.xml配合使用。 对于Server、Listener、GlobaNamingResources等元素,如果没有特殊需要,一般不需要修改其默认配置,以免影响服务器的正常运行。 视频讲解 3.2.2Tomcat核心组件 Tomcat可以根据需求,通过设置不同的监听端口配置多个Connector,当连接器指定的端口号监听到客户端发送过来的TCP请求后,将分别创建一个request和response对象,然后新建一个线程,将request和response对象传送给Engine组件,并等待处理结果,获得响应后,将响应返还给客户端。 Engine组件可以指定多个虚拟主机Host组件,Host可以配置以下3个属性。 (1) appBase属性。Web项目的部署路径,在2.1.2节中设置在webapps路径下。 (2) autoDeploy属性。项目是否自动部署,取值为ture或者false,ture表示自动。 (3) name属性。虚拟主机名称,取值localhost表示本机,刚好对应Engine元素的defaultHost的取值。 在Host组件下又可以具体指定Context组件,实际上对应着已经在Tomcat服务器下运行的Web项目。每当有新的项目部署到服务器时,都会在Host组件下生成一个新的Context元素进行配置。例如: 说明: docBase属性设置了Chapt_01项目的根目录; path属性表示项目访问的路径,即http://localhost:8080/Chatp_01/xxx。reloadable=true,表示服务器会检测项目文件的变动,Tomcat服务器在运行状态下会监视WEBINF/classes和WEBINF/lib目录下class文件的改动,如果监测到class文件有变动,服务器会自动重新加载Web应用。 Context组件实际上就是运行Servlet的基础容器,当用户访问该Web应用项目时,所有的请求都需要到该Context环境(即该项目)下去寻找对应的Servlet类去处理。 3.3Servlet的编写 3.3.1Servlet的创建 视频讲解 在Eclipse中新建一个名为Chapt_03的动态Web项目,由于Servlet是一个Java类,所以需要在项目的src目录下建立,因此需在src目录下新建一个com.test.servlet的包。在Eclipse中可以通过模板来创建Servlet。Servlet的创建步骤如下所述。 (1) 右击com.test.servlet包,在弹出的菜单中选择New→Other菜单项,在弹出的对话框中,找到Web组件下的Servlet选项,选择新建Servlet类,如图32所示,单击Next按钮。 图32新建Servlet类 (2) Servlet命名,如图33所示。在Create Servlet对话框中的Class name文本框中输入FirstServlet,其他选项按照默认的即可,然后单击Next按钮。 图33Servlet命名 (3) 设置Servlet参数,如图34所示。在弹出的对话框中对Servlet的Initialization parameters以及URL mappings参数进行设置,其中Initialization parameters表示Servlet类的初始参数,URL mappings表示访问该Servlet的映射路径,默认设置为/FirstServlet。如果需要设置初始参数,以及添加或者修改映射路径,就可以单击Initialization parameters列表或者URL mappings列表右边的Add按钮进行配置。此处先按照默认设置即可,单击Next按钮。 图34Servlet 参数设置 (4) 选择Servlet重写方法,如图35所示。在弹出的对话框中选中FirstServlet类需要创建的方法,模板默认会有一个父类构造器,以及继承父类的doGet和doPost方法,也可以选中其他需要继承的父类抽象方法。此处采用默认配置,单击Finish按钮,即完成FirstServlet类的创建。 图35选择Servlet重写方法 此时在项目下已经生成了FirstServlet类,代码如下: import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/FirstServlet") public class FirstServlet extends HttpServlet { private static final long serialVersionUID = 1L; public FirstServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().append("Served at: ").append(request. getContextPath()); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } 从代码中可以看出,FirstServlet类引入了javax.servlet.http包中的一些类。除了父类HttpServlet类以外,还包括HttpServletRequest以及 HttpServletResponse,分别表示请求和响应,它们的实例化对象request和response分别作为doGet和doPost方法的参数。 此外,FirstServlet类还引入了javax.servlet.annotation.WebServlet类,这个类是用于注解的类,可以看到在FirstServlet类上包含有@WebServlet("/FirstServlet")的一行注解代码。这是因为在新建项目时选择的Dynamic Web Module Version为3.1,因此项目采用的是Servlet 3.1规范,在Servlet 3.0以上版本中,默认是使用注解对Servlet进行配置。@WebServlet("/FirstServlet")这条注解语句,其实对应了在新建FirestServlet时配置的URL mapping。 如果项目选择的是Servlet 2.5及以下版本,就在Servlet新建后,需要在项目的web.xml配置文件的节点下,编写如下代码以完成FirstServlet的配置。 FirstServlet com.test.servlet.FirstServlet FirstServlet /FirstServlet 其中,servlet节点下有servletname和servletclass两个元素,其值分别对应Servlet设置的名称和对应的具体类; servletmapping节点下有servletname和urlpattern两个元素,配置了FirstServlet的访问路径。 通过模板创建Servlet,只需要在界面中设置参数,Eclipse就会自动生成对应的配置信息。无论采用注解还是在web.xml中配置,效果都是等同的。而且相同的Servlet配置只能选取一种方式,重复配置将会报错。当然,也可以采用手动的方式进行编写和修改,此时需要注意对应的配置语法和格式。在本书的演示中,均采用Servlet 3.1规范的注解方式。 视频讲解 3.3.2Servlet的运行 在创建完FirstServlet后,可进行如下操作来运行Servlet。 (1) 右击Chapt_03项目,在弹出的菜单中选择Run As→Run on Server,选择Tomcat 9服务器,单击Finish按钮,此时Chapt_03项目被部署到服务器中。 (2) 打开浏览器,输入网址http://localhost:8080/Chapt_03/FirstServlet,FirstServlet运行效果如图36所示。 图36FirstServlet运行效果 为什么会显示这样一行文本信息呢?实际上在访问FirstServlet时,Tomcat服务器按照以下步骤进行处理。 (1) 该请求中使用端口号为8080,因此会被一直监听8080端口号的Connector组件获取。 (2) Connector组件把请求交给Engine组件处理,并等待回应。 (3) Engine查找Host组件,找到匹配名字为localhost的虚拟主机。 (4) 在localhost主机上,查找Context组件,匹配到名字为Chapt_03的应用。 (5) 根据请求路径/FirstServlet,在Chapt_03下查找URL mapping配置,找到对应的FirstServlet类去处理。 (6) 构造HttpServletRequest对象和HttpServletResponse对象,作为参数传送给FirstServlet的doGet()方法,处理完毕后,将结果封装到HttpServletResponse对象中。 (7) Context将HttpServletResponse响应返回给Host。 (8) Host将响应返回给Engine。 (9) Engine将响应返回给Connector。 (10) Connector将响应结果返回给浏览器客户端。 从以上步骤看,最终页面的显示结果是来自FirstServlet的doGet()方法,在方法体内部只有一条语句: response.getWriter().append("Served at:").append(request.getContextPath()); 说明: response对象的getWriter()方法获取了一个输出流对象,向客户端进行文本的输出,后面的append()方法表示文本的追加输出,第二个append()方法里的参数,由request对象通过getContextPath()方法获取,表示请求的上下文Context对象路径,即/Chapt_03。因此,最终输出为Served at: /Chapt_03。 3.3.3Servlet的运行机制 当Servlet运行后,最终也会编译成字节码文件,存放在Tomcat服务器对应项目目录下,FirstServlet的字节码文件可以在Chapt_03\WEBINF\classes\com\test\servlet\路径下找到。那么当每次运行Servlet时,都会创建一个实例化对象吗?实际上在默认情况下,Servlet是以单例多线程的形式运行的。下面通过例31演示Servlet运行状态。 视频讲解 【例31】Servlet运行状态。 在Chapt_03项目下再新建一个名为SecondServlet的Servlet类,通过注解@WebServlet("/SecondServlet"),设置其映射路径为/SecondServlet,然后在其构造函数和doGet()方法中编写代码如下: public SecondServlet() { System.out.println("SecondServlet对象被实例化"); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("SecondServlet对象doGet被执行"); } 注意,新建Servlet或者JSP需要重启服务器,项目重新部署后才能访问。为模拟不同客户端访问同一个Servlet的场景,首先通过Firefox浏览器访问SecondServlet,然后观察Eclipse的Console输出内容,首次运行SecondServlet后的输出结果如图37所示。 图37首次运行SecondServlet后的输出结果 在不关闭服务器的情况下,使用Google 浏览器,再次访问SecondServlet后的输出结果如图38所示。 图38再次访问SecondServlet后的输出结果 由此可见,只有第一次访问Servlet时,运行了构造函数和doGet()方法,第二次访问只执行了doGet()方法,因此只有第一次运行时创建了对象,再次访问时并没有再次实例化对象。 Servlet是以多线程的方式去处理每一个请求的。即使多个用户访问同一个Servlet对象,服务器也会各自分配一个线程去运行doGet()方法。 在默认情况下,由于Servlet采用单例模式,因此存在线程安全方面的隐患,一般不要在类中直接定义成员变量。当然也可以通过实现SingleThreadModel接口,让每次请求都初始化一个Servlet去处理,此种方式本书暂不讨论。 3.3.4Servlet与生命周期 1. Servlet 在利用模板新建Servlet类时,默认需要继承HttpServlet这个抽象类。HttpServlet又是继承于GenericServlet这个抽象类,而GenericServlet抽象类又实现了Servlet以及ServleConfig两个接口。因此Servlet可以继承或者重写一些父类方法,这些方法将伴随着Servlet的整个生命周期。 (1) init(ServletConfig config): 该方法继承于GenericServlet类,是实现了Servlet接口声明的init()方法。该方法在Servlet类被加载后被调用, ServletConfig接口对象作为参数传递进来,从而可以获取一些初始化参数。如果有特殊初始参数配置方面的需求,可以重写该方法。 (2) service(HttpServletRequest request,HttpServletResponse response): 该方法根据request对象的getMethod()方法获取请求采用的方式,再去调用对应的do×××()方法。 (3) do×××(HttpServletRequest request, HttpServletResponse response): 包括doGet()、doPost()、doPut()、doDelete()、doHead()、doTrace()、doOptions()方法。这些方法处理不同请求方式的HTTP请求。 (4) destroy(): 当Servlet消亡时会调用该方法。当有特殊需求时,如需要清理某些设置及参数时可以重写该方法。 2. Servlet生命周期 Servlet生命周期的过程: 从第一次加载时调用init()方法,接着调用service()方法获取请求采用的方式,然后调用对应的do×××()方法,执行该方法完毕后返回响应的结果。当Servlet要消亡时(如关闭了服务器),则调用destroy()方法。 在实际开发中,一般只需要根据请求方式重写对应的do×××()方法即可。其中使用最多的是doGet()和doPost()方法,分别用于处理GET和POST类型的请求。 3.4Servlet处理请求与响应 3.4.1doGet()与doPost()方法 doGet()和doPost()方法分别处理GET和POST两种发送方式的请求,两种方法的应用场景有所区别。 GET方式一般针对页面及资源的请求。如访问超链接或者通过URL进行参数传值,以及表单默认的提交,均采用GET方式进行请求。 POST方式一般用于向服务器提交数据,例如当表单method属性设置为POST时,则表单采用POST方式进行提交。 GET方式传递的值直接放在请求行中,与网址内容一起进行编码。POST传递的值则放在请求体中。 doGet()和doPost()方法都包含HttpServletRequest和HttpServletResponse类型的参数,通过request对象获取请求参数,进行处理后,再利用response对象返回响应。在编写代码时,只需在doGet()方法体内编写处理请求的代码即可,然后在doPost()方法内调用doGet()方法。 3.4.2rqequest基本信息的获取 request对象提供了下面的方法用于获取请求中的一些重要信息。 (1) String getMethod()方法: 获取请求方式,如GET或者POST。 (2) String getRequestURI()方法: 获取请求的URI(Uniform Resource Identifier, 统一资源标志符)。 (3) String getProtocol()方法: 获取请求采用的协议。 (4) String getServerPort()方法: 获取请求服务器端口号。 (5) String getServerName()方法: 获取请求服务器的名称。 (6) String getContextPath()方法: 获取请求的上下文路径。 (7) String getRemoteAddr()方法: 获取发送请求的客户端IP地址。 以上信息在某些应用场景中需要用到,例如获取上下文路径可以用于绝对地址的拼接,而获取客户端IP地址则可以记录请求日志信息,甚至可以设置黑名单禁用部分IP地址。下面通过例32演示request对象基本信息的获取过程。 视频讲解 【例32】request对象基本信息的获取过程。 新建一个Servlet类,取名为RequestInfoServlet,通过注解@WebServlet("/RequestInfoServlet")设置映射路径为/RequestInfoServlet,然后在doGet()方法中编写以下代码。 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String method="method:"+request.getMethod()+"\r\n"; String protocol="protocol:"+request.getProtocol()+"\r\n"; String servername="servername:"+request.getServerName()+"\r\n"; String port="port:"+request.getServerPort()+"\r\n"; String requestpath="contextpath:"+request.getContextPath()+"\r\n"; String uri="request URI:"+request.getRequestURI()+"\r\n"; String ipaddress="ip address:"+request.getRemoteAddr(); PrintWriter out=response.getWriter() out.append(method).append(protocol).append(servername).append(port). append(uri).append(requestpath).append(ipaddress); out.close(); } RequestInfoServlet的doGet()方法中使用了request的相应方法获取基本信息。访问RequestInfoServlet后,request对象的基本信息如图39所示。 图39request对象的基本信息 Servlet输出时利用response对象使用getWriter()或者getOutputStream()方法以获取输出流,二者互斥不能混用。例子中使用的PrintWriter对象用于向客户端输出字符流,包括以下5个常用的方法。 (1) void print()方法: 输出文本信息后不换行。 (2) void println()方法: 输出文本信息后换行。 (3) void append()方法: 和print()类似,但方法可以直接追加,例32中即采用此方法,因此在相应变量后添加了\r\n进行换行操作。 (4) void flush()方法: 输出缓冲区数据,在客户端输出后清除缓冲区数据。 (5) void close()方法: 关闭输出流。 3.4.3URL传值数据的获取 超链接是网页中常见的元素,单击超链接可以跳转到指定的URL,该地址可以是服务器外部网址,也可以是服务器内部地址。同时超链接后面可以附带参数一同发送给服务器,从而实现页面之间信息的传递。单击超链接是通过GET方式提出请求,下面通过例33演示通过超链接进行URL传递参数以及服务器通过Servlet获取后再输出到客户端页面中的过程。 视频讲解 【例33】通过超链接进行URL传递参数。 (1) 在项目的WebContent下新建一个HTML页面,取名为hyperlink.html,在标签体内部添加一个超链接,代码如下: 通过超链接进行URL传值 该超链接指向URLServlet,超链接指向的URL后面附带两个参数,访问hyperlink.html页面如图310所示。 图310访问hyperlink.html页面 注意,超链接的href地址前没有加反斜杠(/),表示采用的是相对路径,访问的是/Chapt_03/路径下的URLServlet。如果地址前加了反斜杠,就表示使用的是绝对地址,此时必须加项目路径/Chapt_03/,代码如下: 通过超链接进行URL传值 访问路径的写法是初学时容易犯错的地方,尤其是超链接的href属性以及表单提交的action的写法,如果路径有误,则页面会报404错误,此时应排查采用的是相对还是绝对路径。 (2) 新建一个Servlet,取名为URLServlet,通过注解@WebServlet("/URLServlet")设置映射路径为/URLServlet,然后在doGet()方法中编写代码如下: String a=request.getParameter("a"); String b=request.getParameter("b"); PrintWriter out=response.getWriter(); out.println("parameter a:"+a); out.println("parameter b:"+b); 单击hyperlink.html中的超链接,页面跳转到URLServlet,获取超链接和URL传递的参数,如图311所示。 图311获取超链接和URL传递的参数 此时参数附加在URL后面,并出现在地址栏中,说明超链接的确是通过GET方式进行的请求。在URLServlet类的doGet()方法中,使用了request对象的getParameter(String name)方法。该方法的作用就是通过参数名来获取对应的参数值。该方法使用较为频繁,在获取表单提交的数据时也会采用。 3.4.4表单中单值元素数据的获取 表单提交是Web应用中常见的功能,本节介绍Servlet处理表单中单一元素数据的方法。单值元素是指该表单元素提交数据给服务器时,只包含一个变量。表单中的单值元素包括文本框、密码框、单选按钮、下拉框以及多行文本框等。下面通过例34演示Servlet获取表单中单值元素的数据并处理的操作步骤。 视频讲解 【例34】Servlet获取表单中单值元素的数据。 (1) 新建一个HTML页面,取名为single.html,在标签体内部编写一个包含上述元素的表单,代码如下: 用户名: 密 码: 性别(单选): 男 女 所在区域: 中国 美国 英国 请输入个人信息: 说明: 表单action属性的值表示表单提交后,所有元素数据由GetSingleServlet处理,注意此时采用的是相对路径的写法; method属性的值表示提交方式,默认为GET方式,本例使用POST方式进行提交。 (2) 新建GetSingleServlet,通过注解@WebServlet("/GetSingleServlet")设置映射路径为/GetSingleServlet,在doGet()方法中的代码如下: String username=request.getParameter("username"); String password=request.getParameter("password"); String gender=request.getParameter("gender"); String country=request.getParameter("country"); String information=request.getParameter("information"); PrintWriter out=response.getWriter(); out.println("username:"+username); out.println("password:"+password); out.println("gender:"+gender); out.println("country:"+country); out.println("information:"+information); 与获取URL传值方式一样,使用request对象的getParameter()方法,以表单中input元素的name属性为参数,获取input元素的value属性值,从而得到表单元素提交的数据。 (3) 打开浏览器访问single.html并填写表单数据,如图312所示。 图312访问single.html并填写表单数据 单击“提交”按钮将表单数据提交给GetSingleServlet处理,获取单值元素并输出到页面,如图313所示。 图313获取单值元素并输出到页面 3.4.5表单中多值元素数据的获取 表单中如果有多个元素的name属性值相同,当表单提交时,这些同名元素将以数组的形式向服务器发送数据,这样的元素称为多值元素。典型的多值元素包括复选框、多选列表框以及其他同名元素组合。下面通过例35演示Servlet获取表单中多值元素的数据并处理的操作步骤。 视频讲解 【例35】Servlet获取表单中多值元素的数据并处理。 (1) 新建一个HTML页面,取名为multiple.html,在标签体内部编写一个包含上述元素的表单,代码如下: 勾选你的兴趣爱好(可多选): 阅读 跳舞 唱歌 运动 选择精通的语言(可多选): 汉语 英语 法语 俄语 填写你擅长的其他技能: 技能1: 技能2: 技能3: 说明: multiple.html页面中的下拉框有一个属性multiple,表示这个下拉框和CheckBox一样,可以多选。另外还有3个name属性值都是skills的文本框,这是一个文本框的组合。表单提交方式为POST,提交给GetMultipleServlet去处理。 由于多选框、多选下拉列表和组合文本框的value取值有多个,因此需要采用request对象的getParameterValues()方法,参数仍然是这些多值元素的name属性值,但该方法返回的不再是单一的字符串,而是一个String类型的数组。 (2) 新建一个Servlet类,取名为GetMultipleServlet,通过注解@WebServlet("/GetMultipleServlet")设置映射路径为/GetMultipleServlet,在doGet()方法中编写代码如下: String[] hobbies=request.getParameterValues("hobbies"); String[] languages=request.getParameterValues("languages"); String[] skills=request.getParameterValues("skills"); PrintWriter out=response.getWriter(); out.println("your hobbies include:"); for(int i=0;i 如果不修改服务器的URICoding编码方式,就需要对GET方式传递的每个参数都单独转换编码格式,例如: String username = new String(request.getParameter("username"). getBytes("ISO-8859-1"), "UTF-8"); 2. POST方式 将表单提交方式修改为POST方式,重新提交表单,利用Console进行观察,发现出现了中文乱码。中文参数通过POST方式传递到服务器的效果如图320所示。 图320中文参数通过POST方式传递到服务器的效果 由于通过POST方式提交的数据是存放在HTTP的请求体中的,而服务器对POST请求的数据编码的默认方式不是UTF8,因此会在服务器端出现中文乱码。为此可以重新指定服务器对请求体的编码方式来避免上述问题,修改GetSingleServlet类的代码,在使用getParameter()方法获取参数前,加上如下一行代码,用于指定POST方式提交参数的编码格式。 request.setCharacterEncoding("UTF-8"); 再重新提交表单后,后台输出中文参数为正常。 3.5.2响应编码 当Servlet处理完毕后,通过response对象进行中文的输出时,服务器也有默认的编码方式,可以通过response.getCharacterEncoding()方法获取,同时也提供了setCharacterEncoding()方法用于更改编码方式。在GetSingleServlet类中,添加代码如下: //获取response默认编码 System.out.println(response.getCharacterEncoding()); //设置Tomcat的response编码方式为UTF-8 response.setCharacterEncoding("UTF-8"); //查看修改后的response编码方式 System.out.println(response.getCharacterEncoding()); 注意,修改编码方式的语句需要放在执行输出的语句之前。重新提交表单,在Console中观察response编码方式的输出结果,如图321所示。 图321response编码方式的输出结果 3.5.3客户端编码 在修改了response编码方式后,中文输出是否不再乱码呢?然而在查看页面后发现,仍然是乱码。这是因为setCharacterEncoding()方法只是设定了响应信息的编码格式,但输出到客户端时,文本信息最终显示的编码方式是由客户端浏览器决定的。在GetSingleServlet输出页面下,按F12键,打开开发者工具,在控制台选项卡下,查看中文乱码提示信息,如图322所示。 图322中文乱码提示信息 因此当要输出中文时,还要告诉浏览器以何种编码方式进行显示。可以使用response的setHeader()或者setContentType()方法设置响应输出的信息类型以及编码方式。再次修改GetSingleServlet类的代码,在输出语句前添加代码如下: response.setHeader("Content-type","text/plain;charset=UTF-8"); 或者添加代码如下: response.setContentType("text/plain;charset=UTF-8")。 这两句代码的作用相同,都是通过设置HTTP response响应头的方式告诉客户端浏览器: 输出信息的格式是纯文本类型,编码方式采用UTF8。重新提交表单,修改响应头后中文输出正常,如图323所示。 图323修改响应头后中文输出正常 setContentType()方法的第一个参数是指定访问资源的类型,比如text/html表示HTML格式,image/gif表示GIF格式的图片,而application/pdf 表示PDF格式。关于资源类型代码,可以查询https://www.w3cschool.cn/http/ahkmgfmz.html。 setHeader()方法能够设置HTTP响应的很多参数,非常实用,在后续章节中还会介绍其用法。 3.6Servlet生成HTML页面 在上述的例子中,Servlet的响应都是输出普通的文本类型的字符串,页面输出的是纯文本信息。那么如何通过Servlet输出一个由HTML元素组成的页面呢?下面通过例36来演示使用Servlet输出10以内的加法表格的操作步骤。 视频讲解 【例36】使用Servlet输出10以内的加法表格。 (1) 新建一个Servlet,取名为HtmlServlet,通过注解@WebServlet("/HtmlServlet")设置映射路径为/HtmlServlet,在其doGet()方法中编写代码如下: PrintWriter out=response.getWriter(); out.println(""); out.println(""); out.println(""); for(int i=1;i<=9;i++) {//for循环语句输出包含加法算式的单元格 out.println(""); for(int j=1;j<=i;j++) { int add1=i-j+1; int add2=j; int sum=add1+add2; out.println(""+add1+"+"+add2+"="+sum+""); } out.println(""); } out.println(""); (2) 访问HtmlServlet,Servelt输出加法表格的效果如图324所示。 图324Servlet输出加法表格的效果 从上面的例子可以看出,Servlet通过输出流的方式可以逐句生成HTML标签以及CSS样式,从而生成动态页面。这种方式过于低效,相对于JSP页面,而Servlet的优势在于业务逻辑的处理。在后续章节学习完JSP后,可以结合Servlet和JSP技术共同开发。