Spring框架浅析 -- SpringMVC

Spring MVC

使用过Spring用于建设Web服务工程的,都不可避免地要接触Spring MVC。

那么MVC究竟是什么?MVC是Model,View和Control的简称,用于标识Web工程中功能划分明晰、各自承担各自职责的模型层、视图层和控制层。如下图所示:

通常情况下,用户向Web服务发送请求,由Control层负责对请求进行校验、解析、路由,转发到指定的控制逻辑,然后控制逻辑通过Model层,从DB、FS或者其他子系统中获取数据,返回给控制层。最后,控制层将数据传至View层,最终由View层完成结果的渲染与展现,呈现给用户。

MVC的优点就在于各层划分清楚明晰、各司其职,能够在各自层内结合自身定位,深耕功能。比如Control层就专注于请求的路由与处理,偏重于控制操作,不太需要考虑数据的具体展现形式;Model层专注于对底层数据的抽象;View层就专注于对最终相应结果数据的渲染与展现。因此,增加一种新的结果展现方式,比如从JSON View变成XX View,完全不需要对Control层和Model层进行变更,仅需要在View层新增一种XX View解析渲染组件即可。

常见的MVC框架主要有Struts,Spring等。我们在本文中只关注Spring MVC框架。

Spring MVC配置

先来看一个简单的Spring MVC配置。

首先需要在web.xml中对DispatcherServlet进行设置

  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring/spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

从上述配置中不难看出,DispatcherServlet将处理该web应用下的所有URL请求,即<url-pattern>/</url-pattern>这一处配置。如果这不是你的初衷,可以再此进行调整。

接下来,需要在配置文件spring-mvc.xml中进行配置,其中的配置项的具体解释,可见本文后续章节中的介绍。配置如下所示:

    <mvc:annotation-driven/>

    <!-- 容器默认的DefaultServletHandler处理 所有静态内容与无RequestMapping处理的URL -->
    <mvc:default-servlet-handler/>

    <mvc:resources mapping="/css/**" location="/css/"/>
    <mvc:resources mapping="/img/**" location="/img/"/>
    <mvc:resources mapping="/js/**" location="/js/"/>

    <context:component-scan base-package="com.xxx" use-default-filters="false">
        <context:include-filter type="annotation"
                                expression="org.springframework.stereotype.Controller"/>
        <context:include-filter type="annotation"
                                expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

Spring MVC原理浅析

对于使用Spring MVC的Web开发者而言,如果对Spring MVC的原理不了解的话,确实有点说不过去。此外,很多开发者平时遇到的坑,其实本质上都是因为对Spring MVC原理理解有误导致的,比如同一种Bean注册了两遍(Spring容器一遍,Spring MVC容器一遍)、Spring MVC中若干注解不生效等。本章节将简单介绍一下Spring MVC的原理。

Servlet简介

提到Spring MVC和Web开发,最为绕不过去的概念就是Servlet了。Servlet是什么?为什么它如此重要?它究竟解决了什么问题?生命周期是怎样的?详见wiki:https://zh.wikipedia.org/zh-hans/Java_Servlet

Servlet其实本质上就是一种部署在Web服务器上的JAVA服务端程序。对于外部发起的请求,Servlet根据服务端逻辑,获取相应数据,并进行加工,将加工完毕的数据返回给外部。

Servlet的生命周期主要有下述几个阶段:

  1. 初始化阶段
  2. 运行阶段
  3. 销毁阶段

Servlet的初始化阶段并不是在Web容器启动的时候发起的,而是在外部第一次发起访问的时候,才进行的初始化。这样的设计其实优缺点参半。优点是Web容器的启动速度不受到Servlet初始化速度的制约,相对独立。缺点是存在“首次惩罚”的问题,即第一次请求时响应时间过长,甚至有可能会超时。

个人认为,这种设计其实可以加以改进,即在Web容器中指定哪些Servlet可以在容器启动的时候进行初始化(也可能已有此种工程,小伙伴们可以给我留言)。这样可以在容器启动时,将重要的Servlet初始化,解决“首次惩罚”的问题。

