Servlet配置及使用详解

1. Servlet介绍

Sun提供的一种动态web资源开发技术,本质上就是一段java小程序。

可以将Servlet加入到Servlet容器中运行。

tomcat既是web容器也是Servlet容器。


1.1 如何创建Servlet

先写一个类,实现sun公司定义的Servlet接口:

package java_web.Servlet;//这里是我自己定义的包,后面用到

import java.io.*;
import javax.servlet.*; //servlet-api.jar中的包

//GenericServlet类已经帮我们实现了Servlet的接口,只需继承并编写service即可
public class HelloServlet extends GenericServlet{
    public void service(ServletRequest req, ServletResponse res) {
        res.getWriter().write("hello Servlet");
    }
}

上面是参照javaee api文档写的一个简单Servlet,然后在同目录下运行指令:

javac HelloServlet.java

会报错,找不到那些关于Servlet的包,因为我们一般使用的是JavaSE,缺少这些必要的package,而tomcat实际上是自带有的,所以我们可以通过命令设置临时的classpath,以便编译:

set classpath=%classpath%;%CATALINA_HOME%\lib\servlet-api.jar;

再运行javac HelloServlet.java,这时就应该可以成功了,不过我们需要的是一个文件夹package而不是单独的class文件,所以应该使用指令:

javac -d . HelloServlet.java

会在当前目录下生成一个包含了HelloServlet.classpackage,名为java_web

接下来,将该整个包文件放到Tomcat中

tomcat\webapps\news\WEB-INF\classes\java_web

news是一个web应用,需要将刚创建的整个包文件java_web放到该应用的classes中。

还需要配置WEB-INF中的web.xml(可以参考conf中的web.xml):

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  version="3.0">

    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>java_web.Servlet.HelloServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/servlet/first</url-pattern>
    </servlet-mapping>

</web-app>

这样就通过URL映射到了相应的类文件。

在浏览器地址栏中访问localhost/news/servlet/first,即可看到刚才创建的servlet文件效果。


1.2 使用IDEA创建Servlet项目

  1. 点击Filenewproject

    newpro

  2. 选择Java Enterprise,并在右边配置好版本和路径,点击Next,再勾选Create project from template(或者在上一步勾选 Web Application):

    template

  3. 点击Next,选择项目的名称和目录,设置服务器为tomcat

    proconf

  4. 点击finish后,项目创建完毕,用默认的配置直接启动Tomcat运行该项目,会生成out文件夹及其内部结构,并自动打开浏览器跳转到index.jsp

    prodir

    src目录存放的是一些待编译的java文件,web目录存放的是网页的web资源。out目录存放的是编译输出文件,其中web_war_exploded的名称意为,项目名称为web的应用资源经过打包的war文件展开后的结构。

    为什么会出现这个呢?其实当我们启动项目,IDEA会自动生成这样的war文件,部署运行在Tomcat服务器上,只是为了方便所以开启了exploded模式,展开显示给开发者,可以在Project Structure中修改为war模式。因此,某个module有了artifacts 就可以部署到应用服务器中了。

    官方对artifacts的解释:

    An artifact is an assembly of your project assets that you put together to test, deploy or distribute your software solution or its part. Examples are a collection of compiled Java classes or a Java application packaged in a Java archive, a Web application as a directory structure or a Web application archive, etc.

    artifact是一个项目里资源的整合,比如说一些Java类文件,web资源等,我们可以对它们进行测试、部署或发布。

  5. 整个过程看似顺理成章,但是别忘了,我们是用IDEA生成的项目默认配置启动的,虽然运行成功,我们却不知其所以然。下面对它的一些相关配置进行解释。


1.3 项目相关配置

首先要明确,我们有两块需要配置:Project StructureRun/Debug Configurations,前者指的是刚创建的这个web项目的结构相关配置,后者指的是我们运行这个项目所用的相关配置,即Tomcat服务器的配置。


