spring源码初步学习-SpringMVC

第四部分 SpringMVC的实现

Spring的MVC是基于Servlet功能实现的,通过实现Servlet接口的DispatcherServlet来封装其核心功能实现。

一 ContextLoaderListener:

ContextLoaderListener的作用就是启动web容器时,自动装配ApplicationContext的配置信息。

它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法(系统启动时会调用ServletContextListener实现类的contextInitialized方法)。

使用ServletContextListener接口,开发者能够在为客户端请求提供服务之前向ServletContext中添加任意对象。这个对象在ServletContext启动的时候被初始化,然后再ServletContext整个运行期间都是可见的。

每一个web应用都有一个servletContext与之相关联,ServletContext对象在应用启动时被创建,在应用关闭时被销毁。ServletContext在全局范围内有效,类似于应用中的一个全局变量。

在ContextLoaderListener中的核心逻辑便是初始化WebApplicationContext实例并存放只ServletContext中。

1 ContextLoaderListener中的contextInitialized方法

	public void contextInitialized(ServletContextEvent event) {
		this.contextLoader = createContextLoader();
		if (this.contextLoader == null) {
			this.contextLoader = this;
		}
		this.contextLoader.initWebApplicationContext(event.getServletContext());
	}

WebApplicationContext继承自ApplicationContext,在ApplicationContext的基础上又追加了一些特定于Web的操作及属性。

initWebApplicationContext函数主要是体现了创建WebApplicationContext实例的一个功能架构。

1)在初始化过程中,程序会首先读取ContextLoader类同目录下的配置文件,提前将要实现WebApplicationContext接口的实现类

Class<?> contextClass = determineContextClass(sc);

2)根据这个实现类通过反射的方式进行实例的创建

return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

3)将实例记录在servletContext中

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

4)映射当前的类加载器与创建的实例到全局变量currentContextPerThread中

currentContextPerThread.put(ccl, this.context);

二 DispatcherServlet:

在Spring中,ContextLoaderListener只是辅助功能,用于创建WebApplicationContext类型实例,而真正的逻辑实现其实是在DispatcherServlet中进行的,DispatcherServlet是实现Servlet接口的实现类。

1 DispatcherServlet的初始化

	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");
		}
	}

DispatcherServlet的初始化过程主要是通过将当前的Servlet类型实例转换为BeanWapper类型实例,以便使用Spring中提供的注入功能进行对应属性的注入。

这些属性如contextAttribute、contextConfiguration等,都可以在web.xml文件中以初始化参数的方式配置在Servlet的声明中。

DispatcherServlet继承自FrameworkServlet,FrameworkServlet类上包含对应的同名属性,Spring会保证这些参数被注入到对应的值中。

属性注入包含以下步骤:

1)封装及验证初始化参数:PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);

封装属性主要是对初始化的参数进行封装,也就是servlet中配置的<init-param>中配置的封装。

2)将当前servlet实例转换成BeanWrapper实例:BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);

3)注册相对于Resource的属性编辑器:bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));

4)属性注入:bw.setPropertyValues(pvs, true);

BeanWrapper支持Spring的自动注入,最常用的属性注入无非是contextAttribure、contextClass、nameSpace、contextConfigLocation

5)servletBean的初始化:initServletBean();

在ContextLoaderListener加载的时候已经创建了WebApplicationContext实例,在这个函数中最重要的就是对这个实例进行进一步的补充初始化。

该函数设计了计时器来统计初始化的执行时间,而作为关键的初始化逻辑实现委托给了initWebApplicationContext();

二 WebApplicationContext的初始化:initWebApplicationContext()

initWebApplicationContext()函数的主要工作就是创建或刷新WebApplicationContext实例,并对servlet功能所使用的变量进行初始化。

本函数主要包含以下部分:

1 寻找或创建对应的WebApplicationContext实例

1)如果this.webApplicationContext != null,则可以判定this.webApplicationContext 已经通过构造函数初始化了

2)通过contextAttribute进行初始化:wac = findWebApplicationContext();

在ContextLoaderListener加载时会创建WebApplicationContext实例,并将实例以WebApplicationContext.class.getName()+”.ROOT”为Key放入ServletContext中

