Spring Web项目

使用Spring MVC搭建了web项目(~/IdeaProjects/Beautiful1205/test-springmvc/springmvctest,有时间上传至github)后,我们来探索一下原理...

一、Spring MVC 与 Spring Boot

Spring MVC提供了一种轻度耦合的方式来开发web应用。它是Spring的一个模块,是一个web框架。通过DispatcherServlet, ModelAndView 和 ViewResolver,开发web应用变得很容易。(实际上还有很多其他的web框架,同样可以跟Spring进行整合。这就要求Spring有一些通用的配置,不但能支持Spring MVC也能支持Structs等其他web框架。这里看下官网吧)

Spring Boot实现了自动配置,降低了项目搭建的复杂度。它主要是为了解决使用Spring框架需要进行大量的配置太麻烦的问题,所以它并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具。同时它集成了大量常用的第三方库配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),Spring Boot应用中这些第三方库几乎可以零配置的开箱即用(out-of-the-box)。 

在SpringBoot项目中整合Spring MVC只需要添加对应的starer依赖即可。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

SpringBoot、SpringMVC和Spring区别_努力的土豆的博客-CSDN博客_springboot springmvc

SpringBoot、SpringMVC整合与比较:_kingmax54212008的博客-CSDN博客

二、Spring MVC原理

http://www.it165.net/pro/html/201604/66385.html

2.1、从Web应用角度看Spring MVC

在Servlet模型中,请求-响应的实现依赖于两大元素的共同配合:

  1. 在web.xml中配置Servlet及其映射关系
  2. 在Servlet实现类中完成响应逻辑

项目规模扩大之后,请求-响应的映射关系全部定义在web.xml中,将造成web.xml的不断膨胀而变得难以维护。针对这个问题,SpringMVC提出的方案就是:提炼一个核心的Servlet覆盖对所有Http请求的处理这一被提炼出来的Servlet,通常被我们称之为:核心分发器。在SpringMVC中,核心分发器就是org.springframework.web.servlet.DispatcherServlet

核心分发器要解决的是下面两个问题:

  • 问题1:核心Servlet应该能够建立起一整套完整的对所有Http请求进行规范化处理的流程。
  • 问题2:核心Servlet应该能够根据一定的规则对不同的Http请求分发到不同的Servlet对象上去进行处理。

针对上面的这个两个问题,SpringMVC的解决方案是:将整个处理流程规范化,并把每一个处理步骤分派到不同的组件中进行处理

  • 处理流程规范化 :将处理流程划分为若干个步骤(任务),并使用一条明确的逻辑主线将所有的步骤串联起来
  • 处理流程组件化 : 将处理流程中的每一个步骤(任务)都定义为接口,并为每个接口赋予不同的实现模式

处理流程规范化的首要内容就是考虑一个通用的Servlet响应程序大致应该包含的逻辑步骤:对Http请求进行初步处理 -> 查找与之对应的Controller处理类(方法) -> 调用相应的Controller处理类(方法) -> 完成业务逻辑 -> 对Controller处理类(方法)调用时可能发生的异常进行处理 -> 根据Controller处理类(方法)的调用结果进行Http响应处理。

所谓的组件化,实际上也就是使用编程语言将这些逻辑语义表达出来。在Java语言中,最适合表达逻辑处理语义的语法结构是接口,而接口可以有不同的实现,因此上述的四个流程也就被定义为了四个不同接口,它们分别是:HandlerMapping、HandlerAdapter、HandlerExceptionResolver、ViewResolver

2.2、从Spring角度看Spring MVC

从上面可以看出,组件是核心分发器(DispatchServlet)的核心所在,它们是http请求处理的逻辑载体,DispatcherServlet是逻辑处理的调度中心,组件则是被调度的操作对象。

Spring容器在这里所起到的作用协助DispatcherServlet更好地对组件进行管理

我们知道,SpringMVC的组件是一个个的接口定义,当我们在SpringMVC的核心配置文件中定义一个组件时,使用的却是组件的实现类。

用具体的实现类来指定组件的行为模式,不同的实现类代表了不同的行为模式,它们在Spring中是可以共存的。

