第 3 章 请求的跳转与转发

注意

应用广泛,而又没几个人明白的东西上场了,这章就只讨论这一个问题。

如果你确认自己明白下面这个问题,请直接进入下一章:第 4 章 四个作用域

  1. forward, redirect的区别。

3.1. 范例

这次用户可以在首页选择自己喜欢的颜色,进入对应的页面。

选择绿色,会进入绿色界面:

选择红色,会进入红色界面:

好的,这里我们会看到四个页面:

  1. index.jsp中选择颜色,点击按钮后提交到test.jsp。

  2. test.jsp取得用户选择的颜色,根据颜色值显示对应的页面。

  3. 如果选择了红色,就显示red.jsp。

  4. 如果选择了绿色,就显示green.jsp。

在这里例子里,index.jsp,red.jsp,green.jsp中的内容都是一样的,所有的玄机都在test.jsp中。

现在面临的问题是如何在test.jsp决定实现red.jsp或者green.jsp,我们可以在forward和redirect中任选其一。

3.2. 如果用forward

test.jsp中需要这样写:

<%@ page contentType="text/html; charset=gb2312"%>
<%
    String color = request.getParameter("color");
    if ("red".equals(color)) {
        request.getRequestDispatcher("red.jsp").forward(request, response);
    } else if ("green".equals(color)) {
        request.getRequestDispatcher("green.jsp").forward(request, response);
    }
%>
        

略过取得参数与比较参数值不提,只关注forward的部分:

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

首先调用request的getRequestDispatcher()方法,获得对应red.jsp的转发器,然后调用forward()方法执行请求转发。结果用户看到的就是red.jsp中的结果了,一个红色的页面。

这里请大家注意一下浏览器的url地址:

选择红色页面时:

选择绿色页面时:

于是,无论转发至red.jsp还是green.jsp,地址栏上显示的都是test.jsp。

这是为什么呢?通过下面的流程图会让我们容易理解:

  1. 浏览器向test.jsp发送请求。

  2. test.jsp计算客户选择的颜色,将请求转发至red.jsp。

  3. red.jsp返回响应给浏览器。

这下知道为什么浏览器的地址没有变化了吧?因为浏览器只是执行了对test.jsp的请求,test.jsp到red.jsp的部分是在服务器内执行的,浏览器并不知道服务器里到底发生了什么,它只知道自己获得的响应是test.jsp发回来的,甚至不知道服务器还有个red.jsp。

这就是请求转发forward了。例子见lingo-sample/03-01/。

3.3. 如果用redirect

test.jsp中需要这样写:

<%@ page contentType="text/html; charset=gb2312"%>
<%
    String color = request.getParameter("color");
    if ("red".equals(color)) {
        response.sendRedirect("red.jsp");
    } else if ("green".equals(color)) {
        response.sendRedirect("green.jsp");
    }
%>
        

略过取得参数与比较参数值不提,只关注redirect的部分:

response.sendRedirect("red.jsp");
        

response翻译过来就是响应,代表着http响应。调用response的sendRedirect("red.jsp")方法,将页面重定向到red.jsp。

再请大家注意一下浏览器的url地址:

选择红色页面时:

选择绿色页面时:

与forward不同,url地址一直在变化,红色的时候显示red.jsp,绿色的时候显示green.jsp。

再看一下流程图吧:

  1. 浏览器向test.jsp发送请求。

  2. test.jsp计算客户选择的颜色,向浏览器发送一个页面重定向(redirect)的响应,响应中包含red.jsp的url地址。

  3. 浏览器根据页面重定向(redirect)响应中的red.jsp地址,再次向服务器发送请求,这次请求的就是red.jsp了。

  4. red.jsp执行,返回响应。

redirect会触发另一个请求响应流程,第二次请求的时候是由浏览器发起对red.jsp的请求,所以url地址改变了。

这就是页面重定向redirect了。例子见lingo-sample/03-02/。

3.4. forward和redirect的问题

3.4.1. 绝对路径与相对路径

  1. 如果咱们使用的URL网址是以“/”开头的,那么这个网址就叫做绝对路径。

  2. 如果咱们使用的URL网址不是“/”开头的,那么这个网址就叫做相对路径。

3.4.1.1. 相对路径

在相对路径上,两者的表现是相同的。