3)重新创建WebApplicationContext

如果以上两种方式并没有找到任何突破,那么只能在这里重新创建新的实例了。

a.获取servlet的初始化参数contextClass,如果没有配置默认为XmlWebApplicationContext.class:Class<?> contextClass = getContextClass();

b.通过反射方式实例化contextClass:ConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

c.获取contextConfigLocation属性,配置在servlet初始化参数中:wac.setConfigLocation(getContextConfigLocation());

d.初始化Spring环境,包括加载配置文件等:configureAndRefreshWebApplicationContext(wac);

2 configureAndRefreshWebApplicationContext()

无论是通过构造函数注入还是单独创建,都免不了调用configureAndRefreshWebApplicationContext(wac)方法来对已经创建的WebApplicationContext实例进行配置及刷新。

其实只要使用ApplicationContext所提供的功能,最后还是使用其公共父类AbstractApplicationContext提供的refresh()方法进行配置文件加载

3 刷新:onRefresh(wac);

onRefresh是FrameworkServlet类中提供的模板方法,在其子类DispatcherServlet中进行了重写。主要用于刷新Spring在Web功能实现中所必须使用的全局变量

	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

	/**
	 * Initialize the strategy objects that this servlet uses.
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 */
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

1)初始化MultipartResolver:initMultipartResolver(context);

在Spring中,MultipartResolver主要用来处理文件上传。如果想使用,则需要在web应用的上下文中添加multipart解析器

2)初始化LocaleResolver:initLocaleResolver(context);

Spring中的国际化配置解析器

3)初始化ThemeResolver:initThemeResolver(context);

Spring中的主体解析器。在web开发中经常会遇到通过主题Theme来控制网页风格,这将进一步改善用户体验

4)初始化HandlerMappings:initHandlerMappings(context);

当客户端发出Request时DispatcherServl会将Request提交给HandlerMapping,然后HandlerMapping根据WebApplicationContext的配置来回传给DispatcherServlet相应的Controller。

在基于SpringMVC的Web应用程序中,我们可以为DispatcherServlet提供多个HandlerMapping供其使用。DispatcherServlet在选用HandlerMapping的过程中,将根据我们所指定的一系列HandlerMapping的优先级进行排序,然后有限使用优先级在前的HandlerMapping。

如果当前的HandlerMapping能够返回可用 的Handler,DispatcherServlet则使用当前返回的Handler进行Web请求的处理,而不再继续询问其他的HandlerMapping。否则,DispatcherServlet将继续按照各个HandlerMapping的优先级进行询问,直到获取一个可用的Handler为止。

默认情况下,SpringMVC将加载当前系统中所有实现了HandlerMapping接口的bean。

5)初始化HandlerAdapters:initHandlerAdapters(context);

这是一个典型的适配器模式的使用。使用适配器,可以使接口不兼容而无法在一起工作的类协同工作。

在getDefaultStrategies函数中,Spring尝试从defaultStrategies中加载对应的HandlerAdapter属性。

在系统加载的时候,defaultStrategies会根据当前路径DispatcherServlet.properties来初始化本身

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

由此得知,如果程序开发人员没有在配置文件中定义自己的适配器,那么Spring会默认加载配置文件中的3个适配器。

作为总控制器的派遣器servlet(DispatcherServlet)通过处理器映射(HandlerMapping)得到处理器(Handler)后,会轮训处理器适配器(HandlerAdapter)模块,查找能够处理当前HTTP请求的处理器适配器的实现。

处理器适配器模块根据处理器映射(HandlerMapping) 返回的处理器类型(例如简单的控制器类型、注解控制器类型等),来选择某一个适当的处理器适配器的实现,从而适配当前的HTTP请求。

6)初始化HandlerExceptionResolvers:initHandlerExceptionResolvers(context);

基于HandlerExceptionResolver接口的异常处理,使用这种方式只需要实现resolveException方法。

换句话说,Spring会搜索所有注册在其环境中的实现了HandlerExceptionResolver接口的bean,逐个执行,直到返回了一个ModelAndWiew对象。