Spring MVC中也用到了Servlet,也就是上文中,在配置中提到的DispatcherServlet,注意其仅在Web容器中初始化一次,因此在使用时要注意其线程安全性。

Spring MVC容器的启动过程

Spring MVC其实也是一个容器,与 Spring框架浅析 -- IoC容器与Bean的生命周期中提到的IoC容器相同,对它的了解也要从其启动过程着手加以了解。其启动的入口在DispatcherServlet的init方法中。

DispatcherServlet及其父类的关系图如下所示:

上节中我们提到,当外界第一次发起访问时,会调用Servlet的init方法,我们看到DispatcherServlet的init方法其实是在其父类:HttpServletBean中。

	@Override
	public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		// Set bean properties from init parameters.
		try {
			PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			throw ex;
		}

		// Let subclasses do whatever initialization they like.
		// 关注这个方法
		initServletBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}

关注其中的initServletBean方法,其实是在调用其子类FrameworkServlet。

@Override
	protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
		if (this.logger.isInfoEnabled()) {
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			// 关注这个方法
			this.webApplicationContext = initWebApplicationContext();
			// 提供给子类额外的处理机会
			initFrameworkServlet();
		}
		catch (ServletException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}
		catch (RuntimeException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (this.logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
					elapsedTime + " ms");
		}
	}

关注其中的initWebApplicationContext方法。

	protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			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
			// 关注此方法,与IoC容器启动时创建容器,刷新容器,读取配置文件,将Bean注册至BeanFactory中一致
			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.
			onRefresh(wac);
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}

		return wac;
	}

沿着createWebApplicationContext走下去,你会发现,其实跟创建IoC容器时极为类似。只不过此时的contextConfigLocation为上文中配置DispatcherServlet时设置的location了,按照上文中的例子来看,对应的配置文件为classpath:spring/spring-mvc.xml。Spring MVC容器会根据配置文件中的配置,识别Bean,将Bean注入到BeanFactory中,调用BeanFactoryPostProcessor,BeanPostProcessor等在Bean的生命周期中起作用的容器级别的组件,完成Bean的实例化、初始化和加工工作。此处就不再赘述了,我们只关注其中比较重要的几个组件,在下文中一一进行介绍。

component-scan的设置

从上文中例子中的spring-mvc.xml中的配置,我们看到有这样一段:

    <context:component-scan base-package="com.xxx" use-default-filters="false">
        <context:include-filter type="annotation"
                                expression="org.springframework.stereotype.Controller"/>
        <context:include-filter type="annotation"
                                expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>

而通常在IoC容器的配置中,在扫描包的时候,通常会排除掉Controller。这是为什么呢?

原因其实就在Spring MVC的原理中。

我们从上文中知道,Spring MVC和Spring IoC容器是两个容器,前者是后者的子容器。只有在Spring MVC容器中,配置了Controller的Bean才能体现出其作为处理器的效能。

如果在父容器的配置文件中,不排除掉Controller,则会在父容器中创建并注入标注有Controller的Bean,但此Bean并不会针对外界发起的请求进行操作。

反之,如果在子容器的配置文件中,不仅扫描@Controller,还把@Service和@Repository也扫描进来,会导致事务失效、注解失效(因为通常不会在Spring容器中配置<aop>、<tx>,会导致被@Service和@Repository注解的Bean没有经过代理增强)。

所以,通常的配置是,在Spring容器中配置扫描@Service和@Repository,并进行aop、tx设置,排除@Controller;在Spring MVC容器中,只扫描@Controller。这样,由于Spring MVC容器是Spring容器的子容器,且负责将被@Controller标注的Bean发挥功效,管理RequestMapping;当需要调用@Service和@Repository的时候,由于Spring MVC容器中没有注入,在其父容器中注入了这些Bean,因此调用其父容器的Bean,这些Bean在父容器中被Proxy进行了增强,因此注入了事务管理、AOP增强功能。至于为什么子容器能获取父容器中的Bean,而父容器不能获取子容器的Bean,需要参考Spring容器启动过程中,AbstractBeanFactory中的doGetBean方法中的逻辑。具体逻辑如下:

