目录
- servlet简介
- servlet的生命周期
- ServletConfig接口
- ServletContext接口简介
- 欢迎页面的设置和url-pattern
- GenericServlet类
- HttpServlet类
- servlet相关类
- HttpServletRequest接口
- servlet请求乱码问题
- HttpServletResponse接口
servlet简介
创建javaee的web项目
在使用servlet之前我们需要先创建一个javaee的web项目,一个web项目通常包含下面一些文件:
- src:存放java源代码
- web:存放静态资源和动态资源
- WEB-INF:该目录下的所有文件无法从浏览器中直接访问
- lib:存放相关的jar包
- classes:存放编译后的class文件
- index.jsp:web项目中默认访问的首页
application context(项目上下文),通过配置项目上下文可以在tomcat部署多个项目。
什么是servlet
servlet是一门用于开发动态web资源的技术,可以运行在Web服务器中的小型Java程序,有时也叫做服务器端的小应用程序,servlet是server applet两个单词的组合而成。servlet 可以通过 HTTP协议接收和响应来自 Web 客户端的请求。
servlet的语法跟JavaSE是一样的并且能够使用JavaSE中的API,要想创建一个servlet并使用的话可以分为以下几个步骤:
- 创建一个javaee的web项目
- 编写一个Java类,实现javax.servlet.Servlet接口,并重写该接口中的抽象方法
- 在web.xml文件中配置该servlet(如果使用注解方式的话,无需配置web.xml)
- 将该web项目部署到tomcat中并启动
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执行流程序列图:
- 客户端向tomcat发送请求,http://localhost:8080/07-01-servlet/hello
- tomcat根据请求的URL找到名称为07-01-servlet的应用
- tomcat根据请求的URI"/hello"找到web.xml文件中url-pattern与之对应的标签
- tomcat根据url-pattern标签中的servlet-name定位到com.monkey1024.servlet.FirstServlet类。
- tomcat实例化FirstServlet类
- 调用FirstServlet类中的init方法
- 调用FirstServlet类中的service方法
- FirstServlet将数据响应给客户端
- 调用distroy方法销毁FirstServlet
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;
}
}
上面代码的运行需要注意下面几个时间点:
- 项目部署后启动服务器,并没有执行 Servlet 的无参构造器方法,说明在 Web 容器启动时并没有创建 Servlet 对象。
- 用户提交请求URL后,马上可以看到无参构造方法、init()方法、service()方法均执行。
- 刷新页面,发现只会执行 service()方法,每刷新一次,即每提交一次请求,就会执行一次 service()方法。
- 让另外一个浏览器也发出同样的请求,会发现只执行 service()方法,而无参构造方法、 init() 方法均未执行。
- 正常关闭 Tomcat(使用 stop server 关闭,不能使用 Terminate 关闭),发现 destroy()方
法也会执行。
servlet的特征
- Servlet是单例多线程的,只创建一个servlet对象,但是每次请求都会起一个线程并在自己线程栈内存中执行service方法。
- 一个 Servlet 实例只会执行一次无参构造器与 init()方法,并且是在第一次访问时执行。
- 用户每提交一次对当前 Servlet 的请求,就会执行一次 service()方法。
- 一个 Servlet 实例只会执行一次 destroy()方法,在应用停止时执行。
- 由于 Servlet 是单例多线程的,所以为了保证其线程安全性,一般情况下是不建议在 Servlet类中定义可修改的成员变量,因为每个线程均可修改这个成员变量,会出现线程安全问题。
- 默认情况下,Servlet 在 Web 容器启动时是不会被实例化的。
tomcat启动时创建servlet实例
在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实例。它的值必须是一个整数。
- 当值大于等于 0 时,表示容器在启动时就加载并初始化这个 Servlet,数值越小,该 Servlet的优先级就越高,其被创建的也就越早;
- 当值相同时,容器会自己选择创建顺序。
ServletConfig接口
ServletConfig接口简介
在 Servlet 接口的 init()方法中有一个参数 ServletConfig,这个参数类型是个接口,里面是一些 在 web.xml 中对当前 Servlet类的配置信息。Servlet 规范将Servlet 的配置信息全部封装到了 ServletConfig 接口对象中。在tomcat调用 init()方法时,首先会将 web.xml 中当前 Servlet 类的配置信息封装为一个对象。这个对象的类型实现了 ServletConfig 接口, Web 容器会将这个对象传递给init()方法中的 ServletConfig 参数。
ServletConfig中的方法
- getInitParameter(): 获取指定名称的初始化参数值。例如从下面的servlet中调用 * * getInitParameter(“userName”);方法会返回字符串"monkey1024"。
- getInitParameterNames():获取当前 Servlet 所有的初始化参数名称。其返回值为枚举类型 Enumeration。
- getServletName():获取当前 Servlet 的中指定的 Servlet名称。如下面中的 * ServletName 为"configServlet"。
- getServletContext():获取到当前 Servlet 的上下文对象 ServletContext,这是个非常重要的对象,将在下一节中详细介绍。
示例:
在配置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());
}
}
ServletConfig的特点
每一个servlet都对应一个ServletConfig用于封装各自的配置信息,即有几个servlet就会产生几个ServletConfig对象。
ServletContext接口简介
ServletContext接口简介
WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,ServletContext对象包含Web应用中所有 Servlet 在 Web 容器中的一些数据信息。ServletContext随着Web应用的启动而创建,随着 Web 应用的关闭而销毁。一个 Web 应用只有一个ServletContext 对象。
ServletContext中不仅包含了 web.xml 文件中的配置信息,还包含了当前应用中所有Servlet可以共享的数据。可以这么说, ServeltContext 可以代表整个应用,所以ServletContext有另外一个名称:application。
ServletContext中常用方法
ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过ServletConfig.getServletContext()方法获得ServletContext对象。
- String getInitParameter ():获取 web.xml 文 件 的 中 指 定 名 称 的上下文参数值 。
- Enumeration getInitParameterNames():获取 web.xml 文件的中的所有的上下文参数名称。其返回值为枚举类型 Enumeration。
- void setAttribute(String name, Object object):在 ServletContext 的公共数据空间中,也称为域属性空间,放入数据。这些数据对于 Web应用来说,是全局性的,与整个应用的生命周期相同。当然,放入其中的数据是有名称的,通过名称来访问该数据。
- Object getAttribute(String name):从 ServletContext 的域属性空间中获取指定名称的数据。
- void removeAttribute(String name):从 ServletContext 的域属性空间中删除指定名称的数据。
- String getRealPath(String path):获取当前 Web 应用中指定文件或目录在本地文件系统中的路径。
- String getContextPath():获取当前应用在 Web 容器中的名称。
示例:
在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
}
}
欢迎页面的设置和url-pattern
设置欢迎页面
在浏览器地址栏中直接通过项目名称访问时,默认显示的页面就是欢迎页面,可以是.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的设置
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 会拦截用户对于静态资源(.css、.js、.html、.jpg、.png……)
与动态资源(.jsp)的请求。即用户不会直接获取到这些资源文件,而是将请求交给当前 Servlet
来处理了。 - 使用/,表示当前的 Servlet 会拦截用户对于静态资源(.css、.js、.html、.jpg、.png……),
但对于动态资源的请求,是不进行拦截的,我们可以访问jsp文件。即用户请求的静态资源文件是不能直接获取到的。
综上所述,对于 Servlet 的url-pattern的设置,我们一般是不会将其指定为/*或/的。
一旦有一个 Servlet 的url-patter被设置为了/*或/,则整个应用的静态资源将可能无法正常显示。
后辍名模式
请求路径最后的资源名称必须携带中指定的后辍名,其请求才可被当前Servlet 处理
<servlet-mapping>
<servlet-name>contextServlet02</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
url-pattern路径优先级
路径优先后辍匹配原则
例如 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。
GenericServlet类
继承GenericServlet类
在通过实现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类源码分析
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)方法中对其进行了调用。
HttpServlet类
继承HttpServlet类
在实际应用中常用的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请求的方法。
快速创建Servlet
在IDEA中直接创建Servlet,会自动生成相关的web.xml配置信息。
在eclipse中右键—>new—>servlet可以快速创建一个Servlet,该Servlet会默认继承HttpServlet类并重写doGet和doPost方法,并且在创建的过程中可以帮我们生成web.xml相关servlet标签配置,这样就不用手动编写了。
模板方法设计模式
什么是模板方法设计模式
定义一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
比如考试的时候,学生是共用同一套试卷,只是学生各自的答案是不同的;因此,试题题目是模板方法是不变的,而试题答案对于每个学生是可变的。
HttpServlet源码分析
通过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相关类
创建servlet的三种方式
- 定一个类实现javax.servlet.Servlet接口
- 定义一个类继承javax.servet.GenericServlet类
- 定义一个类继承javax.servlet.http.HttpServlet类
Servlet(爷爷) –> GenericServlet(爸爸) –> HttpServlet(儿子)
Servlet相关类总结
上图中描述了servlet中常用的接口和类之间的关系,图中下面三个类和接口都是在javax.servlet.http包下,上面的类和接口都在javax.servlet包下。
如果是通过参数传过来的对象,就叫依赖
通过方法得到的对象,就叫关联
- 上图中tomcat通过Servlet接口中的init方法将ServletConfig对象传递过去,所以Servlet接口依赖ServletConfig接口。
- Servlet接口中通过调用getServletConfig方法可以获取ServletConfig对象,所以Servlet接口也关联ServletConfig接口。
- 在ServletConfig接口中通过getServletContext方法获取ServletContext对象,所以ServletConfig接口关联ServletContext接口。
- GenericServlet类分别实现了Servlet接口和ServletConfig接口
- HttpServlet类继承了GenericServlet类
- HttpServletRequest接口继承ServletRequest接口
- HttpServletResponse接口继承ServletResponse接口
HttpServletRequest接口
HttpServletRequest简介
Web服务器收到客户端的http请求,会针对每一次请求,创建一个用于代表请求的HttpServletRequest类型的request对象,并将"HTTP请求协议"的完整内容封装到该对象中。开发者获拿到request对象后就可以获取客户端发送给服务器的请求数据了。
HttpServletRequest的生命周期
当客户端浏览器向服务器发送请求后,服务器会根据HTTP请求协议的格式对请求进行解析。同时,服务器会创建 HttpServletRequest类型的对象,即请求对象,然后将解析出的数据封装到该请求对象中。此时HttpServletRequest实例就创建并初始化完毕了,也就是说,请求对象是由服务器创建。当服务器向客户端发送响应结束后,HttpServletRequest 实例对象被服务器销毁,HttpServletRequest对象的生命周期很短暂。
一次请求对应一个请求对象, 另外一次请求对应另外一个请求对象,即每次请求都会创建一个HttpServletRequest类型的对象,这些对象之间没有关系。
HttpServletRequest中常用的方法
- Map getParameterMap()
获取包含所有请求参数及值的 Map 对象。需要注意,该 Map 的 value 为 String[],即一个参数所对应的值为一个数组。说明一个参数可以对应多个值。 - Enumeration getParameterNames()
获取请求参数 Map 的所有 key,即获取所有请求参数名。 - String[] getParameterValues(String name)
根据指定的请求参数名称,获取其对应的所有值。这个方法一般用于获取复选框(checkbox)数据。 - String getParameter(String name)
根据指定的请求参数名称,获取其对应的值。若该参数名称对应的是多个值,则该方法获取到的是第一个值。这个方法是最常用的方法。
获取客户端信息的方法:
- getRequestURL方法返回客户端发出请求时的完整URL。
- getRequestURI方法返回请求行中的资源名部分。
- getQueryString 方法返回请求行中的参数部分。
- getRemoteAddr方法返回发出请求的客户机的IP地址
- getRemoteHost方法返回发出请求的客户机的完整主机名
- getRemotePort方法返回客户机所使用的网络端口号
- getLocalAddr方法返回WEB服务器的IP地址。
- getLocalName方法返回WEB服务器的主机名
- getMethod得到客户机请求方式
代码示例:
创建一个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请求乱码问题
有时候在servlet中接收到的数据会出现乱码问题。
乱码的产生原因
当用户通过浏览器提交一个包含 UTF-8 编码格式的两个中文请求时,浏览器会将这两个中文字符变为六个字节(一般一个 UTF-8 汉字占用三个字节),即形成六个类似%8E 的字节表示形式,并将这六个字节上传至 Tomcat 服务器。
Tomcat 服务器在接收到这六个字节后,并不知道它们原始采用的是什么字符编码。而Tomcat 默认的编码格式为 ISO-8859-1。所以会将这六个字节按照 ISO-8859-1 的格式进行解码,解码后在控制台显示,所以在控制台会显示乱码。
乱码的解决方案
针对 POST 提交乱码的解决方式
在接收请求参数之前先通过 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);
}
针对get提交乱码的解决方式
可以通过修改 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
- 表示使用eclipse工作空间的tomcat,该路径在工作空间目录中的.metadata.plugins\org.eclipse.wst.server.core\tmp0目录中,如果你使用的是这个(eclipse会默认使用这个),需要修改该目录中的server.xml才会生效。
- 表示使用我单独下载的tomcat所在的路径,我本机的是在:F:\monkey1024\apache-tomcat-9.0.0.M26中,如果你使用的是这个,需要修改该目录中的server.xml文件。
- 表示自定义一个位置,即手动指定web应用运行的目录位置,如果你使用的是这个,需要修改该目录中的server.xml文件。
万能解决方案
//根据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的乱码问题,但是代码量较大,开发中使用较少。
HttpServletResponse接口
HttpServletResponse简介
Web服务器收到客户端的http请求,会针对每一次请求,创建一个用于代表响应的HttpServletResponse类型的response对象,开发者可以将要向客户端返回的数据封装到response对象中。
HttpServletResponse向客户端发送数据
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);
}
}
HttpServletResponse响应乱码的解决方案
响应时会产生乱码的原因是在 HTTP 协议中规定,默认响应体的字符编码为ISO-8859-1。所以,若要解决乱码问题,就需要修改响应体的默认编码。一般情况下,有两种方式可以修改:
- 方法一:HttpServletResponse 的 setCharacterEncoding(“utf-8”)方法,将编码修改为utf-8,然后再通过setHead(“Content-type”,”text/html;charset=UTF-8″);方法告诉客户端浏览器的编码方式。
代码:
response.setCharacterEncoding("UTF-8");
response.setHead("Content-type","text/html;charset=UTF-8");
- 方法二:为了简便操作,开发者可以直接使用HttpServletResponse 的 setContentType(“text/html;charset=utf-8”)方法,告诉浏览器的编码方式,该方法相当于方法一种的两条代码。
代码:
response. setContentType("text/html;charset=UTF-8");
注意:设置响应编码时必须在 PrintWriter 对象产生之前先设置,否则将不起作用。