JavaWeb中的SpringMVC概述

Spring的控制反转和面向切面,主要表达了代码之间解耦合的编程思想。

而现在说的SpringMVC则是一种web框架,它跟Struts2类似,从web.xml来看,struts2是通过Filter配置,spring是通过Servlet配置。

不管是struts.xml还是spring-mvc.xml,他们都通过xml的配置方式使用了反射,并指定相关类,将其加载到不同框架的容器里。

然后每当浏览器访问时,根据不同url对应的不同处理类(映射),从而达到控制用户访问的目的。

先来说SpringMVC在web.xml中是如何使用的。

Spring的配置和Struts2的配置稍有不同,并未使用Filter,使用的是Servlet.

下面上web.xml的配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
	<display-name>Eryi</display-name>

	<servlet>
		<!-- load-on-startup:表示启动容器时初始化该Servlet; -->
		<servlet-name>spring</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- url-pattern:表示哪些请求交给Spring Web MVC处理, “/” 是用来定义默认servlet映射的。 -->
	<!-- 也可以如“*.html”表示拦截所有以html为扩展名的请求。 -->
	<servlet-mapping>
		<servlet-name>spring</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>


	<!-- 自此请求已交给Spring Web MVC框架处理,因此我们需要配置Spring的配置文件, -->
	<!-- 默认DispatcherServlet会加载WEB-INF/[DispatcherServlet的Servlet名字,也就是上面的spring]-servlet.xml配置文件。 -->
	<!-- 即spring-servlet.xml -->

	<welcome-file-list>
		<welcome-file>login.jsp</welcome-file>
	</welcome-file-list>

</web-app>

双击web.xml中得Servlet配置,查看该实现类DispatcherServlet,查看里面的方法,按照经验,一般先查找一下里面是否有Init开头的方法,查看struts2框架式也可以使用该方法。