// Check if bean definition exists in this factory.
			BeanFactory parentBeanFactory = getParentBeanFactory();
			// 如果存在父容器,且在子容器中并没有找到对应beanName的Bean
			if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
				// Not found -> check parent.
				String nameToLookup = originalBeanName(name);
				if (args != null) {
					// Delegation to parent with explicit args.
					// 从父容器中按beanName获取Bean
					return (T) parentBeanFactory.getBean(nameToLookup, args);
				}
				else {
					// No args -> delegate to standard getBean method.
					return parentBeanFactory.getBean(nameToLookup, requiredType);
				}
			}

至于为什么这么设计,其实也是合理的,因为这样处理的话,父容器中可以注入公用的Bean(比如第三方工具Bean),由父容器负责将其加载,各子容器就可以复用了。如果某个子容器觉得不能满足它的业务需求,可以自己写一个,然后由它自己加载,自己维护,对自身提供服务,同时无需影响到其他兄弟容器。这种思路与JVM双亲委派模型不同,但JVM双亲委派模型主要是考虑到安全性,毕竟不想因为应用方的JAVA类覆盖了父类,导致JVM执行过程中crash。

Url-处理器映射

上文中介绍了Spring MVC容器的启动过程和包扫描配置,在容器启动过程中,注入了相应的Bean,处理了相应的自定义命名空间(本质上是注入了对应的BeanDefinitionParser),然后通过其中重要的AnnotationDrivenBeanDefinitionParser及其执行逻辑中注入的若干组件,生成了Url-处理器映射关系。这样,在后续DispatcherServlet处理外部请求的时候,就可以根据请求Url获取到指定的Controller的响应方法,进行相应的操作完成响应对象的生成,最后交由视图解析器渲染成响应结果呈现给外部,完成整个请求-响应过程。本节主要讲解Url-处理器映射关系是如何生成的,下一节将着重讲解DispatcherServlet的响应过程。

我们回到spring-mvc.xml中的配置,注意这一句:

 <mvc:annotation-driven/>

与其他自定义命名空间一样,其对应的NameSpaceHandler可在对应的spring-webmvc.jar包的META-INF目录下找到,对应的文件名为spring.handlers。

其hanlders为MvcNamespaceHandler,其中annotation-driven属性对应的BeanDefinitionParser是AnnotationDrivenBeanDefinitionParser。在其parse方法中可以找到,其注册了重要的RequestMappingHandlerMapping。找到其父类AbstractHandlerMethodMapping,可以看出它实现了InitializingBean接口,即会在Bean初始化的过程中,调用afterPropertiesSet方法。

该方法中调用了initHandlerMethods方法,这个方法中填充了Url-处理器映射。

protected void initHandlerMethods() {
		if (logger.isDebugEnabled()) {
			logger.debug("Looking for request mappings in application context: " + getApplicationContext());
		}
		String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
				getApplicationContext().getBeanNamesForType(Object.class));

		for (String beanName : beanNames) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				Class<?> beanType = null;
				try {
					beanType = getApplicationContext().getType(beanName);
				}
				catch (Throwable ex) {
					// An unresolvable bean type, probably from a lazy bean - let's ignore it.
					if (logger.isDebugEnabled()) {
						logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
					}
				}
				// 只有被isHandler方法判定为true的,才会去尝试处理Url-处理器映射
				if (beanType != null && isHandler(beanType)) {
					detectHandlerMethods(beanName);
				}
			}
		}
		handlerMethodsInitialized(getHandlerMethods());
	}

关注其中的isHanlder方法:

@Override
	protected boolean isHandler(Class<?> beanType) {
		return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
				AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
	}

很清楚地看到,只会考虑标注有@Controller的或者是RequestMapping的,才会尝试填充Url-处理器映射关系。

这也就解释了为什么在Spring容器的配置文件中(通常不配置<mvc:annotation-driven/>,因为这本身就不是Spring容器配置文件中应该配置的东西),即使设置了扫描@Controller,也不会建立Url-处理器映射关系,从而导致Url访问404。