看看lingo-sample/03-03/这个例子,如果我们去请求relative/forward.jsp或redirect.jsp,然后从这里再跳转向它下面的result/result.jsp会怎样呢?

  1. forward的例子:

    <%request.getRequestDispatcher("result/result.jsp").forward(request, response);%>
                            

    这里的相对路径就是result/result.jsp。

    因为刚刚请求的test.jsp是在/03-03/relative/下,所以我们的当前路径就是/03-03/relative/,执行forward的时候会寻找当前路径下的result/result.jsp,找到之后便转发请求。

  2. redirect的例子:

    <%response.sendRedirect("result/result.jsp");%>
                            

    这里的相对路径也是result/result.jsp。

    因为刚刚请求的test.jsp是在/03-03/relative/下,所以我们的当前路径就是/03-03/relative/,执行redirect的时候会把当前路径加上result/result.jsp,把结果作为重定向的地址发送给浏览器,浏览器再去请求/03-03/relative/result/result.jsp,从而得到响应。

3.4.1.2. 绝对路径

问题出现了,绝对路径在forward和redirect中出现了差别,还是刚才的情况,但使用绝对路径的时候写法便不同了。

  1. forward的例子:

    <%request.getRequestDispatcher("/relative/result/result.jsp").forward(request, response);%>
                            

    这里的绝对路径就是/relative/result/result.jsp。

    在本地测试时,forward把http://localhost:8080/03-03/当作根路径,在它的基础上计算绝对路径。

    这是由jsp的部署方式决定的,webapp里可以放好多项目,为了让这些项目可以互不影响、独立运行,不能让请求从一个项目直接在服务器内部转移到另一个项目。为了防止出现这种情况,在执行forward的时候干脆把项目的路径当作根目录,开发者看不到其他项目,也就不会出现问题了。

  2. redirect的例子:

    <%response.sendRedirect("/03-03/absolute/result/result.jsp");%>
                            

    这里的绝对路径却是/03-03/absolute/result/result.jsp。

    在本地测试时,redirect把http://localhost:8080/当作根路径,在它的基础上计算绝对路径。

    因为redirect会让浏览器重新发起一个新请求,所以不会搅乱服务器里多个项目之间的关系,也就不需要对它做限制,如果需要在多个项目之间进行跳转,就只能使用redirect。不过因为重新发起了新的请求,上次请求的那些数据都会丢失,如果有什么重要的数据,记得要重新设置。

3.4.2. forward导致找不到图片

找不到图片,找不到js脚本,找不到css样式表,都属于这个问题。

要演示这个问题,是非常容易的,只需要满足两个条件:

  1. forward前后的jsp页面不在一个目录下。

  2. forward后的jsp页面里使用相对路径引用一些资源,图片,js脚本,css样式表什么的。

03-04里就模拟了这样一个环境,你进入http://localhost:8080/03-04/,选择“有问题的”:

打开03-04可以看到如下的目录结构:

|--+ 03-04
   |--- index.jsp
   |--- test.jsp
   |--+ result
      |--- success.jsp
      |--- failure.jsp
      |--- lingo.png
            

刚才咱们看到的页面是failure.jsp,它里边显示图片的部分是:

<img src="lingo.png" />
            

这时候就有疑问了,lingo.png和failure.jsp明明在同一个目录下,为什么无法显示。

现在请在无法显示的图片上,点击鼠标右键,选择属性,让我们看一下图片的请求地址:

图片的位置本来在http://localhost:8080/03-04/result/lingo.png,但请求的地址却是http://localhost:8080/03-04/lingo.png。问题就是丢掉了中间的/result。

再试一次index.jsp上的“没问题的”:

这次我们看到的页面是success.jsp,它里边显示图片的部分是:

<img src="result/lingo.png" />
            

结果手工加上result这段路径后就可以显示图片了。

这个问题还要追溯到浏览器对html的处理方式,在html里包含的图片,css样式表,js脚本,视频等等外部资源,都需要浏览器再次向服务器发起请求。

如果这些外部资源使用了相对路径,浏览器就会在当前请求路径的基础上,加上相对路径拼接出完整的http请求,发送给服务器。这个例子中,我们请求http://localhost:8080/03-04/test.jsp,浏览器得到的当前路径就是http://localhost:8080/03-04/,failure.jsp中图片的相对路径是lingo.png,那么拼接的结果是http://localhost:8080/03-04/lingo.png。

不要怪浏览器太傻,是因为使用forward的时候浏览器并不清楚这些改变。它一直认为,既然自己请求的是test.jsp,返回的自然就是test.jsp的内容,那么再使用test.jsp当作当前路径去计算相对路径当然没有问题。是我们欺骗了浏览器,在服务器偷偷改变了请求流向,返回了其他页面的内容。

清楚了以上的请求流程,就知道如何应对这种问题了。

  1. 第一种方法:不要在不同目录之间使用forward做请求转发,保证当前路径不发生变化。

  2. 第二种方法:像上例一样修改图片路径,或全部改为绝对路径。

请根据实际需要进行选择。