Spring容器对这些实现类进行管理,具体如何使用,由应用程序本身来决定。

上图是Spring官方reference中的一幅图,DispatchServlet对外接收http的请求,而请求的处理是依靠组件来完成的。

组件的接口实现的是依靠Spring IOC容器(WebApplicationContext)来管理的。

从这个图中我们可以看出,Spring MVC实现web应用是依赖于Spring提供的基础特性(IOC等)的。(图中的两个WebApplicationContext的父子关系)

三、上下文初始化过程

3.1、Spring MVC 入口配置文件web.xml

遵循servlet规范,Spring MVC的web应用的入口配置文件也是web.xml。在web.xml配置文件中,有两个主要的配置:ContextLoaderListenerDispatcherServlet。同样的关于spring配置文件的相关配置也有两部分:context-param和DispatcherServlet中的init-param。

在ContextLoaderListener中创建的Spring容器主要用于整个Web应用程序需要共享的一些组件,比如DAO、数据库的ConnectionFactory等;而由DispatcherServlet创建的Spring MVC的容器主要用于和该Servlet相关的一些组件,比如Controller、ViewResovler等。在实际工程中,一个项目中会包括很多配置,根据不同的业务模块来划分,我们一般思路是:Spring根容器负责所有其他非controller的Bean的注册,而SpringMVC只负责controller相关的Bean的注册参考链接

参考链接

Spring Framework本身没有Web功能,Spring MVC使用WebApplicationContext类扩展ApplicationContext,使得拥有web功能。

先看一下WebApplicationContext是如何扩展ApplicationContext来添加对Web环境的支持的。WebApplicationContext接口定义如下:

package org.springframework.web.context;

import javax.servlet.ServletContext;

import org.springframework.context.ApplicationContext;
import org.springframework.lang.Nullable;

/**
 * Interface to provide configuration for a web application. This is read-only while
 * the application is running, but may be reloaded if the implementation supports this.
 *
 * <p>This interface adds a {@code getServletContext()} method to the generic
 * ApplicationContext interface, and defines a well-known application attribute name
 * that the root context must be bound to in the bootstrap process.
 *
 * <p>Like generic application contexts, web application contexts are hierarchical.
 * There is a single root context per application, while each servlet in the application
 * (including a dispatcher servlet in the MVC framework) has its own child context.
 *
 * <p>In addition to standard application context lifecycle capabilities,
 * WebApplicationContext implementations need to detect {@link ServletContextAware}
 * beans and invoke the {@code setServletContext} method accordingly.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @since January 19, 2001
 * @see ServletContextAware#setServletContext
 */
public interface WebApplicationContext extends ApplicationContext {

	/**
	 * Context attribute to bind root WebApplicationContext to on successful startup.
	 * <p>Note: If the startup of the root context fails, this attribute can contain
	 * an exception or error as value. Use WebApplicationContextUtils for convenient
	 * lookup of the root WebApplicationContext.
	 * @see org.springframework.web.context.support.WebApplicationContextUtils#getWebApplicationContext
	 * @see org.springframework.web.context.support.WebApplicationContextUtils#getRequiredWebApplicationContext
	 */
	String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

	/**
	 * Scope identifier for request scope: "request".
	 * Supported in addition to the standard scopes "singleton" and "prototype".
	 */
	String SCOPE_REQUEST = "request";

	/**
	 * Scope identifier for session scope: "session".
	 * Supported in addition to the standard scopes "singleton" and "prototype".
	 */
	String SCOPE_SESSION = "session";

	/**
	 * Scope identifier for the global web application scope: "application".
	 * Supported in addition to the standard scopes "singleton" and "prototype".
	 */
	String SCOPE_APPLICATION = "application";

	/**
	 * Name of the ServletContext environment bean in the factory.
	 * @see javax.servlet.ServletContext
	 */
	String SERVLET_CONTEXT_BEAN_NAME = "servletContext";