再看detectHandlerMethods方法,考虑它是如何填充的Url-处理器映射关系。方法比较复杂,我们分成三个阶段来看:

  1. 获取Bean中的方法
  2. 获取方法上的标注属性
  3. 将标注有Controller的Bean-方法-方法上标注属性中的值,关联起来,形成Url-处理器映射。

下边我们一一看一下代码,看这三步是如何完成的。

	protected void detectHandlerMethods(final Object handler) {
		Class<?> handlerType = (handler instanceof String ?
				getApplicationContext().getType((String) handler) : handler.getClass());
		final Class<?> userType = ClassUtils.getUserClass(handlerType);

		// 获取方法上的Annotation,建立Key为方法签名,Value为Annotation中配置的属性这样的Map,层层调用略复杂,可深入进去看
		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				new MethodIntrospector.MetadataLookup<T>() {
					@Override
					public T inspect(Method method) {
						try {
							// 获取方法上的Annotation的属性
							return getMappingForMethod(method, userType);
						}
						catch (Throwable ex) {
							throw new IllegalStateException("Invalid mapping on handler class [" +
									userType.getName() + "]: " + method, ex);
						}
					}
				});

		if (logger.isDebugEnabled()) {
			logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
		}
		for (Map.Entry<Method, T> entry : methods.entrySet()) {
			Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
			T mapping = entry.getValue();
			// 将Controller类-方法-方法上标注的Annotation的属性,三者关联起来,建设Url-处理器的映射关系
			registerHandlerMethod(handler, invocableMethod, mapping);
		}
	}

先看一下比较复杂这一段:

// 获取方法上的Annotation,建立Key为方法签名,Value为Annotation中配置的属性这样的Map,层层调用略复杂,可深入进去看
		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				new MethodIntrospector.MetadataLookup<T>() {
					@Override
					public T inspect(Method method) {
						try {
							// 获取方法上的Annotation的属性
							return getMappingForMethod(method, userType);
						}
						catch (Throwable ex) {
							throw new IllegalStateException("Invalid mapping on handler class [" +
									userType.getName() + "]: " + method, ex);
						}
					}
				});

关注其中的MethodIntrospector.selectMethods方法,进入之后,关注内部的ReflectionUtils.doWithMethods方法。

	public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf) {
		// Keep backing up the inheritance hierarchy.
		// 获取类中的方法
		Method[] methods = getDeclaredMethods(clazz);
		// 遍历所有的方法
		for (Method method : methods) {
			if (mf != null && !mf.matches(method)) {
				continue;
			}
			try {
				// mc为上层传入的,关注调用的时候,传入的MethodCallback,其中实现了doWith方法
				mc.doWith(method);
			}
			catch (IllegalAccessException ex) {
				throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
			}
		}
		if (clazz.getSuperclass() != null) {
			doWithMethods(clazz.getSuperclass(), mc, mf);
		}
		else if (clazz.isInterface()) {
			for (Class<?> superIfc : clazz.getInterfaces()) {
				doWithMethods(superIfc, mc, mf);
			}
		}
	}

关注传入的MethodCallback实现的doWith方法

new ReflectionUtils.MethodCallback() {
				@Override
				public void doWith(Method method) {
					// 获取方法
					Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
					// metadataLookup为传入的,关注传入时的metadataLookup的inspect方法
					// T为method上标注的属性
					T result = metadataLookup.inspect(specificMethod);
					if (result != null) {
						Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
						if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
							// 填充方法-方法标注属性(例如urlPath,GET or POST)
							methodMap.put(specificMethod, result);
						}
					}
				}
			}

关注其上层传入的metadataLookup

	Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				new MethodIntrospector.MetadataLookup<T>() {
					@Override
					public T inspect(Method method) {
						try {
							//  关注此方法,实际上是获取指定method上标注的属性
							return getMappingForMethod(method, userType);
						}
						catch (Throwable ex) {
							throw new IllegalStateException("Invalid mapping on handler class [" +
									userType.getName() + "]: " + method, ex);
						}
					}
				});

