网上已经有很多关于redirect和forward区别的文章,更多的都是只是一些概念上的描述,虽然在大多情况下,知道这些就已经足够了。但也有例外:forward not working for struts2,why?我也是在工作中碰到了这个问题,才特意看了下tomcat有关这部分的源代码。深刻的了解下也无妨。
redirect和forward都是属于servlet规范的,不同的servlet容器的实现可能会有一些区别,但原理都是类似的。
redirect和forward的定义:
1. redirect(重定向):服务端发送给客户端一个重定向的临时响应头,这个响应头包含重定向之后的URL,客户端用新的URL重新向服务器发送一个请求。
2. forward(请求转向):服务器程序内部请求转向,这个特性允许前一个程序用于处理请求,而后一个程序用来返回响应。
Redirect的原理比较简单,它的定义也已经描述的很清楚了,我也不想多讲什么,就贴一段简单的代码吧!
org.apache.catalina.connector.Response#sendRedirect(String):
- public void sendRedirect(String location)
- throws IOException {
- if (isCommitted())
- throw new IllegalStateException
- (sm.getString("coyoteResponse.sendRedirect.ise"));
- // Ignore any call from an included servlet
- if (included)
- return;
- // Clear any data content that has been buffered
- resetBuffer();
- // Generate a temporary redirect to the specified location
- try {
- String absolute = toAbsolute(location);
- setStatus(SC_FOUND);
- setHeader("Location", absolute);
- } catch (IllegalArgumentException e) {
- setStatus(SC_NOT_FOUND);
- }
- // Cause the response to be finished (from the application perspective)
- setSuspended(true);
- }
方法行为:先把相对路径转换成绝对路径,再包装一个包含有新的URL的临时响应头,“SC_FOUND”的值是302,就是重定向临时响应头的状态码。如果传入的“location”值不合法,就包装一个404的响应头。
下面就来看看tomcat是如何实现forward的,forward为什么在struts2下会无效(注解:其实是可以设置的)。
先看下程序是如何调用forward的:
- req.getRequestDispatcher("testForward").forward(req, resp);
整个过程分两个步骤来执行
1. 得到一个请求调度器
2. 通过调度器把请求转发过去。
第一步骤,获取请求调度器。
org.apache.catalina.connector.Request#getRequestDispatcher(String)
- public RequestDispatcher getRequestDispatcher(String path) {
- if (request == null) {
- throw new IllegalStateException(
- sm.getString("requestFacade.nullRequest"));
- }
- if (Globals.IS_SECURITY_ENABLED){
- return (RequestDispatcher)AccessController.doPrivileged(
- new GetRequestDispatcherPrivilegedAction(path));
- } else {
- return request.getRequestDispatcher(path);
- }
方法行为:把获取RequestDispatcher的任务交个内部的request。它们之间的关系如下所示
org.apache.catalina.connector.RequestFacade和类org.apache.catalina.connector.Request都是实现了javax.servlet.http.HttpServletRequest接口,而RequestFacade内部有包装了个Request,对Request的访问做了些控制,应该是代理模式
org.apache.catalina.connector.Request#getRequestDispatcher(String)
- public RequestDispatcher getRequestDispatcher(String path) {
- if (path.startsWith("/"))
- return (context.getServletContext().getRequestDispatcher(path));
- //省略了部分代码
- return (context.getServletContext().getRequestDispatcher(relative));
- }
方法行为:把绝对路径转换成相对路径,最终的格式如“/testForward”。若已经是这种格式的相对路径,就无需再转换了。
接下来就转交给ServletContext来处理,ServletContext是web项目的一个上下文,包含所有的Servlet集合,还定义了一些Servlet与容器之间交互的接口。
org.apache.catalina.core.ApplicationContext#getRequestDispatcher(String)
- public RequestDispatcher getRequestDispatcher(String path) {
- //省去部分代码
- context.getMapper().map(uriMB, mappingData);
- //省去部分代码
- Wrapper 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);
- }
方法行为:根据路径名“path”找到一个包含有Servlet的Wrapper,最后实例化一个ApplicationDispatcher,并且返回该ApplicationDispatcher。
该方法里非常关键的一行:context.getMapper().map(uriMB, mappingData)。
Mapper的类定义我不知道如何描述,就贴上原文吧:Mapper, which implements the servlet API mapping rules (which are derived from the HTTP rules)。
不过只想了解forward的原理,熟悉map函数就够了。
org.apache.tomcat.util.http.mapper.Mapper#map(org.apache.tomcat.util.buf.MessageBytes, org.apache.tomcat.util.http.mapper.MappingData):
- public void map(MessageBytes uri, MappingData mappingData)
- throws Exception {
- uri.toChars();
- CharChunk uricc = uri.getCharChunk();
- uricc.setLimit(-1);
- internalMapWrapper(context, uricc, mappingData);
- }
方法行为:。。。。。。。就介绍下参数吧,uri可以理解是path(“/testforward”)的一个变形,而mappingData用于存储当前线程用到的部分数据。该函数是没有返回值的,处理之后的结果就是存放到mappingData里的。
org.apache.tomcat.util.http.mapper.Mapper#internalMapWrapper(Mapper$Context,org.apache.tomcat.util.buf.CharChunk, org.apache.tomcat.util.http.mapper.MappingData):
- private final void internalMapWrapper(Context context, CharChunk path,
- MappingData mappingData)
- throws Exception {
- int pathOffset = path.getOffset();
- int pathEnd = path.getEnd();
- int servletPath = pathOffset;
- boolean noServletPath = false;
- int length = context.name.length();
- if (length != (pathEnd - pathOffset)) {
- servletPath = pathOffset + length;
- } else {
- noServletPath = true;
- path.append('/');
- pathOffset = path.getOffset();
- pathEnd = path.getEnd();
- servletPath = pathOffset+length;
- }
- path.setOffset(servletPath);
- // Rule 1 -- Exact Match
- Wrapper[] exactWrappers = context.exactWrappers;
- internalMapExactWrapper(exactWrappers, path, mappingData);
- // Rule 2 -- Prefix Match
- boolean checkJspWelcomeFiles = false;
- Wrapper[] wildcardWrappers = context.wildcardWrappers;
- if (mappingData.wrapper == null) {
- internalMapWildcardWrapper(wildcardWrappers, context.nesting,
- path, mappingData);
- if (mappingData.wrapper != null && mappingData.jspWildCard) {
- char[] buf = path.getBuffer();
- if (buf[pathEnd - 1] == '/') {
- /*
- * Path ending in '/' was mapped to JSP servlet based on
- * wildcard match (e.g., as specified in url-pattern of a
- * jsp-property-group.
- * Force the context's welcome files, which are interpreted
- * as JSP files (since they match the url-pattern), to be
- * considered. See Bugzilla 27664.
- */
- mappingData.wrapper = null;
- checkJspWelcomeFiles = true;
- } else {
- // See Bugzilla 27704
- mappingData.wrapperPath.setChars(buf, path.getStart(),
- path.getLength());
- mappingData.pathInfo.recycle();
- }
- }
- }
- if(mappingData.wrapper == null && noServletPath) {
- // The path is empty, redirect to "/"
- mappingData.redirectPath.setChars
- (path.getBuffer(), pathOffset, pathEnd);
- path.setEnd(pathEnd - 1);
- return;
- }
- // Rule 3 -- Extension Match
- Wrapper[] extensionWrappers = context.extensionWrappers;
- if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
- internalMapExtensionWrapper(extensionWrappers, path, mappingData);
- }
- // Rule 4 -- Welcome resources processing for servlets
- if (mappingData.wrapper == null) {
- boolean checkWelcomeFiles = checkJspWelcomeFiles;
- if (!checkWelcomeFiles) {
- char[] buf = path.getBuffer();
- checkWelcomeFiles = (buf[pathEnd - 1] == '/');
- }
- if (checkWelcomeFiles) {
- for (int i = 0; (i < context.welcomeResources.length)
- && (mappingData.wrapper == null); i++) {
- path.setOffset(pathOffset);
- path.setEnd(pathEnd);
- path.append(context.welcomeResources[i], 0,
- context.welcomeResources[i].length());
- path.setOffset(servletPath);
- // Rule 4a -- Welcome resources processing for exact macth
- internalMapExactWrapper(exactWrappers, path, mappingData);
- // Rule 4b -- Welcome resources processing for prefix match
- if (mappingData.wrapper == null) {
- internalMapWildcardWrapper
- (wildcardWrappers, context.nesting,
- path, mappingData);
- }
- // Rule 4c -- Welcome resources processing
- // for physical folder
- if (mappingData.wrapper == null
- && context.resources != null) {
- Object file = null;
- String pathStr = path.toString();
- try {
- file = context.resources.lookup(pathStr);
- } catch(NamingException nex) {
- // Swallow not found, since this is normal
- }
- if (file != null && !(file instanceof DirContext) ) {
- internalMapExtensionWrapper(extensionWrappers,
- path, mappingData);
- if (mappingData.wrapper == null
- && context.defaultWrapper != null) {
- mappingData.wrapper =
- context.defaultWrapper.object;
- mappingData.requestPath.setChars
- (path.getBuffer(), path.getStart(),
- path.getLength());
- mappingData.wrapperPath.setChars
- (path.getBuffer(), path.getStart(),
- path.getLength());
- mappingData.requestPath.setString(pathStr);
- mappingData.wrapperPath.setString(pathStr);
- }
- }
- }
- }
- path.setOffset(servletPath);
- path.setEnd(pathEnd);
- }
- }
方法行为:通过“path”从“context”里找到对应的Servlet,存放到“mappingData”里。
可以看到这里有7个匹配Servlet规则:
1. Rule 1 -- Exact Match:精确匹配,匹配web.xml配置的格式如“<url-pattern>/testQiu</url-pattern>”的Servlet
2. Rule 2 -- Prefix Matcha:前缀匹配,匹配的Servlet格式如“<url-pattern>/testQiu/*</url-pattern>”
3. Rule 3 -- Extension Match:扩展匹配,匹配jsp或者jspx
4. ---Rule 4a -- Welcome resources processing for exact macth:
5. ---Rule 4b -- Welcome resources processing for prefix match:
6. ---Rule 4c -- Welcome resources processing for physical folder:
7. Rule 7 --如果前面6条都没匹配到,那就返回org.apache.catalina.servlets.DefaultServlet。
其实这里真正的匹配的是Wapper,而不是Servlet,因为Wapper最重要的一个属性就是Servlet,说成“匹配Servlet”是为了更容易的表达。
至此返回RequestDispatcher就结束了。
接下来就是讲解RequestDispatcher.forward了。Forward的就不贴出全部的源代码,只贴一些重要的片段,绝大部分的逻辑都在org.apache.catalina.core.ApplicationDispatcher类里。
先描述下过程:
1. 设置request里的部分属性值,如:请求的路径、参数等。
2. 组装一个FilterChain链,调用doFilter方法。
3. 最后根据实际情况调用Filter的doFilter函数或者Servlet的service函数。
注:FilterChain和Filter是两个不同的接口,两个接口的UML
org.apache.catalina.core.ApplicationDispatcher#doForward(ServletRequest,ServletResponse):
- private void doForward(ServletRequest request, ServletResponse response)
- throws ServletException, IOException
- //省略了部分代码
- // Handle an HTTP named dispatcher forward
- if ((servletPath == null) && (pathInfo == null)) {
- ApplicationHttpRequest wrequest =
(ApplicationHttpRequest) wrapRequest(state);
HttpServletRequest hrequest = state.hrequest;
wrequest.setRequestURI(hrequest.getRequestURI());
wrequest.setContextPath(hrequest.getContextPath());
wrequest.setServletPath(hrequest.getServletPath());
wrequest.setPathInfo(hrequest.getPathInfo());
wrequest.setQueryString(hrequest.getQueryString());
processRequest(request,response,state); - //省略了部分代码
- } else {// Handle an HTTP path-based forward
- ApplicationHttpRequest wrequest =
- (ApplicationHttpRequest) wrapRequest(state);
- String contextPath = context.getPath();
- HttpServletRequest hrequest = state.hrequest;
- if (hrequest.getAttribute(Globals.FORWARD_REQUEST_URI_ATTR) == null) {
- wrequest.setAttribute(Globals.FORWARD_REQUEST_URI_ATTR,
- hrequest.getRequestURI());
- wrequest.setAttribute(Globals.FORWARD_CONTEXT_PATH_ATTR,
- hrequest.getContextPath());
- wrequest.setAttribute(Globals.FORWARD_SERVLET_PATH_ATTR,
- hrequest.getServletPath());
- wrequest.setAttribute(Globals.FORWARD_PATH_INFO_ATTR,
- hrequest.getPathInfo());
- wrequest.setAttribute(Globals.FORWARD_QUERY_STRING_ATTR,
- hrequest.getQueryString());
- }
- wrequest.setContextPath(contextPath);
- wrequest.setRequestURI(requestURI);
- wrequest.setServletPath(servletPath);
- wrequest.setPathInfo(pathInfo);
- if (queryString != null) {
- wrequest.setQueryString(queryString);
- wrequest.setQueryParams(queryString);
- }
- processRequest(request,response,state);
- }
- }
-
- if (wrapper.getLogger().isDebugEnabled() )
wrapper.getLogger().debug(" Disabling the response for futher output");
if (response instanceof ResponseFacade) {
((ResponseFacade) response).finish();
} else {
// Servlet SRV.6.2.2. The Request/Response may have been wrapped
// and may no longer be instance of RequestFacade
。。。
// Close anyway
try {
PrintWriter writer = response.getWriter();
writer.close();
} catch (IllegalStateException e) {
。。
}
} - }
private ServletRequest wrapRequest(State state) {
// Locate the request we should insert in front of
ServletRequest previous = null;
ServletRequest current = state.outerRequest;
ServletRequest wrapper = null;
//省略
if ((current instanceof ApplicationHttpRequest) ||
(current instanceof Request) ||
(current instanceof HttpServletRequest)) {
//省略
wrapper = new ApplicationHttpRequest(hcurrent, context, crossContext);
} else {
wrapper = new ApplicationRequest(current);
}
}
下面是 ApplicationRequest类class ApplicationRequest extends ServletRequestWrapper
public ApplicationRequest(ServletRequest request) {
super(request);
setRequest(request);
}
private void processRequest(ServletRequest request, ServletResponse response, State state) throws IOException, ServletException { DispatcherType disInt = (DispatcherType) request.getAttribute(ApplicationFilterFactory.DISPATCHER_TYPE_ATTR); if (disInt != null) { .... invoke(state.outerRequest, response, state); } else { invoke(state.outerRequest, response, state); } } }
private void invoke(ServletRequest request, ServletResponse response, State state) throws IOException, ServletException { // Checking to see if the context classloader is the current context // classloader. If it's not, we're saving it, and setting the context // classloader to the Context classloader ClassLoader oldCCL = Thread.currentThread().getContextClassLoader(); ClassLoader contextClassLoader = context.getLoader().getClassLoader(); if (oldCCL != contextClassLoader) { Thread.currentThread().setContextClassLoader(contextClassLoader); } else { oldCCL = null; } // Initialize local variables we may need HttpServletResponse hresponse = state.hresponse; Servlet servlet = null; IOException ioException = null; ServletException servletException = null; RuntimeException runtimeException = null; boolean unavailable = false; // Check for the servlet being marked unavailable if (wrapper.isUnavailable()) { wrapper.getLogger().warn( sm.getString("applicationDispatcher.isUnavailable", wrapper.getName())); long available = wrapper.getAvailable(); if ((available > 0L) && (available < Long.MAX_VALUE)) hresponse.setDateHeader("Retry-After", available); hresponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm .getString("applicationDispatcher.isUnavailable", wrapper .getName())); unavailable = true; } // Allocate a servlet instance to process this request try { if (!unavailable) { servlet = wrapper.allocate(); } } catch (ServletException e) { wrapper.getLogger().error(sm.getString("applicationDispatcher.allocateException", wrapper.getName()), StandardWrapper.getRootCause(e)); servletException = e; } catch (Throwable e) { ExceptionUtils.handleThrowable(e); wrapper.getLogger().error(sm.getString("applicationDispatcher.allocateException", wrapper.getName()), e); servletException = new ServletException (sm.getString("applicationDispatcher.allocateException", wrapper.getName()), e); servlet = null; } // Get the FilterChain Here ApplicationFilterFactory factory = ApplicationFilterFactory.getInstance(); ApplicationFilterChain filterChain = factory.createFilterChain(request, wrapper,servlet); // Call the service() method for the allocated servlet instance try { support.fireInstanceEvent(InstanceEvent.BEFORE_DISPATCH_EVENT, servlet, request, response); // for includes/forwards if ((servlet != null) && (filterChain != null)) { filterChain.doFilter(request, response); } // Servlet Service Method is called by the FilterChain support.fireInstanceEvent(InstanceEvent.AFTER_DISPATCH_EVENT, servlet, request, response); } catch (ClientAbortException e) { 。。。 } // Release the filter chain (if any) for this request try { if (filterChain != null) filterChain.release(); } catch (Throwable e) { 。。 } // FIXME: Exception handling needs to be similar to what is in the StandardWrapperValue // Deallocate the allocated servlet instance try { if (servlet != null) { wrapper.deallocate(servlet); } } catch (ServletException e) { 。。 } // Reset the old context class loader if (oldCCL != null) Thread.currentThread().setContextClassLoader(oldCCL); // Unwrap request/response if needed // See Bugzilla 30949 unwrapRequest(state); unwrapResponse(state); // Recycle request if necessary (also BZ 30949) recycleRequestWrapper(state); // Rethrow an exception if one was thrown by the invoked servlet if (ioException != null) throw ioException; if (servletException != null) throw servletException; if (runtimeException != null) throw runtimeException; }
第1步:设置新的request的属性:
- wrequest.setContextPath(contextPath);
- wrequest.setRequestURI(requestURI);
- wrequest.setServletPath(servletPath);
- wrequest.setPathInfo(pathInfo);
- if (queryString != null) {
- wrequest.setQueryString(queryString);
- wrequest.setQueryParams(queryString);
- }
第2步: 分配一个Servelt实例
servlet = wrapper.allocate();
// 如果 用户请求的是用户自己写的Servlet,就会返回直接由开发人员自己编写的servlet实例,
//如果请求是一个.JSP文件时,Servlet是org.apache.jasper.servlet.JspServlet的实例,
// 如果请求是一个静态资源时,Servlet用的是org.apache.catalina.servlets.DefaultServlet实例。
servlet = wrapper.allocate();
//在获取这个servlet实例时都调用了 servlet.init()方法了对资源进行初始化。
第3步:组装FitlerChain链,根据web.xml配置信息,是否决定添加Filter----
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
org.apache.catalina.core.ApplicationFilterFactory#createFilterChain(ServletRequest, Wrapper, Servlet):
- public ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {
- //省略部分代码
- filterChain = new ApplicationFilterChain();
- }
- filterChain.setServlet(servlet);
- filterChain.setSupport
- (((StandardWrapper)wrapper).getInstanceSupport());
- // Acquire the filter mappings for this Context
- StandardContext context = (StandardContext) wrapper.getParent();
- FilterMap filterMaps[] = context.findFilterMaps();
- // If there are no filter mappings, we are done
- if ((filterMaps == null) || (filterMaps.length == 0))
- return (filterChain);
- // Acquire the information we will need to match filter mappings
- String servletName = wrapper.getName();
- // Add the relevant path-mapped filters to this filter chain
- for (int i = 0; i < filterMaps.length; i++) {
- if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
- continue;
- }
- if (!matchFiltersURL(filterMaps[i], requestPath))
- continue;
- ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
- context.findFilterConfig(filterMaps[i].getFilterName());
- if (filterConfig == null) {
- ; // FIXME - log configuration problem
- continue;
- }
- boolean isCometFilter = false;
- if (comet) {
- try {
- isCometFilter = filterConfig.getFilter() instanceof CometFilter;
- } catch (Exception e) {
- // Note: The try catch is there because getFilter has a lot of
- // declared exceptions. However, the filter is allocated much
- // earlier
- }
- if (isCometFilter) {
- filterChain.addFilter(filterConfig);
- }
- } else {
- filterChain.addFilter(filterConfig);
- }
- }
- //省略部分代码
- // Return the completed filter chain
- return (filterChain);
- }
如果是<dispatcher>REQUEST</dispatcher>,那就不添加Filter,默认设置是REQUEST
如果是<dispatcher>FORWARD</dispatcher>,添加Filter到FilterChain。
第4步:调用doFilter或者service,代码删减了很多。
org.apache.catalina.core.ApplicationFilterChain#doFilter(ServletRequest, ServletResponse):
- public void doFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {
- internalDoFilter(request,response);
- }
- org.apache.catalina.core.ApplicationFilterChain#internalDoFilter(ServletRequest, ServletResponse)
- private void internalDoFilter(ServletRequest request,
- ServletResponse response)
- throws IOException, ServletException {
- // Call the next filter if there is one
- if (pos < n) {
- filter.doFilter(request, response, this);
- return;
- }
- servlet.service((HttpServletRequest) request,(HttpServletResponse) response);
- // //最终
- Filter全部调用完毕后,就会把请求真正的传递给Servlet了,调用它的#service()方法。
servlet分别对应着对Servlet,JSP,和静态资源的处理。
如果是真正Servlet比较简单,因为这里的"servlet"就是真实的Servlet实例了, - 直接调用开发人员自己编写的#service()方法了
- (#service()内部是会调用#doGet(),#doPost()等方法的)。
对于静态资源,是调用org.apache.catalina.servlets.DefaultServlet的#service()方法,由于DefaultServlet并没有重写这个方法,所以直接使用HttpServlet的#service()方法。但是DefaultServlet重写了#doGet(),#doPost()等方法(#doPost()内部又调用了#doGet()), - 所以请求就又到了#doGet()这个方法中,DefaultServlet的#doGet()只调用了#serveResource()这个方法来提取资源,代码太长,就不再仔细的看了。
如果是JSP资源,要调用org.apache.jasper.servlet.JspServlet的#service()方法进行
// 获得请求的JSP文件的路径
// 获取JspServletWrapper实例 然后调用org.apache.jasper.servlet.JspServletWrapper的service()方法
进行 // (1) 编译 // (2) 载入由jsp变成的Servlet类文件 // (3) 调用Servlet.service(req,resp)处理请求 - }
如果我对Filter非常了解的,根本就不需要花那么多时间去查看tomcat源代码。只要在web.xml增加一点配置就OK了。
- <filter-mapping>
- <filter-name>struts2</filter-name>
- <url-pattern>/*</url-pattern>
- <dispatcher>REQUEST</dispatcher>
- <dispatcher>FORWARD</dispatcher>
- </filter-mapping>
相关推荐
- `forward()`方法将请求转发给另一个资源,而`redirect()`则告诉客户端重新发起一个新的请求。 6. **过滤器(Filter)**: - Filter是Servlet技术的一部分,可以对请求和响应进行预处理和后处理,如登录验证、...
读者可能会接触到RequestDispatcher和Forward/Redirect的区别,以及如何使用Filter进行请求和响应的拦截处理。此外,对于初学者来说,这一章可能也会讲解如何组织项目结构,以符合良好的编程习惯和标准。 总之,这...
两者通过RequestDispatcher或Forward、Redirect等方式进行交互。 2. **MVC设计模式**:Model-View-Controller模式是Web开发中的常见架构,其中Model代表业务逻辑,View负责渲染视图,Controller处理请求并协调Model...
- <jsp:forward>和<jsp:redirect>处理页面跳转 - 创建和操作JavaBean - 和设置和获取Bean属性 2. **JSTL(JavaServer Pages Standard Tag Library)** - JSTL简介和优点 - JSTL核心库(fmt, fmt, sql, ...
20. forward 和redirect的区别 13 21. EJB与JAVA BEAN的区别? 13 22. Static Nested Class 和 Inner Class的不同。 13 23. JSP中动态INCLUDE与静态INCLUDE的区别? 14 24. List, Set, Map区别 14 25. 集合类都有...
在Web开发中,"转发"(Forward)和"重定向"(Redirect)是两种常见的页面跳转方式,它们虽然在效果上看似相似,但其实有着本质的区别。理解这两种技术的工作原理及其应用场景对于优化应用程序的性能和用户体验至关...
JSP主要负责视图层的展示,但两者可以通过转发(Forward)或重定向(Redirect)等方式协作。Servlet与JSP的配合使用,实现了MVC(Model-View-Controller)设计模式,有助于提高代码的可维护性和可扩展性。 **3. ...
22、forward 和redirect的区别 forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址...
- 常见的有转发(forward)和重定向(redirect)两种方式。 **4. 在Tomcat中配置数据源** - 通过配置文件`context.xml`设置数据库连接池。 **5. 持久化状态: Cookie, Session** - Cookie用于客户端存储少量数据,...
1. `forward()`与`redirect()`的区别: - `forward()`是服务器内部重定向,浏览器地址栏URL不变,请求参数仍有效,效率较高。 - `redirect()`是客户端重定向,浏览器重新发起请求,地址栏URL改变,请求参数需要...