	/**
	 * Name of the ServletContext init-params environment bean in the factory.
	 * <p>Note: Possibly merged with ServletConfig parameters.
	 * ServletConfig parameters override ServletContext parameters of the same name.
	 * @see javax.servlet.ServletContext#getInitParameterNames()
	 * @see javax.servlet.ServletContext#getInitParameter(String)
	 * @see javax.servlet.ServletConfig#getInitParameterNames()
	 * @see javax.servlet.ServletConfig#getInitParameter(String)
	 */
	String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";

	/**
	 * Name of the ServletContext attributes environment bean in the factory.
	 * @see javax.servlet.ServletContext#getAttributeNames()
	 * @see javax.servlet.ServletContext#getAttribute(String)
	 */
	String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";


	/**
	 * Return the standard Servlet API ServletContext for this application.
	 */
	@Nullable
	ServletContext getServletContext();

}
* <p>Like generic application contexts, web application contexts are hierarchical.
* There is a single root context per application, while each servlet in the application
* (including a dispatcher servlet in the MVC framework) has its own child context.

注释翻译过来就是:Web环境中的每个应用都有一个根上下文,同时每个servlet还都持有一个子上下文

ContextLoaderListener:spring-web-5.1.10.RELEASE.jar!/org/springframework/web/context/ContextLoaderListener.class

DispatcherServlet

(/Users/-/.m2/repository/org/springframework/boot/spring-boot/2.1.9.RELEASE/spring-boot-2.1.9.RELEASE.jar!/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContext.class prepareWebApplicationContext() 方法)

3.2、Spring容器(根上下文)的初始化

Web环境中的每个应用都有一个根上下文,同时每个servlet还都持有一个子上下文。对于这样的Context结构在Spring MVC中是如何实现的呢?下面就先从ROOT Context入手,ROOT Context是在ContextLoaderListener中配置的,ContextLoaderListener读取context-param中的contextConfigLocation指定的配置文件,创建ROOT Context。下面看一下ContextLoaderListener中创建context的源码:(注意看注释第一句话)

package org.springframework.web.context;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

/**
 * Bootstrap listener to start up and shut down Spring's root {@link WebApplicationContext}.
 * Simply delegates to {@link ContextLoader} as well as to {@link ContextCleanupListener}.
 *
 * <p>As of Spring 3.1, {@code ContextLoaderListener} supports injecting the root web
 * application context via the {@link #ContextLoaderListener(WebApplicationContext)}
 * constructor, allowing for programmatic configuration in Servlet 3.0+ environments.
 * See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
 *
 * @author Juergen Hoeller
 * @author Chris Beams
 * @since 17.02.2003
 * @see #setContextInitializers
 * @see org.springframework.web.WebApplicationInitializer
 */
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	...

	/**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}

    ...

}
package org.springframework.web.context;

import *****;

/**
 * Performs the actual initialization work for the root application context.
 * Called by {@link ContextLoaderListener}.
 *
 * <p>Looks for a {@link #CONTEXT_CLASS_PARAM "contextClass"} parameter at the
 * {@code web.xml} context-param level to specify the context class type, falling
 * back to {@link org.springframework.web.context.support.XmlWebApplicationContext}
 * if not found. With the default ContextLoader implementation, any context class
 * specified needs to implement the {@link ConfigurableWebApplicationContext} interface.
 *
 * <p>Processes a {@link #CONFIG_LOCATION_PARAM "contextConfigLocation"} context-param
 * and passes its value to the context instance, parsing it into potentially multiple
 * file paths which can be separated by any number of commas and spaces, e.g.
 * "WEB-INF/applicationContext1.xml, WEB-INF/applicationContext2.xml".
 * Ant-style path patterns are supported as well, e.g.
 * "WEB-INF/*Context.xml,WEB-INF/spring*.xml" or "WEB-INF/&#42;&#42;/*Context.xml".
 * If not explicitly specified, the context implementation is supposed to use a
 * default location (with XmlWebApplicationContext: "/WEB-INF/applicationContext.xml").
 *
 * <p>Note: In case of multiple config locations, later bean definitions will
 * override ones defined in previously loaded files, at least when using one of
 * Spring's default ApplicationContext implementations. This can be leveraged
 * to deliberately override certain bean definitions via an extra XML file.
 *
 * <p>Above and beyond loading the root application context, this class can optionally
 * load or obtain and hook up a shared parent context to the root application context.
 * See the {@link #loadParentContext(ServletContext)} method for more information.
 *
 * <p>As of Spring 3.1, {@code ContextLoader} supports injecting the root web
 * application context via the {@link #ContextLoader(WebApplicationContext)}
 * constructor, allowing for programmatic configuration in Servlet 3.0+ environments.
 * See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
 *
 * @author Juergen Hoeller
 * @author Colin Sampaleanu
 * @author Sam Brannen
 * @since 17.02.2003
 * @see ContextLoaderListener
 * @see ConfigurableWebApplicationContext
 * @see org.springframework.web.context.support.XmlWebApplicationContext
 */