关于Project Struture

理解 IntelliJ IDEA 的项目配置和Web部署

IDEA里面的facets和artifacts的讲解

上面的文章已经对项目配置信息解释得很清楚了,相信认真看完之后,都能明白如何配置。需要强调的是,别对目录结构有疑惑,web资源原封不动输出,src编译输出到WEB-INF,种种设计是为了让我们能够更方便地开发web项目。


关于Run/Debug Configurations

理解了上面文章里的项目配置后,关于Run/Debug ConfigurationsTomcat设置,也不在话下,需要注意的是,记得在Deployment选项中给Tomcat添加资源路径,也就是将url与web应用的资源包进行映射,此处显示的应用资源包就是outartifactsweb_war_exploded目录。

tomconf


1.4 Servlet的调用过程

服务器处理请求的过程:

  1. 分析出当前请求的是哪台虚拟主机:
    1. 查看请求头中的Host,分析访问的是哪台主机。
    2. 如果没有该属性,则访问缺省虚拟主机。
  2. 分析访问的是虚拟主机中哪个web应用;
  3. 分析要访问的是这个web应用中的哪个资源(静态资源直接获取);
  4. 查看web.xml文件,是否有对应的虚拟路径,有则使用该路径对应的资源(如Servlet)做响应。

Servlet生命周期:

Servlet在第一次被访问到的时候,服务器会创建出Servlet对象,并立即调用init方法做初始化操作,创建出来的对象会一直驻留在内存中,之后对这个Servlet的访问都会导致其中的Service方法执行。当web应用移除容器或服务器关闭后,Servlet会启动destroy方法,进行销毁。

Servlet的继承结构:

Servlet接口定义了Servlet应该具有的基本方法,GenericServlet是通用的Servlet实现,对于不常用的方法在这个实现类中进行了基本实现,而Service设计为了抽象方法,需要子类去实现。HttpServlet是在通用Servlet的基础上基于HTTP协议进行了进一步的强化,实现了Service方法,并判断当前的请求方式,对应去调用doGet或doPost方法,所以一般我们开发Servlet只需要继承HttpServlet即可。