关注getMappingForMethod方法,该方法实际上是在AbstractHandlerMethodMapping的子类RequestMappingHandlerMapping中。

	@Override
	protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
		// 关注此方法,首先获取method上的Annotation属性
		RequestMappingInfo info = createRequestMappingInfo(method);
		if (info != null) {
			// 获取Bean上的Annotation属性
			RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
			if (typeInfo != null) {
				// 二者做merge
				info = typeInfo.combine(info);
			}
		}
		return info;
	}

关注createRequestMappingInfo方法,

	private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
		RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
		RequestCondition<?> condition = (element instanceof Class ?
				getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
		return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
	}

从中非常明确的看到是在获取RequestMapping标注的属性,这其中的属性通常有value和method。

其中value通常标注的是UrlPath,method是在定义是HTTP GET还是POST。

我们回到

Map<Method, T> methods = MethodIntrospector.selectMethods

结合刚才层层向下的分析结果,能够明确地知道,返回值为key为Method签名,value为方法上Annotation属性的map。从而完成了上述所说的1.获取Bean中的方法和2.获取方法上的标注属性。

接下来我们分析一下如何实现第三步-“将标注有Controller的Bean-方法-方法上标注属性中的值,关联起来,形成Url-处理器映射”。关注registerHandlerMethod方法。

public void register(T mapping, Object handler, Method method) {
			this.readWriteLock.writeLock().lock();
			try {
				// 创建HandlerMethod数据结果,包含了处理器Bean和处理方法
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
				// 校验HandlerMethod唯一性,即Bean+Method不能重复
				assertUniqueMethodMapping(handlerMethod, mapping);

				if (logger.isInfoEnabled()) {
					logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
				}
				// Key为Annotation属性(Url等),Value为处理器+处理方法
				this.mappingLookup.put(mapping, handlerMethod);

				// 从Annotation属性中获取响应Url
				List<String> directUrls = getDirectUrls(mapping);
				for (String url : directUrls) {
					// Url-Annotation属性的关联关系
					this.urlLookup.add(url, mapping);
				}

				String name = null;
				if (getNamingStrategy() != null) {
					name = getNamingStrategy().getName(handlerMethod, mapping);
					addMappingName(name, handlerMethod);
				}

				CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
				if (corsConfig != null) {
					this.corsLookup.put(handlerMethod, corsConfig);
				}

				this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
			}
			finally {
				this.readWriteLock.writeLock().unlock();
			}
		}

至此,完成了整个Url-处理器关联关系的填充,做好了响应Http请求的准备。后续即可以在访问请求到来的时候,从映射关系中找到处理器和处理方法,进行路由,然后执行相应的逻辑。也就是下一节中要讲解的内容。

DispatcherServletservice过程

上一小节中阐述了如何去填充Url-处理器关联关系,本节讲一下当请求到来的时候,如何根据上节中填充的关联关系,找到指定的处理器和处理方法,执行相应的逻辑。

首先回顾一下DispatcherServlet的类图,其继承了父类HttpServlet,因此它在响应外来请求的时候,调用的是其父类的service方法。而父类HttpServlet的service方法中,实际上是根据不同的HTTP请求类型,进行了相应的处理,比如对于HTTP GET,使用的是doGet;对于HTTP POST,使用的是doPost。我们以doGet为例(其他响应方法执行逻辑均与此类似),HttpServlet的子类FrameworkServlet的doGet方法覆盖了父类的同名方法,在这个方法中,执行了processRequest方法。在processRequest方法中,调用了FrameworkServlet的子类DispatcherServlet的doService方法,并最终调用了doDispatch方法。

下边我们来看一下这个doDispatch方法。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				// 根据输入的request,主要是根据url获取对应的Handler执行链(所谓链是因为有可能有多个HandlerMapping)
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				// 获取hanlder适配器,由适配器找到对应的Controller的相应方法
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				// 调用Controller的相应方法,获取结果
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

本质上是根据url获取到对应的Controller的对应方法,然后调用这个方法,获取最终的结果。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值