public class ContextLoader {


}

/spring-web-5.2.5.RELEASE.jar!/org/springframework/web/context/ContextLoader.class类中initWebApplicationContext()方法如下:

	/**
	 * Initialize Spring's web application context for the given servlet context,
	 * using the application context provided at construction time, or creating a new one
	 * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
	 * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
	 * @param servletContext current servlet context
	 * @return the new WebApplicationContext
	 * @see #ContextLoader(WebApplicationContext)
	 * @see #CONTEXT_CLASS_PARAM
	 * @see #CONFIG_LOCATION_PARAM
	 */
	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        //在整个web应用中,只能有一个根上下文
		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException(
					"Cannot initialize context because there is already a root application context present - " +
					"check whether you have multiple ContextLoader* definitions in your web.xml!");
		}

		servletContext.log("Initializing Spring root WebApplicationContext");
		Log logger = LogFactory.getLog(ContextLoader.class);
		if (logger.isInfoEnabled()) {
			logger.info("Root WebApplicationContext: initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			// Store context in local instance variable, to guarantee that it is available on ServletContext shutdown.
			if (this.context == null) {
                // 在这里执行了创建WebApplicationContext的操作
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent ->
						// determine parent for root web application context, if any.
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}

            //将根上下文放置在servletContext中		
	        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}

			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
			}

			return this.context;
		}
		catch (RuntimeException | Error ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
	}

再看一下WebApplicationContext对象是如何创建的:

	/**
	 * Instantiate the root WebApplicationContext for this loader, either the
	 * default context class or a custom context class if specified.
	 * <p>This implementation expects custom contexts to implement the
	 * {@link ConfigurableWebApplicationContext} interface.
	 * Can be overridden in subclasses.
	 * <p>In addition, {@link #customizeContext} gets called prior to refreshing the
	 * context, allowing subclasses to perform custom modifications to the context.
	 * @param sc current servlet context
	 * @return the root WebApplicationContext
	 * @see ConfigurableWebApplicationContext
	 */
	protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
		Class<?> contextClass = determineContextClass(sc);
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

以上是web容器中根上下文的加载与初始化,下面介绍一下Spring MVC对应的上下文是如何加载的。      

3.3、Spring MVC容器(子上下文)的初始化

以上是web容器中根上下文的加载与初始化,在完成对ContextLoaderListener的初始化以后,web容器开始初始化DispatchServlet,DispatchServlet会建立自己的上下文来管理Spring MVC的bean对象。在建立这个自己持有的上下文的时候,会从ServletContext中得到根上下文作为DispatchServlet持有的上下文的双亲上下文,再对自己持有的上下文进行初始化,最后把自己持有的这个上下文也保存到ServletContext中。

我们先看下DispatchServlet的继承关系,如下图,

DispatchServlet通过继承FrameworkServlet和HttpServletBean而继承了HttpServlet。HttpServletBean是Spring对于Servlet最低层次的抽象。

在这一层抽象中,Spring会将这个Servlet视作是一个Spring的bean,并将web入口配置文件web.xml中DispatchServlet定义的init-param参数中的值作为bean的属性注入进来。

