在使用servlet之前我们需要先创建一个javaee的web项目,一个web项目通常包含下面一些文件:
application context(项目上下文),通过配置项目上下文可以在tomcat部署多个项目。
servlet是一门用于开发动态web资源的技术,可以运行在Web服务器中的小型Java程序,有时也叫做服务器端的小应用程序,servlet是server applet两个单词的组合而成。servlet 可以通过 HTTP协议接收和响应来自 Web 客户端的请求。
servlet的语法跟JavaSE是一样的并且能够使用JavaSE中的API,要想创建一个servlet并使用的话可以分为以下几个步骤:
web访问流程图
如上图所示,服务器端程序其实就是实现了servlet接口的程序,servlet的主要作用其实就是处理和响应客户端的请求。
编写第一个servlet
创建一个web项目可以在创建项目时关联tomcat,这样eclipse会把相关的servlet-api.jar包拷贝到项目中,在项目创建一个Java类实现servlet接口并重写里面的方法。
import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class FirstServlet implements Servlet { @Override public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException { System.out.println("Hello Servlet"); } @Override public void destroy() { } @Override public ServletConfig getServletConfig() { return null; } @Override public String getServletInfo() { return null; } @Override public void init(ServletConfig arg0) throws ServletException { } }
servlet创建好之后,需要在web.xml文件中进行配置,给servlet一个可以访问的URI地址:
<!-- 创建一个servlet实例 --> <servlet> <!-- 给servlet取一个名字,该名字需与servlet-mapping中的servlet-name一致 --> <servlet-name>firstServlet</servlet-name> <!-- servlet的包名+类名 --> <servlet-class>com.monkey1024.servlet.FirstServlet</servlet-class> </servlet> <!-- 给servlet一个可以访问的URI地址 --> <servlet-mapping> <!-- servlet的名字,与 servlet中的servlet-name一致--> <servlet-name>firstServlet</servlet-name> <!-- URI地址:http://locahost:8080/07-01-servlet/hello --> <url-pattern>/hello</url-pattern> </servlet-mapping>
之后将该web项目部署到tomcat中,启动成功后访问:http://locahost:8080/07-01-servlet/hello
可以看到eclipse控制台中打印出了Hello Servlet
通过上面示例可以看出,servlet的需要部署在tomcat中才能运行,有时tomcat也被称为是servlet的容器。
servlet执行流程
servlet执行流程序列图:
Servlet 生命周期指的是,Servlet 对象的创建、Servlet 对象的初始化、Servlet 对象服
务的执行,及最终 Servlet 对象被销毁的整个过程。
servlet生命周期图:
Servlet 的整个生命周期过程的执行,均由 Web 服务器负责管理,程序员无法控制其
执行流程。但程序员可以获取到 Servlet 的生命周期时间点,并可以指定让 Servlet 做一些业务相关的事情。
示例代码:
import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class LifeServlet implements Servlet { public LifeServlet(){ System.out.println("无参构造方法执行"); } @Override public void destroy() { System.out.println("执行destroy方法"); } @Override public void init(ServletConfig arg0) throws ServletException { System.out.println("执行init方法"); } @Override public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException { System.out.println("执行service方法"); } @Override public ServletConfig getServletConfig() { return null; } @Override public String getServletInfo() { return null; } }
上面代码的运行需要注意下面几个时间点:
在tomcat启动时,默认不会创建servlet实例,如果想要让tomcat在启动时创建servlet实例的话,只需要在web.xml中添加load-on-startup标签即可
<servlet> <servlet-name>lifeServlet</servlet-name> <servlet-class>com.monkey1024.servlet.LifeServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>lifeServlet</servlet-name> <url-pattern>/life</url-pattern> </servlet-mapping>
添加load-on-startup的作用是,标记是否在 Tomcat启动时创建并初始化这个 Servlet实例。它的值必须是一个整数。
在 Servlet 接口的 init()方法中有一个参数 ServletConfig,这个参数类型是个接口,里面是一些 在 web.xml 中对当前 Servlet类的配置信息。Servlet 规范将Servlet 的配置信息全部封装到了 ServletConfig 接口对象中。在tomcat调用 init()方法时,首先会将 web.xml 中当前 Servlet 类的配置信息封装为一个对象。这个对象的类型实现了 ServletConfig 接口, Web 容器会将这个对象传递给init()方法中的 ServletConfig 参数。
示例:
在配置web.xml时,可以为servlet指定多个初始化参数:
<servlet> <servlet-name>configServlet01</servlet-name> <servlet-class>com.monkey1024.servlet.ConfigTest01</servlet-class> <init-param> <param-name>userName</param-name> <param-value>monkey1024</param-value> </init-param> <init-param> <param-name>password</param-name> <param-value>123456</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>configServlet01</servlet-name> <url-pattern>/config01</url-pattern> </servlet-mapping>
创建一个servlet:
import java.io.IOException; import java.util.Enumeration; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** * ServletConfig接口 * */ public class ConfigTest01 implements Servlet { private ServletConfig config; @Override public void destroy() { } @Override public ServletConfig getServletConfig() { return this.config; } @Override public String getServletInfo() { return null; } @Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println("init方法中ServletConfig:" + servletConfig); this.config = servletConfig; } @Override public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException { String userName = config.getInitParameter("userName"); System.out.println("userName=" + userName); Enumeration<String> param = config.getInitParameterNames(); while(param.hasMoreElements()){ String name = param.nextElement(); String value = config.getInitParameter(name); System.out.println(name + "=" + value); } System.out.println("ServletName=" + config.getServletName()); } }
每一个servlet都对应一个ServletConfig用于封装各自的配置信息,即有几个servlet就会产生几个ServletConfig对象。
WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,ServletContext对象包含Web应用中所有 Servlet 在 Web 容器中的一些数据信息。ServletContext随着Web应用的启动而创建,随着 Web 应用的关闭而销毁。一个 Web 应用只有一个ServletContext 对象。
ServletContext中不仅包含了 web.xml 文件中的配置信息,还包含了当前应用中所有Servlet可以共享的数据。可以这么说, ServeltContext 可以代表整个应用,所以ServletContext有另外一个名称:application。
ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过ServletConfig.getServletContext()方法获得ServletContext对象。
示例:
在web.xml文件中加入初始化参数,web.xml文件修改后要重启tomcat才能生效:
<!-- 初始化参数 --> <context-param> <param-name>MySQLDriver</param-name> <param-value>com.mysql.jdbc.Driver</param-value> </context-param> <context-param> <param-name>dbURL</param-name> <param-value>jdbc:mysql:</param-value> </context-param>
创建servlet:
import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** * ServletContext接口 * */ public class ContextTest01 implements Servlet { private ServletConfig config; @Override public void destroy() { } @Override public ServletConfig getServletConfig() { return this.config; } @Override public String getServletInfo() { return null; } @Override public void init(ServletConfig servletConfig) throws ServletException { this.config = servletConfig; } @Override public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException { ServletContext application = this.config.getServletContext(); System.out.println("ContextTest01:" + application); String driver = application.getInitParameter("MySQLDriver"); System.out.println(driver); String contextPath = application.getContextPath(); System.out.println("contextPath:" + contextPath); //文件在硬盘中的绝对路径 String realPath = application.getRealPath("FirstServlet"); System.out.println("realPath:" + realPath); //向ServletContext中添加属性 application.setAttribute("admin", "tiger"); application.setAttribute("password", 123456); //删除password application.removeAttribute("password"); } }
再创建一个servlet:
import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class ContextTest02 implements Servlet { private ServletConfig config; @Override public void init(ServletConfig config) throws ServletException { this.config = config; } @Override public ServletConfig getServletConfig() { // TODO Auto-generated method stub return null; } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { ServletContext application = this.config.getServletContext(); System.out.println("Context02中的application:" + application); String admin = (String)application.getAttribute("admin"); System.out.println(admin); String password = (String)application.getAttribute("password"); System.out.println(password); } @Override public String getServletInfo() { // TODO Auto-generated method stub return null; } @Override public void destroy() { // TODO Auto-generated method stub } }
在浏览器地址栏中直接通过项目名称访问时,默认显示的页面就是欢迎页面,可以是.html.jsp,可以通过welcome-file-list进行设置。
设置多个欢迎页面:
<welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list>
可以为应用设置多个欢迎页面,但只会有一个起作用,系统加载这些欢迎页面的顺序与
其代码的顺序相同,即由上到下逐个查找,一旦找到,则马上显示,不会再向下查找。
如果当前应用没有指定欢迎页面,则系统会从当前项目的根目录下依次查找 index.html、 index.htm
及 index.jsp 文件,如果这些文件不存在的话,浏览器会报出 404 错误。
url-pattern标签用于对请求进行筛选匹配,对当前注册的 Servlet 所要处理的请求类
型进行筛选。对于url-pattern中路径的写法,有多种不同模式,表示不同的意义,一个Servlet可以对应多个url-pattern.
请求路径必须与url-pattern的值完全相同才可被当前 Servlet 处理。
<servlet-mapping> <servlet-name>contextServlet02</servlet-name> <url-pattern>/context02</url-pattern> <url-pattern>/servlet/context02</url-pattern> <url-pattern>/test/servlet/context02</url-pattern> </servlet-mapping>
该模式中的路径由两部分组成:精确路径部分与通配符部分。请求路径中只有携带了url-pattern值中指定的精确路径部分才可被当前 Servlet 处理。
<servlet-mapping> <servlet-name>contextServlet02</servlet-name> <url-pattern>/servlet/*</url-pattern> </servlet-mapping>
或
<servlet-mapping> <servlet-name>contextServlet02</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
/*与/表示所有请求均会被当前 Servlet 所处理。如果一个servlet的url-pattern是/*或/,则该servlet表示默认映射,当一个请求找不到相应的url的servlet时,系统会调用这个默认映射的servlet。
这两个路径的不同之处是:
请求路径最后的资源名称必须携带中指定的后辍名,其请求才可被当前Servlet 处理
<servlet-mapping> <servlet-name>contextServlet02</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
例如 SomeServlet的url-pattern为*.do,OtherServlet 的url-pattern
为 /xxx/。若用户提交的请求 URL 为 http://localhost:8080/oa/xxx/abc.do
,此时服务器发现 SomeServlet 的.do 与 OtherServlet 的/xxx/*都可以与用户提交请求的
/xxx/abc.do 相匹配。那么服务器会按照“路径优先后辍匹配”的原则选择 OtherServlet。
例如 SomeServlet的url-pattern为/some, OtherServlet 的url-pattern
为 /*。若用户提交的请求 URL 为 http://localhost:8080/oa/some
,此时服务器发现SomeServlet 的/some 与 OtherServlet 的/*都可以与用户提交请求的/some 相匹配。那么服务器会按照“精确路径优先匹配”的原则选择 SomeServlet。
例如 SomeServlet 的url-pattern为/some/,OtherServlet 的
url-pattern为 /some/other/。若用户提交的请求 URL 为
http://localhost:8080/oa/some/other
,此时服务器发现 SomeServlet 的/some/*
与 OtherServlet 的/some/other/*都可以与用户提交请求的/some/other 相匹配。那么
服务器会按照“最长路径优先匹配”的原则选择 OtherServlet。
在通过实现Servlet接口来定义一个Servlet类时存在一个很不方便的问题:有太多不需要的方法必须要实现。通常我们只在service()方法中完成业务逻辑,但由于Servlet 接口中还存在另外四个方法,所以也要必须实现。
为了解决这个问题JavaEE的API中提供了一个javax.servet.GenericServlet类,开发者在定义一个servlet时继承该GenericServlet类,此时只需要重写service方法即可。
import java.io.IOException; import javax.servlet.GenericServlet; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** * 继承GenericServlet * */ public class SimpleServlet extends GenericServlet { @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { System.out.println("SimpleServlet继承自GenericServlet"); } }
适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
可以将这里的适配器看做是一个万能充电接口,该充电接口一端可以连接安卓手机的充电器,另一端连接苹果手机,这样就实现了使用安卓手机的充电器给苹果手机充电了。
GenericServlet类中就使用了适配器模式。如上面编写的SimpleServlet,使用了GenericServlet后,可以将SimpleServlet类和Servlet接口适配在一起,只重写service方法就能够创建一个servlet类了。
通过查看GenericServlet的源码可以看到,该类是一个抽象类,实现了servlet接口和ServletConfig接口并重写了除了service方法以外的全部方法,这样子类在继承GenericServlet类时,只需重写service方法。如果想要使用destroy方法时,直接重写GenericServlet类中的destroy方法就行,而在GenericServlet类中的destroy方法本身就是一个空实现,里面没有代码。
GenericServlet类中的destroy方法:
/** * Called by the servlet container to indicate to a servlet that the servlet * is being taken out of service. See {@link Servlet#destroy}. */ @Override public void destroy() { // NOOP by default }
GenericServlet类中的init方法:
/** * Called by the servlet container to indicate to a servlet that the servlet * is being placed into service. See {@link Servlet#init}. * <p> * This implementation stores the {@link ServletConfig} object it receives * from the servlet container for later use. When overriding this form of * the method, call <code>super.init(config)</code>. * * @param config * the <code>ServletConfig</code> object that contains * configuration information for this servlet * @exception ServletException * if an exception occurs that interrupts the servlet's * normal operation * @see UnavailableException */ @Override public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } /** * A convenience method which can be overridden so that there's no need to * call <code>super.init(config)</code>. * <p> * Instead of overriding {@link #init(ServletConfig)}, simply override this * method and it will be called by * <code>GenericServlet.init(ServletConfig config)</code>. The * <code>ServletConfig</code> object can still be retrieved via * {@link #getServletConfig}. * * @exception ServletException * if an exception occurs that interrupts the servlet's * normal operation */ public void init() throws ServletException { // NOOP by default }
上面的源码中有两个init方法,其中无参的init方法是GenericServlet中自己定义的,那为什么要这么做呢?
如果在其子类中想要调用init方法的话,需要重写其init方法,但这个重写的 init(ServletConfig) 方法必须要调用父类的 init(ServletConfig) 方法,即在第一句必须写上super.init(config);否则将无法获取到 ServletConfig 对象。若 ServletConfig 对象未获取,程序在运行时就有可能会出现空指针异常,但这个 init(ServletConfig)语句,有时候会忘写,例如下面程序就会出现问题:
import java.io.IOException; import javax.servlet.GenericServlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** * 继承GenericServlet * */ public class SimpleServlet extends GenericServlet { @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { System.out.println("SimpleServlet继承自GenericServlet"); //这里会报错,因为没有获取父类中的config对象 System.out.println(super.getServletName()); } @Override public void init(ServletConfig config) throws ServletException { //忘记写这句代码 //super.init(config); System.out.println("init方法"); } }
所以为了避免这个问题的出现,在GenericServlet类中自己定义了一个没有参数的init方法,该方法就是让子类去重写的,子类重写该方法时,无需编写super.init(config);为了保证该无参方法在初始化时执行,在init(ServletConfig config)方法中对其进行了调用。
在实际应用中常用的http提交方式有get和post(除此之外还有put、delete),在之前所编写的servlet中是无法直接处理这两种提交方式的,为了方便开发,JavaEE规范的API提供了javax.servlet.http.HttpServlet类,在实际开发中也经常使用继承HttpServlet类的方式创建一个servlet。
import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 继承HttpServlet处理get和post请求 * */ public class HttpTest01 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("执行doGet方法"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("执行doPost方法"); } }
上面是通过继承HttpServlet来创建的一个名为HttpTest01的servlet,在HttpTest01中并没有重写service方法,主要重写了doGet和doPost方法来分别处理http请求中的get和post,除此之外,在HttpServlet类中还有doPut、doDelete等其他用于处理http请求的方法。
在IDEA中直接创建Servlet,会自动生成相关的web.xml配置信息。
在eclipse中右键—>new—>servlet可以快速创建一个Servlet,该Servlet会默认继承HttpServlet类并重写doGet和doPost方法,并且在创建的过程中可以帮我们生成web.xml相关servlet标签配置,这样就不用手动编写了。
定义一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
比如考试的时候,学生是共用同一套试卷,只是学生各自的答案是不同的;因此,试题题目是模板方法是不变的,而试题答案对于每个学生是可变的。
通过HttpServlet源码可以看到,这是一个抽象类,该类继承了GenericServlet并重写了其父类中的Service方法。
HttpServlet类中重写的service方法:
@Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { throw new ServletException("non-HTTP request or response"); } service(request, response); }
在重写的service方法中,将ServletRequest和ServletResponse强转成了HttpServletRequest和HttpServletResponse,并且调用另外一个service方法将强转后的HttpServletRequest和HttpServletResponse对象作为参数传递了过去,再看下这个service方法的源码:
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); } catch (IllegalArgumentException iae) { // Invalid date header - proceed as if none was set ifModifiedSince = -1; } if (ifModifiedSince < (lastModified / 1000 * 1000)) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } }
通过该源码可以看到,他从HttpServletRequest对象中的getMethod方法里面获取到http的请求方式,然后将不同的请求方式交给了不同的方法来处理,这其中包含了doGet、doPost、doPut、doDelete等方法,在这些方法里面并没有写什么特殊的业务处理的代码,这样做的目的就是让子类去重写这些方法,所以说HttpServlet类中使用了模板方法设计模式。需要注意的是HttpServlet的子类不需要重写service方法,倘若重写了该方法后可能会导致所编写的Servlet无法正常工作。
Servlet(爷爷) –> GenericServlet(爸爸) –> HttpServlet(儿子)
上图中描述了servlet中常用的接口和类之间的关系,图中下面三个类和接口都是在javax.servlet.http包下,上面的类和接口都在javax.servlet包下。
如果是通过参数传过来的对象,就叫依赖
通过方法得到的对象,就叫关联
Web服务器收到客户端的http请求,会针对每一次请求,创建一个用于代表请求的HttpServletRequest类型的request对象,并将"HTTP请求协议"的完整内容封装到该对象中。开发者获拿到request对象后就可以获取客户端发送给服务器的请求数据了。
当客户端浏览器向服务器发送请求后,服务器会根据HTTP请求协议的格式对请求进行解析。同时,服务器会创建 HttpServletRequest类型的对象,即请求对象,然后将解析出的数据封装到该请求对象中。此时HttpServletRequest实例就创建并初始化完毕了,也就是说,请求对象是由服务器创建。当服务器向客户端发送响应结束后,HttpServletRequest 实例对象被服务器销毁,HttpServletRequest对象的生命周期很短暂。
一次请求对应一个请求对象, 另外一次请求对应另外一个请求对象,即每次请求都会创建一个HttpServletRequest类型的对象,这些对象之间没有关系。
获取客户端信息的方法:
创建一个html里面写一些表单信息:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>表单提交</title> </head> <body> <form action="RequestTest01" > 用户名:<input name="username" type="text"> <br> 密码:<input name="password" type="password"> <br> 爱好: <input type="checkbox" name="hobby" value="basketball">篮球 <input type="checkbox" name="hobby" value="football">足球 <input type="checkbox" name="hobby" value="volleyball">排球 <br> <input type="submit" value="提交"> </form> </body> </html>
创建一个servlet来接收用户提交的数据:
import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * HttpServletRequest获取请求数据 */ public class RequestTest01 extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //根据html中的name的名字获取用户在input中填写的值 String username = request.getParameter("username"); String password = request.getParameter("password"); //获取用户勾选的checkbox的值 String[] hobby = request.getParameterValues("hobby"); System.out.println(username); System.out.println(password); for(String s:hobby){ System.out.println(s); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
有时候在servlet中接收到的数据会出现乱码问题。
当用户通过浏览器提交一个包含 UTF-8 编码格式的两个中文请求时,浏览器会将这两个中文字符变为六个字节(一般一个 UTF-8 汉字占用三个字节),即形成六个类似%8E 的字节表示形式,并将这六个字节上传至 Tomcat 服务器。
Tomcat 服务器在接收到这六个字节后,并不知道它们原始采用的是什么字符编码。而Tomcat 默认的编码格式为 ISO-8859-1。所以会将这六个字节按照 ISO-8859-1 的格式进行解码,解码后在控制台显示,所以在控制台会显示乱码。
在接收请求参数之前先通过 request 的 setCharacterEncoding()方法,指定请求体的字符编码格式。这样的话,在接收到请求中的参数后,就可按照指定的字符编码进行解码。
注意,request 的 setCharacterEncoding()方法只能解决 POST 提交方式中的乱码问题,对
于 GET 提交方式的不起作用。因为该方法设置的是请求体中的字符编码, GET 提交中的参数不出现在请求体中,而出现在请求行。
//设置post请求的字符编码 request.setCharacterEncoding("utf-8"); //根据html中的name的名字获取用户在input中填写的值 String username = request.getParameter("username"); String password = request.getParameter("password"); //获取用户勾选的checkbox的值 String[] hobby = request.getParameterValues("hobby"); System.out.println(username); System.out.println(password); for(String s:hobby){ System.out.println(s); }
可以通过修改 Tomcat 默认字符编码的方式来解决 GET 提交方式中携带中文的乱码问题。在 Tomcat 安装目录的 conf/server.xml 中,找到端口号为 8080 的标签,在其中添加 URIEncoding=”UTF-8″的设置,即可将 Tomcat 默认字符编码修改为 UTF-8。
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="UTF-8"/>
需要注意的是在修改时要找到正确的server.xml文件。
IDEA中在启动tomcat的时候,控制台的最上面有一项是Using CATALINA_BASE,这个后面是一个文件路径,进入后,打开里面的conf文件夹,修改这里的server.xml即可。
eclipse中双击tomcat服务器,如下图:
上图中有三个选项,不同的选项表示使用的不同路径下的tomcat
//根据html中的name的名字获取用户在input中填写的值 String username = request.getParameter("username"); //将数据按照ISO8859-1编码后放到字节数组中 byte[] bytes = username.getBytes("ISO8859-1"); //将字节数组按照UTF-8解码为字符串 username = new String(bytes,"UTF-8");
先以 ISO8859-1 的形式先对单字节的数据进行编码,并将编码后的数据存放在字节数组中。然
后,再将字节数组中的数据,按照指定的 UTF-8 格式进行解码,即变为了需要的 UTF-8 字符
编码的数据,解决了中文乱码问题。
通过上面的代码就可以解决get和post的乱码问题,但是代码量较大,开发中使用较少。
Web服务器收到客户端的http请求,会针对每一次请求,创建一个用于代表响应的HttpServletResponse类型的response对象,开发者可以将要向客户端返回的数据封装到response对象中。
ServletResponse 接口有一个方法 getWriter(),用于获取到一个输出流对象 PrintWriter,
该输出流对象是专门用于向客户端浏览器中输出字符数据的,称为标准输出流。
import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 使用HttpServletResponse向客户端发送数据 * */ public class ResponseTest01 extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //设置post请求的字符编码,此方式只对post请求有效 request.setCharacterEncoding("UTF-8"); //根据html中的name的名字获取用户在input中填写的value值 String username = request.getParameter("username"); //从response中取得PrintWriter对象 PrintWriter out = response.getWriter(); //向客户端发送数据 out.print("用户:" + username + "添加成功!<br>"); out.print("感谢您的注册"); //关闭PrintWriter out.close(); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
响应时会产生乱码的原因是在 HTTP 协议中规定,默认响应体的字符编码为ISO-8859-1。所以,若要解决乱码问题,就需要修改响应体的默认编码。一般情况下,有两种方式可以修改:
response.setCharacterEncoding("UTF-8"); response.setHead("Content-type","text/html;charset=UTF-8");
response. setContentType("text/html;charset=UTF-8");
注意:设置响应编码时必须在 PrintWriter 对象产生之前先设置,否则将不起作用。