找到如下方法,这句话的意思是初始化servlet使用的策略对象:

	/**
	 * 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);
	}

先说一下形参ApplicationContext,如果我们创建了applicationContext.xml文件,甭切在在web.xml中指定了该文件的路径,那么spring框架启动后,就会去读取该文件,将该文件里面的bean配置归于spring管理,如果是通过注解的方式配置,那么spring会自动扫描指定包下面的类,将其注册成bean,然后归属spring管理,不管怎么配置,这些都属于applicationContext。

spring-mvc读取applicationContext.xml和struts2读取struts.xml的方式都是一样的。

看下面的代码,我们可以回顾一下我们通过xml获取到的ApplicationContext对象。

ApplicationContext.xml配置文件实际上就是指导Spring工厂进行Bean生产、依赖关系注入(装配)及Bean实例分发的“图纸”。

	public static void main(String[] args) {
		
	ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");//读取beans.xml中的内容
        Cat p = ctx.getBean("cat",Cat.class);//创建bean的引用对象
        p.Cry();
		
	}
回到之前的init方法,我么不看一下spring初始化操作时,都调用了那些方法,

initMultipartResolver(context); 初始化文件上传解析,如果在BeanFactory中为此命名空间定义了指定名称的bean,则不会提供多部分处理。

initLocaleResolver(context);初始化本地解析,和上述内容相同,那么我们将默认为AcceptHeaderLocaleResolver。

initThemeResolver(context);初始化主题解析,和上述内容相同如果没有的话将默认使用FixedThemeResolver

initHandlerMappings(ApplicationContext context);初始化映射服务,将请求映射到处理器(处理类),和上述内容相同如果没有的话将默认使用BeanNameUrlHandlerMapping

initHandlerAdapters(ApplicationContext context);初始化适配器,用来支持多种类型的处理器(处理类)和上述内容相同如果没有的话将默认使用SimpleControllerHandlerAdapter

initHandlerExceptionResolvers(ApplicationContext context);初始化异常解析器,运行过程中异常则交给这里处理,和上述内容相同如果没有的话将默认为Exception

initRequestToViewNameTranslator(ApplicationContext context);初始化servlet使用的”URL请求名“转换器,将url请求解析到到视图名,未配置则使用DefaultRequestToViewNameTranslator

initViewResolvers(ApplicationContext context);初始化视图解析器,通过ViewResolver解析逻辑视图名到具体视图实现,如果在BeanFactory中为此命名空间定义了指定名称的bean ,则使用InternalResourceViewResolver

initFlashMapManager(ApplicationContext context);初始化Flash数据管理,若未配置,则默认为web.servlet.support.DefaultFlashMapManager

说一下initHandlerMappings方法,该方法是将url映射到处理类:

	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;

		if (this.detectAllHandlerMappings) {
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				//ApplicationContext Ignore, we'll add a default HandlerMapping later.
			}
		}

		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}

既然是映射服务,那么这个方法肯定是为url映射到class控制类上进行服务,因此知道,这里的映射服务要不通过xml配置,要不通过注解配置。

这时候我们要知道,在web容器中,bean的创建肯定在映射创建之前。

handlerMappings的创建肯定要在servlet之前,因为url映射都没有的话,容器怎么去找servlet呢?

我们想一下Servlet的3个方法,我们就知道,handlerMappings的创建可能或者应该在servlet的初始化方法Init()中。

下面的绿色文字是对上述代码进行描述(个人见解,若有错误请指正):

handlerMappings是个List,代码为:private List<HandlerMapping> handlerMappings

代码中,首先检测映射服务this.detectAllHandlerMappings是否为true。( 这里判断是否默认添加所有的HandlerMappings,初始值是默认添加的)

若为true,则获取Map<String, HandlerMapping> matchingBeans,即从ApplicationContext中找到matchingBeans,若其不为null,则将其赋值给handlerMappings,且使用注解的方式进行排序。

如果默认值this.detectAllHandlerMappings为false,则通过ApplicationContex找到指定的bean获取HandlerMapping并排序

上述的代码说来说去,就是为了获取或创建HandlerMapping,若能获取到则跳过,若获取不到则创建,并采用默认的方式创建。

该链接对handlerMappings进行了详细解释:http://www.cnblogs.com/dragonfei/p/6148625.html,我没提取,等有时间再提取。。。

说完这个,没有说调用映射服务的方法,那么接下来再说一下DispatcherServlet类的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.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				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.
				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请求时,springmvc将得到的是一个HandlerExecutionChain对象,而这个对象就属于上面所说的handlerMappings,他包含了mvc模块的拦截器,即handlerInterceptor和真正处理请求的handler。

而下面这个方法最终调用的是getHandler()这个方法,这个方法就是隶属于handlerMappings对象的。

//根据请求找到对应的handler       
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
   noHandlerFound(processedRequest, response);
   return;
}

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
......  调用拦截器等   ......
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

由此,我们看到,我们通过遍历handlerMappings,获取每一个对象,然后从每个对象中获取HandlerExecutionChain,然后就返回给了上面的对象mv,mv即ModelAndView。mv根据映射结果,返回给浏览器。

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		for (HandlerMapping hm : this.handlerMappings) {
			if (logger.isTraceEnabled()) {
				logger.trace(
						"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
			}
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}

对此我们进行一个简单的整合:

当浏览器请求一个url时,假如web应用刚启动,则springMVC首先通过xml或者注解的方式将各个bean加载到applicationContext中。

然后会去创建映射服务,即handlerMappings对,该对象的作用是将url与指定的控制类进行一一对应。在此之前,servlet未被创建。

然后根据url去创建servlet。如果该url未找到对应的控制类则报错。找到则根据对应控制类的相应,返回给浏览器页面。

下面是个简单的流程图(这图是盗的,无意侵犯。。。):


spring的详细流程图在这里(也是盗的。。。):


大概的意思说到了,希望各位能和我一样,能有个大概的了解,毕竟别人写的东西看起来还是很吃力的,其次是希望这文章能让大家对spring底层稍微有点理解。

参考博客:http://www.cnblogs.com/baiduligang/p/4247164.html

参考博客:http://blog.csdn.net/hongxingxiaonan/article/details/47910911

参考博客:http://blog.csdn.net/sunxing007/article/details/4584748


猜你喜欢

转载自blog.csdn.net/u012605477/article/details/76199857
今日推荐