7)初始化tRequestToViewNameTranslator:initRequestToViewNameTranslator(context);

当Controller处理器方法没有返回一个View对象或者逻辑视图名称,并且在该方法中没有直接往response的输出流里面写数据的时候,Spring就会采用约定好的方式提供一个逻辑视图名称。

这个逻辑视图名称是通过Spring定义的RequestToViewNameTranslator接口的getViewName方法实现的。

当我们没有在SpringMVC的配置文件中手动的定义一个名为viewNameTranlator的Bean的时候,Spring就会为我们提供一个默认的viewNameTranlator,即DefaultRequestToViewNameTranslator。

8)初始化ViewResolvers:initViewResolvers(context);

当Controller将请求处理结果放入到ModelAndView中以后,DispatcherServlet会根据ModelAndView选择合适的视图进行渲染。

ViewResolver接口定义了resolverViewName方法,根据viewName创建合适类型的View实现


三 DispatcherServlet的逻辑处理

HttpServlet类中提供了不同的服务方法,它会根据请求的不同形式将程序引导至对应的函数进行处理。

对于不同的方法,Spring并没有做特殊处理,而是统一将程序再一次引导至processRequest(request,response)中。

1 processRequest(HttpServletRequest request, HttpServletResponse response)所做的事情:

函数中已经开始了对请求的处理,虽然吧细节转移到了doService(request, response)函数中实现,但不难看出处理请求前后所做的准备工作。

1)为了保证当前线程的LocaleContext和RequestAttributes可以在当前请求后还能恢复,提取当前线程的两个属性:

LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();

RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();

2)根据当前request创建对应的LocaleContext和RequestAttributes,并绑定到当前线程:

LocaleContext localeContext = buildLocaleContext(request);

ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

initContextHolders(request, localeContext, requestAttributes);

3)委托给doService方法进一步处理:

doService(request, response);

4)请求处理结束后恢复线程到原始状态:

resetContextHolders(request, previousLocaleContext, previousAttributes);

5)请求处理结束后,无论成功与否,发布事件通知:

publishRequestHandledEvent(request, startTime, failureCause);

2 doService(HttpServletRequest request, HttpServletResponse response)方法:

在doService中并没有看到 诸如寻找Handler并进行页面跳转之类的处理逻辑,想法却同样是一些准备工作,但这些准备工作却是必不可少的。

Spring将已经初始化的功能辅助工具变量,比如localeResolver、themeResolver等设置在request属性中,而这些属性会在接下来的处理中派上用场。

将处理的核心逻辑委托给了doDispatch(HttpServletRequest request, HttpServletResponse response)方法。

3 doDispatch(HttpServletRequest request, HttpServletResponse response)方法:

doDispatch函数中展示了Spring请求处理所涉及的主要逻辑,而我们之前设置在request中的各种辅助属性也都有被派上了用场。

1)MultipartContent类型的request处理:processedRequest = checkMultipart(request)

对于请求的处理,Spring首先考虑的是对于Multipart的处理,如果是MultipartContent类型的request,则转换request为MultipartHttpServletRequest类型的request。

2)根据request信息寻找对应的Handler:mappedHandler = getHandler(processedRequest, false)

在Spring加载的过程中,Spring会将类型为SimpleUrlHandlerMapping的实例加载到this.handlerMappings中。

HandlerExecutionChain getHandler(HttpServletRequest request)的目的就是遍历所有的HandlerMapping,并调用其getHandler方法进行封装处理。

AbstractHandlerMapping中的getHandler方法:

	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		Object handler = getHandlerInternal(request);
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = getApplicationContext().getBean(handlerName);
		}
		return getHandlerExecutionChain(handler, request);
	}

a.函数首先会使用getHandlerInternal(request)方法根据request信息获取对应的Handler。

(以SimpleUrlHandlerMapping为例,此步骤就是根据URL找到匹配的Controller并返回)

(以AbstractUrlHandlerMapping为例,在getHandlerInternal中的buildPathExposingHandler函数,将Handler封装成了HandlerExecutionChain。

函数首先构建HandlerExecutionChain类型的实例,然后加入了两个拦截器,这是在Spring中常用的链处理机制)

