JavaWeb详解(第一篇)之Servlet基础简介
1、Serlvet概述
Servlet是运行在Web服务器或应用服务器上的java程序,它是一个中间层,负责连接来自web浏览器或其他HTTP客户程序和HTTP服务器上应用程序。是sun公司提供的一门用于开发动态web资源的技术。
Servlet执行下面的任务:
1)读取客户发送的显示数据。如:html表单数据
2)读取由浏览器发送的隐式请求数据。如:http请求头
3)生成结果。
4)向客户端发送显示数据(即文档)。servlet和jsp最重要的任务就是将结果包装在文本(html)、二进制(图片)等格式的文件中。
5)发送隐式的HTTP响应数据。如:http响应头
如果想要用浏览器请求一个动态web资源,我们需要完成一下2个步骤即可:a)编写一个Java类,实现servlet接口。b)把开发好的Java类部署到web服务器中。
Servlet就是Sun公司在其API中提供了一个servlet接口(其实就是java程序),只不过该java程序要遵循servlet开发规范,他能帮我们处理浏览器发送的HTTP请求,并返回一个响应给浏览器。
2、servlet开发入门
编写第一个servlet程序。
2.1、第一个servlet程序
2.1.1、步骤一
新建一个web工程,新建一个名为:FirstServletDemo的程序,并实现Servlet接口,会要求我们实现其抽象方法。init【初始化】,destroy【销毁】,service【服务】,ServletConfig【Servlet配置】,getServletInfo【Serlvet信息】
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class FirstServletDemo implements Servlet{
@Override
public void destroy() {
System.out.println("我是destroy,我执行了");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void init(ServletConfig arg0) throws ServletException {
System.out.println("我是init,我执行了");
}
@Override
public void service(ServletRequest req, ServletResponse rsp) throws ServletException, IOException {
System.out.println("我是service,我执行了");
OutputStream out = rsp.getOutputStream();
out.write("Hello Servlet!".getBytes());
}
}
2.2、步骤二
配置tomcat的web.xml,并在其中添加如下信息。
<!--servlet标签中:
servlet-name配置servlet的名字,servlet-class配置servlet对应的java类
-->
<servlet>
<servlet-name>FirstServletDemo</servlet-name>
<servlet-class>com.cn.test.FirstServletDemo</servlet-class>
</servlet>
<!--servlet-mapping标签中:
servlet-name配置servlet的名字,url-pattern配置访问的url
"/":开头默认代表:http://localhost:8080/
-->
<servlet-mapping>
<servlet-name>FirstServletDemo</servlet-name>
<url-pattern>/FirstServletDemo</url-pattern>
</servlet-mapping>
启动tomcat服务,并在浏览器中输入:http://localhost:8080/FirstServletDemo
展示页面如下:
2.2、servlet的生命周期
很多程序都有自身执行的一个事件流,servlet也一样。Servlet 运行在 Servlet 容器中,并由容器管理从创建到销毁的整个过程。
2.2.1、servlet的生命周期概述
Servlet生命周期大致可以分为:
1) 加载和实例化
如果Servlet容器还没实例化一个Servlet对象,此时容器装载和实例化一个 Servlet。创建出该 Servlet 类的一个实例。如果已经存在一个Servlet对象,此时不再创建新实例。
2)初始化 init()
当Servlet被实例化后,Tomcat会调用init()方法初始化这个对象。
3)处理请求service()
当 Servlet 容器接收到一个 Servlet 请求时,便运行与之对应的 Servlet 实例的 service() 方法来处理用户请求及对客户的响应。
4)销毁destroy()
当 Servlet 容器决定将一个 Servlet 从服务器中移除时 ( 如:Servlet 文件被更新,一个Servlet如果长时间不被使用的话,也会被Servlet 容器自动销毁 ),便调用该 Servlet 实例的 destroy() 方法。
2.2.2、servlet的生命周期特别说明
init()方法执行初始化有两种类型:常规初始化和参数化初始化。
1)常规初始化
在没有配置web.xml中配置<load-on-startup></load-on-startup>的时候, init()只初始化一次,并且只有访问对应的Servlet的时候才去执行init()方法。
2)参数化初始化
当然,Servlet多起来了,如果想要有个加载顺序,程序员可以通过代码去实现什么时候初始化以及初始化顺序,但如果突然想要调整,这需要开发人员修改代码,为了方便,于是提供了一种可以通过参数化配置实现初始化顺序,即参数化初始化。
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class FirstServletDemo implements Servlet{
int num = 0;
@Override
public void destroy() {
System.out.println("我是destroy,我执行了");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void init(ServletConfig arg0) throws ServletException {
System.out.println("我是init,我执行了");
num = (int) (Math.random()*100);
}
@Override
public void service(ServletRequest req, ServletResponse rsp) throws ServletException, IOException {
System.out.println("我是service,我执行了");
PrintWriter out = rsp.getWriter();
out.println(num);
}
}
修改web.xml
<servlet>
<servlet-name>FirstServletDemo</servlet-name>
<servlet-class>com.cn.test.FirstServletDemo</servlet-class>
<!--小于0:表示不执行初始化方法
大于或等于0: 当服务器启动的时候就会按照配置的顺序依次创建Servlet类的实例,并且优先级0最大。
-->
<load-on-startup>2</load-on-startup>
</servlet>
案例效果:
每次启动的时候,就初始化了。再次访问时,页面直接显示71了。
配置参数化初始化的作用:
1)为web应用写一个InitServlet,这个servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据。
2)完成一些定时的任务【定时写日志,定时备份数据】
3、serlvet三种开发方式
实现Servlet接口有三种方式:
1)实现Servlet接口;2)通过继承GenericServlet;3)通过继承HttpServlet。
前面我们已经知道怎么使用Servlet接口了,对Servlet接口SUN公司定义了两个默认实现类:GenericServlet、HttpServlet。
3.1、继承GenericServlet
GenericServlet是一个抽象类,它实现了Servlet接口。只需要我们重写service方法。
源码:
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable{
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
......
}
使用案例:
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class GenericServletDemo extends GenericServlet{
@Override
public void service(ServletRequest req, ServletResponse rsp) throws ServletException, IOException {
// TODO Auto-generated method stub
PrintWriter out = rsp.getWriter();
out.println("GenericServletDemo!");
}
}
3.2、继承HttpServlet
HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。
源码:
public abstract class javax.servlet.http.HttpServlet extends javax.servlet.GenericServlet{
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) {
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
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 {
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);
}
}
......
}
使用案例:
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;
public class HttpServletDemo extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter out = resp.getWriter();
out.println("HttpServletDemo!");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
扩展:Servlet调用过程分析
4、Servlet开发细节
4.1、Servlet可以被多次映射
由于客户端是通过URL地址访问web服务器中的资源,所以Servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上,这个工作在web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成。
web.xml配置如下:
<servlet>
<servlet-name>HttpServletDemo</servlet-name>
<servlet-class>com.cn.test.HttpServletDemo</servlet-class>
</servlet>
<!-- 多个url映射同一个servlet -->
<servlet-mapping>
<servlet-name>HttpServletDemo</servlet-name>
<url-pattern>/HttpServletDemo</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>HttpServletDemo</servlet-name>
<url-pattern>/HttpServletDemo/Demo2</url-pattern>
</servlet-mapping>
案例效果:访问两个效果一致
4.2、使用通配符
在Servlet映射到的URL中也可以使用*通配符,但是只能有两种固定的格式:一种格式是“*.扩展名”,另一种格式是以正斜杠(/)开头并以“/*”结尾。
4.2.1、/*结尾
4.2.2、 *.扩展名
案例分析:
假如我有:
Servlet1 映射到 /abc/*
Servlet2 映射到 /*
Servlet3 映射到 /abc
Servlet4 映射到 *.do
问题1:当请求URL为“/abc/a.html”,“/abc/*”和“/*”都匹配,对应的哪个servlet会响应请求?
答案1:Servlet引擎将调用Servlet1
问题2:当请求URL为“/abc/a.do”时,“/abc/*”和“*.do”都匹配,对应的哪个servlet会响应请求?
答案2:Servlet引擎将调用Servlet1
注:在匹配的时候,要参考的标准:1)谁的匹配度高,谁就被选择;2)*.do 的优先级最低。
5、Servlet的线程安全问题
因为Servlet是单例,当多个客户端并发访问同一个Servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题。
案例:创建一个ServletDemo1的servlet
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;
public class ServletDemo1 extends HttpServlet{
int i = 0;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=gbk");
PrintWriter out = response.getWriter();
out.println("<form action='/ServletDemo1' method='post' >");
out.println("<input type='submit' value='点我' /><br />");
out.println("</form><br />");
out.println("我被点了:"+i+"次");
out.flush();
out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
i++;
doGet(request, response);
}
}
web.xml配置:
<servlet>
<servlet-name>ServletDemo1</servlet-name>
<servlet-class>com.cn.test.ServletDemo1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletDemo1</servlet-name>
<url-pattern>/ServletDemo1</url-pattern>
</servlet-mapping>
案例效果:
我们发现在不同的客户端操作,共享变量i的值也被共享了。右边先点一次,左边再点两次,还未等右边反映过来,变量i其实已经发生了变化,所以左边也出现了3次,实际只点了一次。
方案二:使用SingleThreadModel接口,Servlet引擎将以单线程模式来调用其service方法
public class ServletDemo1 extends HttpServlet implements SingleThreadModel{
int i = 0;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=gbk");
PrintWriter out = response.getWriter();
out.println("<form action='/ServletDemo1' method='post' >");
out.println("<input type='submit' value='点我' /><br />");
out.println("</form><br />");
out.println("我被点了:"+i+"次");
out.flush();
out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
i++;
try {
//为了突出并发量,这里设置一个延时,量大反映慢了的时候
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
doGet(request, response);
}
}
案例效果:
SingleThreadModel以另一种方式(单线程模式调用)解决多线程并发问题,而真正意义上解决多线程安全问题是指一个Servlet实例对象被多个线程同时调用的问题。事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为Deprecated(过时的)。
真正避免线程安全问题的:可以将变量写在方法里,局部变量为线程私有,则不存在线程安全问题。
6、ServletConfig和ServletContext详解
6.1、ServletConfig详解
Servlet给我们提供了一个ServletConfig对象,该对象主要用于读取Servlet的配置信息。允许我们将配置信息写在web.xml中。
web容器(Tomcat)在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet。
返回类型 | 方法&描述 |
---|---|
java.lang.String | getInitParameter(java.lang.String name) 返回一个 String包含值指定初始化参数,或 null如果参数不存在。 |
java.util.Enumeration | getInitParameterNames() 返回servlet的名称的初始化参数作为一个 Enumeration的 String对象,或一个空 Enumeration如果servlet没有初始化参数。 |
ServletContext | getServletContext() 返回一个正在执行ServletContext 的引用. |
java.lang.String | getServletName() 返回这个servlet实例的名称. |
ServletConfig类的三大作用:
1)可以获取Servlet程序的别名servlet-name的值;2)获取初始化参数init-param;3)获取ServletContext对象。
使用案例:
import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletConfigDemo extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
//ServletConfig对象是整个Servlet
String charset = this.getServletConfig().getInitParameter("charset");
String sname = this.getServletConfig().getServletName();
resp.getWriter().println(charset);//输出: GBK
resp.getWriter().println(sname);//输出: ServletConfigDemo
}
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
//可用于启动时初始化时连接数据库
System.out.println(config.getInitParameter("username")); //控制台输出:root
System.out.println(config.getInitParameter("url")); //控制台输出:jdbc:mysql://localhost:3306/test
}
}
web.xml配置
<servlet>
<servlet-name>ServletConfigDemo</servlet-name>
<servlet-class>com.cn.test.ServletConfigDemo</servlet-class>
<!-- 初始化参数 -->
<init-param>
<param-name>charset</param-name>
<param-value>GBK</param-value>
</init-param>
<init-param>
<param-name>username</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/test</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>ServletConfigDemo</servlet-name>
<url-pattern>/ServletConfigDemo</url-pattern>
</servlet-mapping>
6.2、ServletContext 详解
ServletConfig中返回的ServletContext对象的引用。这个对象是干嘛的呢?ServletConfig是对应的单个servlet,而ServletContext对象,代表的是当前web应用,它会为每个WEB应用程序都创建一个对应的ServletContext对象。
ServletContext对象被包含在ServletConfig对象中,可以通过ServletConfig.getServletContext方法获得对ServletContext对象的引用。Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象(可以像Map一样存取数据:setAttribute、getAttribute、removeAttribute)。
ServletContext 是当web应用启动的时候,自动创建,当web应用关闭/tomcat关闭/对web应用reload,会造成servletContext销毁。
ServletContext的主要作用:
1)获取web.xml中配置的上下文参数context-param;(getInitParameter(name))
2)获取当前工程路径,格式:/工程路径;
3)获取工程部署在服务器上的绝对路径;
4)像Map一样存取数据。
5)读取资源文件。
6.2.1、读取配置文件及获取路径
使用案例一:
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletContextDemo extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=gbk");
//ServletConfig对象是整个Servlet
ServletContext sc = this.getServletConfig().getServletContext();
String username = (String) sc.getInitParameter("username");
PrintWriter out = resp.getWriter();
out.println("获取的上下文配置getInitParameter(username):"+username);//输出:context
//如果在:server.xml中的Context中没有配置path,则此处获取到的getContextPath()为""
out.println("<br/>当前工程路径getContextPath():"+sc.getContextPath());//
out.println("<br/>当前工程部署路径getRealPath():"+sc.getRealPath("/"));//
//设置上下文配置
sc.setAttribute("password", "test");
}
}
web.xml配置:
<context-param>
<description>我是整个工程的配置</description>
<param-name>username</param-name>
<param-value>context</param-value>
</context-param>
<servlet>
<servlet-name>ServletContextDemo</servlet-name>
<servlet-class>com.cn.test.ServletContextDemo</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletContextDemo</servlet-name>
<url-pattern>/ServletContextDemo</url-pattern>
</servlet-mapping>
案例效果:
6.2.2、存取数据
使用案例二:修改下ServletConfigDemo
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletConfigDemo extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext sc = this.getServletConfig().getServletContext();
String password = (String) sc.getAttribute("password");
resp.getWriter().println(password);//
}
}
案例效果:能取到ServletContextDemo中设置的值
6.2.3、读取资源文件
使用案例三:读取资源文件
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletContextDemo2 extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//首先读取到文件
resp.setContentType("text/html;charset=gbk");
PrintWriter out = resp.getWriter();
//文件放到WEB-INF目录下
InputStream in=this.getServletContext().getResourceAsStream("/WEB-INF/info.properties");
//文件放到src目录下
// ClassLoader classLoader = ServletContextDemo2.class.getClassLoader();
// InputStream in = classLoader.getResourceAsStream("info.properties");
Properties pro=new Properties();
pro.load(in);
out.println("username="+pro.getProperty("username")+"<br />");
out.println("password="+pro.getProperty("password")+"<br />");
out.flush();
out.close();
}
}
info.properties
username=root
password=123456
7、HttpServletRequest、HttpServletResponse详解
前面我们看到HttpServlet中的service(doGet或doPost)方法中接收两个参数:service(HttpServletRequest req, HttpServletResponse resp)。下面分别介绍下这两个对象。
7.1、HttpServletRequest
HttpServletRequest对象代表客户端的请求。当客户端通过HTTP协议访问Tomcat服务器时,服务器救护把请求过来的HTTP请求头中的所有信息都封装到Request这个对象中,然后传递给service方法(doGet或doPost)中给我们使用。开发人员通过这个对象的方法,可以获得客户这些信息。
常用API:
返回类型 | 方法 | 描述 |
---|---|---|
java.lang.String | getMethod() | 返回这个请求使用的HTTP方法(例如:GET、POST、PUT) |
HttpSession | getSession() getSession(boolean create) | 获取到当前客户端请求的session对象, 如果不存在,则创建。 |
java.lang.String | getParameter(String name) | 获取客户端提交的数据 |
java.lang.String[] | getParameterValues(String name) | 获取所有参数 |
java.util.Map | getParameterMap() | 获取到客户端传递过来装有数据的map集合 |
java.lang.Object | getAttribute(String name) | 获取到上一个页面传递过来的键值对 |
void | setAttribute(String name, Object o) | 将键值对设置到request中传递到下一个Servlet或者页面 |
void | setCharacterEncoding(String env) | 设置请求的字符集 |
RequestDispatcher | getRequestDispatcher(String path) | 当处理完用户的请求之后:请求跳转到xxx页面(请求转发) |
java.lang.StringBuffer | getRequestURL() | 返回客户端发出请求时的完整URL |
java.lang.String | getQueryString() | 返回请求行中的参数部分 |
java.lang.String | getHeader(String name) | 获取请求头参数 |
使用案例:
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ReqServletDemo extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=gbk");
PrintWriter out = response.getWriter();
out.println("<form action='/ReqServletDemo' method='post' >");
out.print("<input type='txt' name='username' /><br /><br />");
out.print("<input type='checkbox' name='hobby' value='java'/>java<br />");
out.print("<input type='checkbox' name='hobby' value='python'/>python<br />");
out.print("<input type='checkbox' name='hobby' value='html'/>html<br />");
out.println("<input type='submit' value='提交' /><br />");
out.println("</form><br />");
out.flush();
out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//设置请求响应字符编码,默认是ISO-8859-1,不设置的就会出现乱码
request.setCharacterEncoding("GBK");
//response.setCharacterEncoding("GBK");
//推荐使用下面这种
response.setHeader("Content-Type","text/html;charset=GBK");
//回显请求数据
PrintWriter out = response.getWriter();
out.println("url:"+request.getRequestURL().toString());//
out.println("User-Agent:"+request.getHeader("User-Agent"));//
out.println("获取请求参数username:"+request.getParameter("username"));//
out.println("获取请求参数hobby:"+Arrays.asList(request.getParameterValues("hobby")));//
}
}
案例效果:
7.1.1、RequestDispatcher请求转发
HttpServletRequest对象返回RequestDispatcher对象,叫做请求转发:服务器收到请求后,从一次资源跳转到另一个资源的操作。
首先我们在WEB-INF目录下方一张图片,然后在浏览器中输入:http://localhost:8080/WEB-INF/logo.jpg,去访问,会出现如下页面。默认浏览器是不能直接访问WEB-INF目录的。
使用案例:
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ReqServletDemo extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=gbk");
PrintWriter out = response.getWriter();
out.println("<form action='/ReqServletDemo' method='post' >");
out.print("<input type='txt' name='username' /><br /><br />");
out.print("<input type='checkbox' name='hobby' value='java'/>java<br />");
out.print("<input type='checkbox' name='hobby' value='python'/>python<br />");
out.print("<input type='checkbox' name='hobby' value='html'/>html<br />");
out.println("<input type='submit' value='提交' /><br />");
out.println("</form><br />");
out.flush();
out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//请求转发
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/logo.jpg");
//前进到转发的目录:正常浏览器是访问不到WEB-INF目录的资源的,但是请求转发可以
rd.forward(request, response);
}
}
案例效果:
请求转发的特点:
1)浏览器地址栏没有变化,他们是一次请求;
2)共享Request域中的数据;
3)可以转发到WEB-INF目录下的资源,但是不可以访问工程以外的资源。
使用案例二:refresh的应用
我们经常看到一个网站贴出即将跳转到哪里,5秒后没跳转请点击链接。
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;
public class RspServletDemo extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=gbk");
PrintWriter out = response.getWriter();
out.println("<form action='/first' method='post' >");
out.println("<input type='submit' value='跳转' /><br />");
out.println("</form><br />");
out.flush();
out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setHeader("refresh", "3;url='https://www.baidu.com/'");
//页面跳转
request.getRequestDispatcher("first.jsp").forward(request, response);
}
}
web.xml
<servlet-mapping>
<servlet-name>RspServletDemo</servlet-name>
<url-pattern>/RspServletDemo</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>RspServletDemo</servlet-name>
<url-pattern>/first</url-pattern>
</servlet-mapping>
first.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>RequestDispatcher</title>
</head>
<body>
3秒后跳转百度
<a href="https://www.baidu.com/">未跳转请点击</a>
</body>
</html>
案例效果:
虽然不可以访问外部资源,但是我们可以使用设置响应头response.setHeader("refresh", "3;url='百度一下,你就知道'");的方式,让浏览器跳转到指定的url。
7.2、HttpServletResponse
HttpServletResponse对象服务器的响应。这个对象中封装了向客户端发送数据、发送响应头,发送响应状态码的方法。
常用API:
返回类型 | 方法 | 描述 |
---|---|---|
void | setCharacterEncoding(String charset) | 设置响应客户端信息编码的编码方式 |
ServletOutputStream | getOutputStream() | 返回一个 ServletOutputStream 适合在响应中写入二进制数据的 |
java.io.PrintWriter | getWriter() | 返回一个PrintWriter对象,可以发送字符的文本给客户端 |
void | setHeader(String name, jString value) | 设置一个响应头和给定的名称和值 |
void | setStatus(int sc) | 设置状态代码响 |
void | sendRedirect(String location) | 发送一个临时重定向响应给客户端使用指定URL重定向的位置 |
7.2.1、getOutputStream和getWriter的区别
字符流:getWriter() 用于向客户机回送字符数据
字节流:getOutputStream() 回送字节数据(二进制数据,常用于下载),当然它也可以回送字符,但是没有PrintWriter对象 ,效率高。
OutputStream os=response.getOutputStream();
os.write("hello,world".getBytes());
注:两个流同时只能使用一个,Web服务器会自动检查并关闭流。
使用案例:
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class RspServletDemo2 extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=gbk");
PrintWriter out = response.getWriter();
out.println("<form action='/first' method='post' >");
out.println("<input type='submit' value='跳转' /><br />");
out.println("</form><br />");
OutputStream out2 = response.getOutputStream();
out2.write("hello,world".getBytes());
}
}
案例效果:
7.2.2、sendRedirect重定向的使用
请求重定向是指:一个web资源收到客户端请求后,通知客户端去访问另外一个web资源,这称之为请求重定向。
实现方式一:设置Location(不推荐)
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;
public class sendRedirectDemo1 extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=gbk");
PrintWriter out = response.getWriter();
out.println("<form action='/sendRedirectDemo1' method='post' >");
out.println("<input type='submit' value='设置Location' /><br />");
out.println("</form><br />");
out.flush();
out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setStatus(302);
response.setHeader("Location", "http://localhost:8080/");
}
}
案例效果:
实现方式二:请求重定向,效果一样
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;
public class sendRedirectDemo2 extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=gbk");
PrintWriter out = response.getWriter();
out.println("<form action='/sendRedirectDemo2' method='post' >");
out.println("<input type='submit' value='设置Location' /><br />");
out.println("</form><br />");
out.flush();
out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.sendRedirect("http://localhost:8080/");
}
}
请求重定向的特点:
1)浏览器地址会发送变化;
2)两次请求,不共享Request域中的数据;
3)不能访问WEB-INF下的资源,可以访问工程外的资源。
总结:请求重定向(sendRedirect)和请求转发(forward)的区别
-
RequestDispatcher.forward方法只能将请求转发给同一个WEB应用中的组件;而HttpServletResponse.sendRedirect 方法还可以重定向到同一个站点上的其他应用程序中的资源,甚至是使用绝对URL重定向到其他站点的资源。
-
调用HttpServletResponse.sendRedirect方法重定向的访问过程结束后,浏览器地址栏中显示的URL会发生改变,由初始的URL地址变成重定向的目标URL;调用RequestDispatcher.forward 方法的请求转发过程结束后,浏览器地址栏保持初始的URL地址不变。
-
RequestDispatcher.forward方法的调用者与被调用者之间共享相同的request对象和response对象,它们属于同一个访问请求和响应过程;而HttpServletResponse.sendRedirect方法调用者与被调用者使用各自的request对象和response对象,它们属于两个独立的访问请求和响应过程。