1.5 Servlet细节问题

  • 一个Servlet可以对应多个mapping,因此一个Servlet可以有多个路径来访问。

  • url-pattern中的路径可以使用*匹配符号进行配置,但是要注意,只能是/开头/*结尾,或者*.后缀这两种方式。

  • 如果一个url匹配到多个Servlet,会调用更精确mapping规则的那个Servlet*.后缀的优先级最低。

  • 如果在servlet元素中配置了一个<load-on-startup>1</load-on-startup>元素,那么web应用在启动时就会装载并创建Servlet的实例对象,以及调用init方法,而不会等到第一次使用才创建。标签中的数字代表启动顺序。

  • 缺省的Servlet配置:如果有一个Servlet的url-pattern被配置成了/,那么其他规则匹配不上的请求就会由这个Servlet来处理。其实对静态资源的访问,就是Tomcat提供的缺省Servlet来处理的,甚至404等提示页面也是由缺省Servlet来处理的,所以一般我们不改动缺省配置。

  • 由于Servlet默认在内存中只有一个对象,当多个线程同时运行,可能会引发线程安全问题。即使用同步锁来解决,效率也比较低,因此我们尽量避免使用类(静态)变量。




2. 和Servlet相关的对象

2.1 ServletConfig

代表当前Servletweb.xml中的配置信息。

Servlet的配置文件中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数。当Servlet配置了初始化参数后,web容器在创建Servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servletinit方法时,将ServletConfig对象传递给servlet,进而开发者可以通过该对象获取当前servlet的初始化参数信息。

<servlet>
    <servlet-name>DemoName</servlet-name>
    <servlet-class>cn.demo.FirstServlet</servlet-class>
    <init-param>
        <param-name>param1</param-name>
        <param-value>value1</param-value>
    </init-param>
</servlet>

可以获取当前Servletweb.xml中配置的名称;

可以获取当前Servlet中配置的初始化参数;

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取Servlet的config对象
    ServletConfig config = this.getServletConfig();

    //获取当前Servlet在web.xml中配置的名称
    String sName = config.getServletName();

    //获取配置的初始化参数
    String value = config.getInitParameter("param1");
}



2.2 ServletContext

web容器(Tomcat)在启动时,会为 每个 web应用程序都创建一个ServletContext对象,它代表当前web应用。

如何获取一个ServletContext对象:

ServletConfig config = this.getServletConfig();
ServletContext context = config.getServletContext();
//或者直接使用this.getServletContext();其实一样的原理
//this指的是HttpServlet对象


ServletContext对象有哪些作用?

作用一:数据共享

ServletContext作为域对象,可以在整个web应用范围内共享数据。

ServletContext context = this.getServletContext();

//一个Servlet设置属性: void setAttribute(String, Object)
context.setAttribute("name", "Tom");

//在另一个Servlet中使用域对象获取属性: Object getAttribute(String)
//注意获取的结果是一个对象,需要转换类型才能输出
String v = (String)context.getAttribute("name"); //Tom

//删除属性
context.removeAttribute("name");

生命周期

当服务器启动web应用创建出ServletContext对象后,域产生。

当web应用被移除或服务器关闭,随着web应用的销毁域也销毁。


作用二:获取初始化参数

前面我们提到ServletConfig可以获取当前Servlet的一些初始配置参数,那我们如何获取ServletContext(web应用)的初始化配置信息呢?

配置共享参数

<context-param>
    <param-name>param1</param-name>
    <param-value>value1</param-value>
</context-param>

获取初始化配置信息

//获取域对象
ServletContext context = this.getServletContext();

//获取应用的初始化配置信息
String value = context.getInitParameter("param1");

//可以使用枚举,获取完整的信息
Enumeration enumeration = context.getInitParameterNames();
while(enumeration.hasMoreElements()){
    String name = (String) enumeration.nextElement();
    String value = context.getInitParameter(name);
    System.out.println(name+":"+value);
}

现在需要理解几个名词:

请求参数:

浏览器发送过来的请求中的参数信息。

初始化参数:

在web.xml中为ServletServletContext配置的初始化时带有的基本参数。

域属性:

四大作用域中存取的键值对。(后面会提到)


作用三:实现Servlet的转发

请求转发:服务器内部进行资源流转。

注意:

请求转发是一次请求一次响应,而请求重定向是两次请求两次响应。

DispatchServlet:

//获取ServletContext对象,调用方法再获取请求转发对象
ServletContext sc = this.getServletContext();

//创建资源转发对象RequestDispatcher,参数为转向的路径
RequestDispatcher rd = sc.getRequestDispatcher("/news/goal");

//实现资源的转发,目的地:DestServlet
rd.forward(request, response);

DestServlet:

//接收转发过来的请求
response.getWriter().write("I received the request from the dispatcher");


作用四:加载资源文件

Servlet中读取资源文件时:

由于路径会相对于程序启动的目录,在web环境下,就是tomcat启动的目录,所以会找不到资源文件。为了解决这样的问题,ServletContext提供了getRealPath方法,在这个方法中传入一个路径,这个方法的底层会在传入路径前拼接当前web应用的硬盘路径,从而得到当前资源的硬盘路径,这种方式可以在任何发布环境下获取正确的资源路径。

//可以利用ServletContext读取资源文件,如:根目录的config.properties

//先看看默认会在哪里找资源
File file = new File("config.properties");
System.out.println(file.getAbsolutePath());
// ...tomcat\bin\config.properties 会去tomcat的启动目录中寻找资源

//用Properties读取资源信息
Properties prop = new Properties();
//从指定的路径加载读取资源
prop.load(new FileReader(this.getServletContext().getRealPath("config.properties")));

System.out.println(prop.getProperty("username"));
System.out.println(prop.getProperty("password"));

在实际场景中,往往是Servlet收发请求,而业务逻辑的处理则在另外的Service类中执行,这种类没有继承HttpServlet,没有ServletContext,该如何获取资源文件呢?

如果在非Servlet环境下要读取资源文件,可以采用类加载器加载文件的方式读取资源。

//HandleServlet.java
//这里主要收发请求
//调用service的方法去处理业务逻辑
Service ser = new Service();
ser.method1();
--------------------------------------
//Service.java
public class Service {
    //在这里处理大部分的业务逻辑(Servlet只负责收发请求)
    public void method1() throws IOException {
        Properties prop = new Properties();

        //使用类加载器去加载资源,默认当前目录是WEB-INF的classes下
        prop.load(new FileReader(Service.class.getClassLoader().getResource("../../config.properties").getPath()));
        //或者使用getResource("...").toString()获取路径

        //读取文件
        System.out.println("ClassLoader get username:"+prop.getProperty("username"));
        System.out.println("ClassLoader get password:"+prop.getProperty("password"));
    }
}



2.3 ServletResponse

ServletResponse是通用的response,提供了一个响应应该具有的基本属性和方法。
HttpServletResponseServletResponse的基础上针对于Http协议强化了一些属性和方法。

2.3.1 输出数据

方式一 getOutputStream:

//将字符串编码转换为输出流发送
response.getOutputStream().write("中国".getBytes());
//如果getBytes()不指定编码,则按照操作系统的默认去编码,即GB2312
//而浏览器在网页没有指定charset时默认也是按照操作系统默认编码来解码

//一般网络传输使用utf-8字符集,所以应该指定编码
response.getOutputStream().write("中国".getBytes("utf-8"));
//同时应该在响应头明确指定浏览器的解码方式
response.setHeader("Content-Type", "text/html;charset=utf-8");

方式二 getWriter:

//效果和setHeader("Content-Type", "text/html;charset=utf-8")一样
response.setContentType("text/html;charset=utf-8");

//指定在发送字符串时使用的编码格式,如果不指定,会使用iso8859-1
//response.setCharacterEncoding("utf-8"); 
//其实这句在指定Content-Type时会自动调用

//编解码格式一致,可以发送
response.getWriter().write("中国");


2.3.2 文件下载

文件下载需要使用到一个响应头属性:Content-Disposition,需要注意,在headers中只能出现iso8859-1中的字符,不能出现中文,因此文件名如果是是中文,需要经过url编码才能传输,使用类URLEncoder编码,URLDecoder解码。

//headers中不能出现中文
response.setHeader("Content-Disposition","attachment;filename="+URLEncoder.encode("美女.jpg","utf-8"));

//获取(文件)输入流
InputStream in = new FileInputStream(this.getServletContext().getRealPath("1.jpg"));

//创建输出流
OutputStream out = response.getOutputStream();

byte[] bs = new byte[1024];
int i = 0;
//写入到输出流
while((i=in.read(bs)) != -1){
    out.write(bs, 0, i);
}

//关闭输入流
in.close();

编解码:

String str = "中国";
String str2 = URLEncoder.encode(str, "utf-8");

String str3 = URLDecoder.decode(str2, "utf-8");//中国


2.3.3 实时刷新页面

//每隔一秒刷新一次页面,输出时间
response.getWriter().write(new Date().toLocaleString());
response.setHeader("Refresh", "1");

//使用最多的:重定向
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("注册成功,三秒后回到主页...");
response.setHeader("refresh", "3;url=/index.jsp");

//更常用的,是在一个html页面中设置重定向
<meta http-equiv="Refresh" content="3;url=/index.jsp">


2.3.4 请求重定向

利用response设置状态码为302,并设置响应头Location为要重定向到的地址,就可以实现请求重定向操作了。

response.setStatus(302);
response.setHeader("Location", "/index.jsp");

//或者直接简化成一条语句
response.sendRedirect("/index.jsp");

在大部分时候请求重定向和转发的效果是差不多的,这时推荐使用转发,以减少对服务器的访问。而在某些情况下是需要使用转发的,目的往往是为了改变浏览器地址栏里的地址(如登录成功后转到主页)和更改刷新操作(如加入商品到购物车后转到购物车页面的操作)。


2.3.5 控制资源缓存

控制不做缓存:

response.setIntHeader("Expires", -1);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");

控制缓存时间:

//缓存一个月。setDateHeader代表设置属性值为日期类型
//是long类型的数值,为时间戳,在这个时间戳之前缓存有效
response.setDateHeader("Expires", System.currentTimeMillis()+1000l*3600*24*30);
//别忘了加l,代表long型数值,只要运算中有任意一个数带l,结果就是long型
//如果不加l,由于int类型容不下这么大的数值,往上累计,最终会变成负数


2.3.6 Response注意事项

  • getOutputStreamgetWriter这两个方法互相排斥,调用了其中的任何一个方法后,就不能再调用另一个方法。转发是一次请求,一次响应。因此在Servlet转发的时候,注意在两个Servlet中使用相同的方法。

  • Servlet程序向ServletOutputStreamPrintWriter对象中写入的数据将被Servlet引擎从response里面获取,这些数据会作为响应消息的正文,然后再与响应状态行和各响应头组合后输出到客户端。

  • Servletservice方法结束后,Servlet引擎将检查getWritergetOutputStream方法返回的输出流对象是否已经调用过close方法,如果没有,tomcat将自动调用close方法关闭该输出流对象。所以一般不要自己在Servlet中关闭这个流,但输入流需要自己关闭。


2.3.7 输出验证码图片

方法介绍:

  • 建立BufferedImage对象,指定图片的长宽和类型

    BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
  • 取得Graphics对象,用来绘制图片

    Graphics graphics = image.getGraphics();
  • 绘制背景颜色

    graphics.setColor(Color.WHITE);
    graphics.fillRect(0,0,width,height);
  • 绘制边界

    graphics.setColor(Color.BLUE);
    graphics.drawRect(0,0,width-1,height-1);
  • 生成随机数

    Random random = new Random();
    random.nextInt(n);//生成0到n的随机数,包括0,不包括n
  • 绘制干扰线

    graphics.drawLine(x1,y1,x2,y2);//线段的两个端点,随机生成
  • 设置字体

    如果验证码是中文,要使用中文的字体库
    graphics.setFont(new Font("宋体", Font.PLAIN, 20));//字体,粗细倾斜等,size
  • 通过词库生成随机验证码内容

    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
    如果是汉字:\u4e00 —— \u9fa5
    graphics.drawString(str, x, y);//str是字符串,x是left,y是top(偏移量)
  • 设置旋转

    Graphics2D graphics = (Graphics2D)image.getGraphics();
    graphics.rotate(theta, x, y);
    //注意这里的theta是弧度,如果旋转45度,要写成 45d/180*Math.PI或0.25*Math.PI
    //否则45/180会是0,45d代表double类型的45
  • 释放此图形的上下文以及它使用的所有系统资源

    graphics.dispose();
  • 通过ImageIO对象的write静态方法将图片输出(输出后response已经commit了)

    ImageIO.write(image, "jpg", res.getOutputStream());

验证码的绘制

//1.在内存中构建一张图片
int height = 30;
int width = 120;
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

//2.获取图像上的画布
Graphics2D g = (Graphics2D)img.getGraphics();

//3.设置背景色
g.setColor(Color.LIGHT_GRAY);
g.fillRect(0,0,width,height);

//4.设置边框
g.setColor(Color.BLUE);
g.drawRect(0,0,width-1,height-1);

//5.画干扰线,线的两端坐标都是随机生成的
for (int i = 0; i < 5; i++) {
    g.setColor(Color.RED);
    g.drawLine(randNum(0, width), randNum(0, height), randNum(0, width), randNum(0, height));
}

//6.绘制汉字
String base = "\u7684\u4e00\u4e86\u662f";//这里可以放置大量常用汉字的Unicode编码
for (int i = 0; i < 4; i++) {
    //适当把rgb值往前调整,可避免字的颜色过分亮丽,影响人的识别
    g.setColor(new Color(randNum(0,150),randNum(0,150),randNum(0,150)));
    g.setFont(new Font("黑体", Font.BOLD, 20));
    //让画布适当旋转
    int r = randNum(-45, 45);
    g.rotate(1.0*r/180*Math.PI, 5+(i*30), 22);
    g.drawString(base.charAt(randNum(0,base.length()-1))+"",5+(i*30), 22);
    //绘制完一个字,旋转回原位,否则后面的字会在该基础上继续旋转
    g.rotate(-1.0*r/180*Math.PI, 5+(i*30), 22);
}

//7.将图片输出到浏览器
ImageIO.write(img, "jpg", response.getOutputStream());
//注意,此时,response已经结束,如果在下面再写setHeader、write是无效的

//细节问题
System.out.println(45/180*Math.PI);//0.0
System.out.println(45d/180*Math.PI);
System.out.println(0.25*Math.PI);

//一个用来生成随机数的方法
private Random rand = new Random();
private int randNum(int begin, int end){
    return rand.nextInt(end-begin) + begin;
}

同时要让验证码页面不缓存,每次刷新:

response.setDateHeader("Expires", -1);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");

注意html页面在使用验证码的时候,切换验证码只需改变src即可再次发送请求,后台不需改动:

function changeImg(img){
    img.src = "/news/valiImg?time=" + new Date().getTime();
}



2.4 ServletRequest

同理,ServletRequest也是一个通用的request,提供一个request应该具有的基本方法,而HttpServletRequest是针对http协议进行了进一步的增强的request

2.4.1 获取客户机信息

//获取客户端请求的完整url
String url = request.getRequestURL().toString();

//获取客户端请求的资源部分名称
String uri = request.getRequestURI();// /news/index.jsp

//获取请求行中参数部分
String qStr = request.getQueryString();//name=Tom&age=18

//获取请求客户端的ip地址
String ip = request.getRemoteAddr();

//获取客户机的请求方式
String method = request.getMethod();

//获取当前web应用的名称,可以用这个来设置路径,以便后续项目迁移
String name = request.getContextPath();
response.sendRedirect(request.getContextPath()+"/index.jsp")


2.4.2 获取请求头信息

String value = request.getHeader("Host");

//获取所有请求头
Enumeration<String> enumeration = request.getHeaderNames();
while(enumeration.hasMoreElements()){
    String name = enumeration.nextElement();
    String value = request.getHeader(name);
    System.out.println(name+":"+value);
}

//获取具体类型的客户机请求头
getIntHeader(name)方法 —— int
getDateHeader(name)方法 —— long(日期对应毫秒)

使用referer请求头防盗链

//假设别人在自己网站上使用一个a标签盗用了我们的localhost/news.html,我们可以在请求头中验证referer是否为我们可信的站点,如果不是,就不让访问,并且重定向到我们的主页

String ref = request.getHeader("Referer");
if(ref==null || "".equals(ref) || !ref.startsWith("http://localhost")){
    response.sendRedirect(request.getContextPath()+"/index.html");
}


2.4.3 获取请求参数

getParameter(name) —— String 通过name获得值
getParameterValues —— String[] 通过name获得多值 checkbox
getParameterNames —— Enumeration<String> 获得所有name
getParameterMap —— Map<String,String[]> key:name,value:多值

解决参数传递过来乱码的问题:

一般在html表单中数据是utf-8编码,而服务器中默认使用的编码是iso8859-1,对于表单中的中文数据是无法解码的,因此需要显式指定请求的解码方式:

request.setCharacterEncoding("utf-8");

注意,上面指定的是服务器以什么编码来解码http请求的实体内容,所以只适合POST请求,而GET请求的参数是附带在URL后的,需要我们手动进行编解码:

//对于get提交参数中的乱码,需要我们先按iso8859-1编码为字节,然后再按utf-8解码为字符串。
String username = request.getParameter("username");
username = new String(username.getBytes("iso8859-1"), "utf-8");
//当然也可以解码post请求


2.4.4 利用请求域传递对象

作用范围:整个请求链上。

生命周期:当服务器收到一个请求,创建出代表请求的request对象,request域开始,当请求结束,服务器销毁代表请求的request对象,request域结束。

方法:

setAttribute
getAttribute
removeAttribute
//其实四大作用域都有这三个方法。

使用请求域:

String value = (String) request.getAttribute("name"); //返回一个对象,需要类型转换

String result = "apple";
request.setAttribute("result", result);

//对于请求附带的参数,使用request域即可,ServletContext域范围过大
//this.getServletContext().getRequestDispatcher("/servlet/demo").forward(request,response);
request.getRequestDispatcher("/show.jsp").forward(request,response);

//在show.jsp中使用request域的属性
<%= (String) request.getAttribute("result") %>

作用:

在整个请求链范围内共享数据,通常我们在Servlet中处理好的数据会存入request域后将请求转发到jsp页面来进行展示。


2.4.5 实现请求转发和请求包含

可以像ServletContext对象一样实现请求转发:

request.getRequestDispatcher("").forward(request,response);

请求转发时,如果已经有数据被写入到了request的缓冲区,但这些数据还没有被发送到客户端,则请求转发时,这些数据将会被清空

response.getWriter().write("something...");
resquest.getRequestDispatcher("/servlet/demo").forward(request,response);

//servlet.demo
response.getWriter().write("other things...");

//最后输出的只是other things,而something被清空了

但是被清空的只是响应中的实体内容头信息并不会被清空(比如setContentType("text/html;charset=utf-8"))。

如果写入了缓冲区,并且还强制刷新了缓冲区,则会报错:

response.getWriter().write("something...");
response.getWriter().flush();//将缓冲区信息输出
resquest.getRequestDispatcher("/servlet/demo").forward(request,response);//会报错,无法转发

请求转发时,如果已经有数据发送给了浏览器,那么再进行请求转发,不能成功,会抛出异常。

一个Servlet中两次请求转发也是不行的。


请求包含:

将两个资源的输出进行合并后再输出,同样也是使用了转发器对象。

this.getServletContext().getRequestDispatcher("").include(request,response);

request.getRequestDispatcher("").include(request,response);

被包含的Servlet程序不能改变响应消息的状态码和响应头,如果它里面存在这样的语句,这些语句的执行结果将被忽略。

//servletA.java
response.getWriter().write("from servletA");
request.getRequestDispatcher("/servletB").include(request,response);

//servletB.java
response.getWriter().write("from servletB");

//最后会输出 from servletA from servletB
//这就是请求包含的作用,一般常用来做页面布局,将重复的header footer包含


2.4.6 请求重定向和请求转发

请求重定向:response.sendRedirect();
请求转发:  request.getRequestDispatcher().forward();
请求包含:  request.getRequestDispatcher().include();
  • 如果需要在资源跳转时利用request域传递域属性则必须使用请求转发。
  • 如果希望资源跳转后修改用户的地址栏则使用请求重定向。
  • 如果使用请求转发和重定向都可以,则优先使用请求转发,减少浏览器对服务器的访问次数。

猜你喜欢

转载自blog.csdn.net/longyin0528/article/details/80890388