b.如果没有找到对应的Controller处理器,那么程序会尝试去查找配置中的默认处理器

c.当查找的controller为String类型时,意味着返回的是配置的bean名称,需要根据bean名称查找对应的bean

d.最后通过getHandlerExecutionChain(handler, request)方法对返回的Handler进行封装,以保证满足返回类型的匹配

getHandlerExecutionChain函数最主要的目的是将配置中的对应拦截器加入到执行链中,以保证这些拦截器可以有效地作用于目标对象。

3)没找到对应的Handler的错误处理:noHandlerFound(processedRequest, response)

一旦遇到没有找到Handler的情况,且用户没有设置默认的Handler来处理请求,就只能通过response向用户返回错误信息。

4)根据当前Handler寻找对应的HandlerAdapter:HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler())

对于获取适配器的逻辑,无非就是遍历所有适配器来选择合适的适配器并返回它,而某个适配器是否适用于当前的Handler逻辑被封装在具体的适配器中(ha.supports(handler))

在默认情况下,普通的web请求会交给SimpleControllerHandlerAdapter去处理。

5)缓存处理:

Spring提供对Last-Modified机制的支持,只需要实现LastModified接口。同时保证LastModified接口的getLastModified方法,当内容发生改变时返回最新的修改时间即可。

long lastModified = ha.getLastModified(request, mappedHandler.getHandler());

以SimpleControllerHandlerAdapter为例,其getLastModified函数实现为

	public long getLastModified(HttpServletRequest request, Object handler) {
		if (handler instanceof LastModified) {
			return ((LastModified) handler).getLastModified(request);
		}
		return -1L;
	}

如果服务器的内容在http请求的“If-Modified_since”请求头的时间后没有改变,则会返回304状态码。

6)HandlerInterceptor的处理:

a.Servlet API定义的servlet过滤器可以在servlet处理每个web请求的前后分别对它进行前置和后置处理。

b.有些时候,我们可能指向处理由某些SpringMVC处理程序处理的web请求,并在这些处理程序 返回的模型属性 被传递到视图之前,对他们进行一些操作。

c.Spring允许我们通过“处理拦截”Web请求,进行前置处理和后置处理。处理拦截是针对特殊的处理程序映射进行注册的,它只拦截通过这些处理程序映射的请求。

d.每个“处理拦截”都必须实现HandlerInterceptor接口,它包含三个你需要实现的回调方法:preHandle()、postHandle()和afterHandle()

mappedHandler.applyPreHandle(processedRequest, response);

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

mappedHandler.applyPostHandle(processedRequest, response, mv);

7)逻辑处理:mv = ha.handle(processedRequest, response, mappedHandler.getHandler())

对于逻辑处理,其实是通过适配器中转调用Handler并返回视图的。

8)异常视图的处理:

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)中的 mv = processHandlerException(request, response, handler, exception);

这里Spring主要的工作就是将逻辑引导至HandlerExceptionResolver类的resolveException方法。

9)根据视图跳转页面:render(mv, request, response)

浏览器在发送请求后无论成功与否都需要一个反馈,因此在逻辑处理的最后一定会涉及一个页面“跳转”的问题。

a.解析视图名称:view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);

DispatcherServlet会根据ModelAndView选择合适的视图来进行渲染,而这一功能就是在resolveViewName函数中完成的

	protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
			HttpServletRequest request) throws Exception {

		for (ViewResolver viewResolver : this.viewResolvers) {
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				return view;
			}
		}
		return null;
	}

b.页面跳转:view.render(mv.getModelInternal(), request, response)

当通过viewName解析到对应的View后,就可以进一步地处理跳转逻辑了。

createMergedOutputModel(model, request, response)主要把将要用到的属性放入request中,以便在其他地方可以直接调用

renderMergedOutputModel(mergedModel, request, response):根据不同的view类型进行页面跳转处理,或做forward跳转,或输出json数据等

    原文作者:Spring MVC
    原文地址: https://blog.csdn.net/Mailzyw/article/details/46651957
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