浅谈Servlet的页面跳转

来源:互联网 发布:无法触碰 知乎 编辑:程序博客网 时间:2024/04/29 16:53

注意:本文的观点立足于Java和Tomcat源码


在Servlet中页面的跳转有两种方式实现:转发重定向

举个工作中的常见例子,测试A发现了一个问题,去找码农B,然后把问题描述和B巴拉巴拉说了一通,然后B一想这问题还得码农C处理一部分问题的,B跑过去和C巴拉巴拉描述了这个问题。这个过程就是转发。如果B听完描述后对A说:这不是我负责的事情,你应该去找码农C,然后测试A找到C重新描述了一遍这个问题。这种场景就是转发。


转发:

转发的意义在于分工,用软件工程的术语就是”分而治之“。如果比较复杂的问题可能需要分成多个步骤处理,每个步骤负责解决其中的一部分。
可以通过ServletRequest的request.getRequestDispatcher("").forward(request, response);方法转发。也可以通过
ServletContext的.getRequestDispatcher("").forward(request, response);方式转发。都要获取一个javax.servlet.RequestDispatcher对象。

我们看Tomcat关于org.apache.catalina.core.ApplicationContext转发的代码实现:
 /**     * Return a <code>RequestDispatcher</code> instance that acts as a     * wrapper for the resource at the given path.  The path must begin     * with a "/" and is interpreted as relative to the current context root.     *     * @param path The path to the desired resource.     */    @Override    public RequestDispatcher getRequestDispatcher(String path) {        // Validate the path argument        if (path == null)            return (null);        if (!path.startsWith("/"))            throw new IllegalArgumentException                (sm.getString                 ("applicationContext.requestDispatcher.iae", path));        // Get query string        String queryString = null;        String normalizedPath = path;        int pos = normalizedPath.indexOf('?');        if (pos >= 0) {            queryString = normalizedPath.substring(pos + 1);            normalizedPath = normalizedPath.substring(0, pos);        }        normalizedPath = RequestUtil.normalize(normalizedPath);        if (normalizedPath == null)            return (null);        pos = normalizedPath.length();        // Use the thread local URI and mapping data        DispatchData dd = dispatchData.get();        if (dd == null) {            dd = new DispatchData();            dispatchData.set(dd);        }        MessageBytes uriMB = dd.uriMB;        uriMB.recycle();        // Use the thread local mapping data        MappingData mappingData = dd.mappingData;        // Map the URI        CharChunk uriCC = uriMB.getCharChunk();        try {            uriCC.append(context.getPath(), 0, context.getPath().length());            /*             * Ignore any trailing path params (separated by ';') for mapping             * purposes             */            int semicolon = normalizedPath.indexOf(';');            if (pos >= 0 && semicolon > pos) {                semicolon = -1;            }            uriCC.append(normalizedPath, 0, semicolon > 0 ? semicolon : pos);            service.getMapper().map(context, uriMB, mappingData);            if (mappingData.wrapper == null) {                return (null);            }            /*             * Append any trailing path params (separated by ';') that were             * ignored for mapping purposes, so that they're reflected in the             * RequestDispatcher's requestURI             */            if (semicolon > 0) {                uriCC.append(normalizedPath, semicolon, pos - semicolon);            }        } catch (Exception e) {            // Should never happen            log(sm.getString("applicationContext.mapping.error"), e);            return (null);        }        Wrapper wrapper = mappingData.wrapper;        String wrapperPath = mappingData.wrapperPath.toString();        String pathInfo = mappingData.pathInfo.toString();        mappingData.recycle();        // Construct a RequestDispatcher to process this request        return new ApplicationDispatcher            (wrapper, uriCC.toString(), wrapperPath, pathInfo,             queryString, null);    }
这个对应的是ServletContext的实现方式。下面看ServletRequest的转发实现源码:
 /**     * Return a RequestDispatcher that wraps the resource at the specified     * path, which may be interpreted as relative to the current request path.     *     * @param path Path of the resource to be wrapped     */    @Override    public RequestDispatcher getRequestDispatcher(String path) {        if (context == null)            return (null);        // If the path is already context-relative, just pass it through        if (path == null)            return (null);        else if (path.startsWith("/"))            return (context.getServletContext().getRequestDispatcher(path));        // Convert a request-relative path to a context-relative one        String servletPath =            (String) getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);        if (servletPath == null)            servletPath = getServletPath();        // Add the path info, if there is any        String pathInfo = getPathInfo();        String requestPath = null;        if (pathInfo == null) {            requestPath = servletPath;        } else {            requestPath = servletPath + pathInfo;        }        int pos = requestPath.lastIndexOf('/');        String relative = null;        if (pos >= 0) {            relative = requestPath.substring(0, pos + 1) + path;        } else {            relative = requestPath + path;        }        return (context.getServletContext().getRequestDispatcher(relative));    }

可以看出来org.apache.catalina.core.ApplicationHttpRequest的getRequestDispatcher方式将相对路径转换成了相对于根上下文的绝对路径。
所以通过Request获得的转发支持绝对路径和相对路径,而通过ServletContext获取的RequestDispatcher只支持绝对路径,并且绝对路径不要包含应用名。

重定向

重定向是服务器告诉浏览器去重新请求另外一个有效的URL。可以使用javax.servlet.http.HttpServletResponse.sendRedirect(String)方法实现。

区别:

首先转发是服务器端的行为而重定向是客户端的行为。转发只能发生在应用内而重定向可以跨站跨应用。服务器转发的时候浏览器客户端感知不到,而当浏览器重定向时,浏览器地址的URL也相对会变成新的URL。重定向一次其实浏览器发送了两次请求。





不积跬步无以致千里




1 0
原创粉丝点击