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请求,并返回一个响应给浏览器。
编写第一个servlet程序。
新建一个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()); } }
配置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
展示页面如下:
很多程序都有自身执行的一个事件流,servlet也一样。Servlet 运行在 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() 方法。
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)完成一些定时的任务【定时写日志,定时备份数据】
实现Servlet接口有三种方式:
1)实现Servlet接口;2)通过继承GenericServlet;3)通过继承HttpServlet。
前面我们已经知道怎么使用Servlet接口了,对Servlet接口SUN公司定义了两个默认实现类:GenericServlet、HttpServlet。
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!"); } }
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); } }
由于客户端是通过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>
案例效果:访问两个效果一致
在Servlet映射到的URL中也可以使用*通配符,但是只能有两种固定的格式:一种格式是“*.扩展名”,另一种格式是以正斜杠(/)开头并以“/*”结尾。
案例分析:
假如我有:
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 的优先级最低。
因为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(过时的)。
真正避免线程安全问题的:可以将变量写在方法里,局部变量为线程私有,则不存在线程安全问题。
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>
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)读取资源文件。
使用案例一:
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>
案例效果:
使用案例二:修改下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中设置的值
使用案例三:读取资源文件
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
前面我们看到HttpServlet中的service(doGet或doPost)方法中接收两个参数:service(HttpServletRequest req, HttpServletResponse resp)。下面分别介绍下这两个对象。
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")));// } }
案例效果:
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。
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重定向的位置 |
字符流: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()); } }
案例效果:
请求重定向是指:一个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对象,它们属于两个独立的访问请求和响应过程。