DispatcherServlet也是一个Servlet,根据Servlet规范的定义,其中有两大核心方法:init()方法service()方法。

  • init()方法:在整个系统启动时运行,且只运行一次。主要的调用顺序是GenericServlet#init() --> HttpServletBean#init() -->FrameworkServlet#initServletBean() --> FrameworkServlet#initWebApplicationContext()。在init方法中我们往往会对整个应用程序进行初始化操作。这些初始化操作包括对容器(WebApplicationContext)的初始化、组件和外部资源的初始化等等。
  • service()方法:在整个系统运行的过程中处于侦听模式,侦听并处理所有的Web请求。因此,在service及其相关方法中,我们看到的则是对Http请求的处理流程。

DispatchServlet、FrameworkServlet和HttpServletBean之间的调用关系如下,首先是HttpServletBean,其次是FrameworkServlet,最后是DispatchServlet。FrameworkServlet是在HttpServletBean的基础之上的进一步抽象。通过FrameworkServlet真正初始化了一个Spring的容器(WebApplicationContext),并引入到ServletContext之中(注意这个子上下文放到ServletContext时的key和根上下文是不一样的)。在这个调用关系中,可以看到MVC的初始化是再DispatchServlet的initStrategies方法中完成的,包括对各种MVC框架的实现元素,比如支持国际化的LocaleResolver、支持request映射的HandlerMappings、以及视图生成的ViewResolver等的初始化。

看下FrameworkServlet#initServletBean()的实现,

	/**
	 * Overridden method of {@link HttpServletBean}, invoked after any bean properties
	 * have been set. Creates this servlet's WebApplicationContext.
	 */
	@Override
	protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
		if (logger.isInfoEnabled()) {
			logger.info("Initializing Servlet '" + getServletName() + "'");
		}
		long startTime = System.currentTimeMillis();

		try {
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (logger.isDebugEnabled()) {
			String value = this.enableLoggingRequestDetails ?
					"shown which may lead to unsafe logging of potentially sensitive data" :
					"masked to prevent unsafe logging of potentially sensitive data";
			logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
					"': request parameters and headers will be " + value);
		}

		if (logger.isInfoEnabled()) {
			logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
		}
	}

其中,FrameworkServlet#initWebApplicationContext的实现

	/**
	 * Initialize and publish the WebApplicationContext for this servlet.
	 * <p>Delegates to {@link #createWebApplicationContext} for actual creation
	 * of the context. Can be overridden in subclasses.
	 * @return the WebApplicationContext instance
	 * @see #FrameworkServlet(WebApplicationContext)
	 * @see #setContextClass
	 * @see #setContextConfigLocation
	 */
	protected WebApplicationContext initWebApplicationContext() {
        //从ServletContext中取得根上下文
		WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
            //如果在构造时已经注入了context则直接使用
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent -> set
						// the root application context (if any; may be null) as the parent
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// No context instance was injected at construction time -> see if one has been registered in the servlet context. 
            // If one exists, it is assumed that the parent context (if any) has already been set 
            // and that the user has performed any initialization such as setting the context id
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			//创建Spring MVC的上下文,并将根上下文作为起双亲上下文
            wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			synchronized (this.onRefreshMonitor) {
                //会最终DispatchServlet的initStrategies()方法, 进行MVC的初始化
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
            // 名字前缀是	public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

通过initWebApplicationContext方法的调用,创建了DispatcherServlet对应的context,并将其放置到ServletContext中,这样就完成了在web容器中构建Spring MVC容器的过程。

四、Spring MVC 上下初始化流程图

ServletContext是Spring容器的宿主环境,Spring容器又包含整个应用的根上下文和每个servlet的子上下文。

用到的组件:(https://blog.csdn.net/lu930124/article/details/51586031)

组件作用
前端控制器接收请求 响应结果  相当于转发器
处理器映射器根据请求的url查找handler
处理器适配器按照特定规则去执行handler 注意:在开发handler时按照handleradapter的要求去做,这样适配器才可以去执行handler
视图解析器进行视图解析,根据逻辑视图名解析成真正的视图
视图是一个接口,实现类支持不同的view类型(jsp freemarker, pdf……)
处理器handler 编写handler时按照handleradapter的要求去做,这样适配器才可以去正确执行handler


    

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值