总结前端控制器DispatcherServlet(下)-程序环境加载

上一篇日志总结了Servlet规范,对于所有处理用户请求的服务器组件,都要去实现Servlet接口。GenericServlet保留了Servlet配置,提供无参数的init()方法供子类重写,还提供了通用协议的service()方法,该方法子类必须重写,来实现不同协议的Servlet。最后HttpServlet重写GenericServlet的service()方法,实现支持HTTP协议的Servlet,负责分发HTTP请求到不同的方法中。由DipsatcherServlet的多级继承图可以看到,往下走,到了HttpServletBean。

 

HttpServletBean

HttpServletBean的作用是把Servlet配置参数作为Bean属性,对我们自己的Servlet(如Spring MVC中的DispatcherServlet)做初始化。具体是在web.xml文件中的<servlet>标签对里,使用<init-param>标签对来配置该servlet的参数,其中参数包含有:contextConfigLocation、contextClass、contextInitializerClass,contexAttribute等。

<servlet>
	<!-- 定义servlet配置,实现类为DispatcherServlet,即前端控制器类 -->
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<!-- DispatcherServlet初始化参数,加载的配置文件为编译目录classpath -->
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springmvc.xml</param-value>
		</init-param>
	</servlet>

初始化Servlet成员变量

实现方式通过重写GenericServlet中的init()方法:

@Override
public final void init() throws ServletException {
	if (logger.isDebugEnabled()) {
		// 从Servlet配置中获取名字,这个Servlet配置来自GenericServlet
		Logger.debug("Initializing servlet '" + getServletName() + "'");
	}
		
	// 设置Servlet的初始化参数作为Servlet Bean的属性
	try {
		// PropertyValues是一个容器类,存放从web.xml配置文件中配置的参数
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), 
				this.requiredProperties);
		
		// 把当前的Servlet作为一个Bean,把Bean的属性和属性的存取方法信息放入BeanWrapper中
		BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
		
		// 注册一个可以在资源和路径之间进行转化的用户化编辑器,这些资源是这个Web应用的内部资源,例如一个文件或图片等
		ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
		bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader));
		
		// 提供一个模板方法initServletBean(),供子类去实现更多的初始化
		initBeanWrapper(bw);
		
		// 把初始化指定的参数都赋值到Serlvet的属性中,第二个参数true表示忽略位置属性
		bw.setPropertyValues(pvs, true);
	} catch (BeansException ex) {
		logger.error("Failed to set bean properties on servlet '" + getServletName() + 
				"'", ex);
	}
	
	// 提供一个模板方法initServletBean(),供子类去初始化其他资源
	initServletBean();
	
	if (logger.isDebugEnabled()) {
		logger.debug("Servlet '" + getServletName() + "' configured successfully");
	}
}

来看看代码,第3-6行是日志输出,关键从第9行开始,首先实例化一个ServletConfigPropertyValues容器对象,用来存放web.xml配置文件中配置的参数,即<init-param>标签对中的配置。到第15行,实例化一个Bean包裹器BeanWrapper(BeanWrapper是Spring中的一个重要组件,作用是为Bean完成属性设置),后面forBeanPropertyAccess()方法中的参数this,指的就是当前这个类HttpServletBean的实例,也就是我们的DispatcherServlet(因为我们在web.xml中配置了它)。第17行,实例化一个ServletContextResourceLoader来获取当前Servlet中的一些资源文件,例如classpath资源(Spring提供的Resource接口可以让我们对一些底层资源做一致的访问操作,如URL资源,File资源和classpath资源)。第19行在BeanWrapper中注册这个用户化编辑器,到这一步,默认的BeanWrapper初始化就完成了,第22行HttpServletBean提供了一个initBeanWrapper()供子类去做更多的初始化,如增加用户化编辑器等。

      最后,第25行,调用BeanWrapper的setPropertyValues()方法,把前面的初始化参数,即ServletConfigPropertyValues中存放的配置参数,都设置到被BeanWrapper包裹的实例里对应的成员变量中去,完成对Servlet的初始化。在第32行还提供了一个模板方法initServletBean(),供子类对Servlet做更多的初始化。

      总结下HttpServletBean,它继承自HttpServlet,功能是把web.xml中的Servlet配置参数(<init-param>标签对中的配置),初始化到Servlet的成员变量中,对于Servlet中的成员变量,都提供了getter()和setter()方法,我们可以很方便地去获取这些参数值。在Spring MVC中,HttpServletBean就是通过BeanWrapper包裹实例DispatcherServlet,然后将web.xml中的Servlet配置参数初始化到DispatcherServlet中。

 

FrameworkServlet

再往下到了FrameworkServlet,FrameworkServlet就是Spring Web的一个基本Servlet实现类了。它的作用是初始化,加载WebApplicationContext(Web应用程序环境),FrameworkServlet实现了HttpServlet中的所有接口实现,所以可以将HTTP支持的GET、PUT、POST,DELETE等方法分发到Spring MVC的控制器方法中进行处理,,子类必须实现其doService()方法来处理接收到的请求。

加载Web应用程序环境

FrameworkServlet通过继承HttpServletBean,实现了ApplicationContextAware接口,接口中定义了一个setApplicationContext()方法,以此来在初始化时加载Web应用程序环境。FrameworkServlet加载WebApplicationContext应用程序环境过程:

可以看到,FrameworkServlet初始化Servlet时,首先会去查找一个专用的应用程序跟环境,即查找我们在web.xml中servlet标签对里是否有配置contextClass,如果有,就会去加载我们配置的专用的contextClass,如果不存在专用的根环境,就会加载默认的共享根环境。

监听与动态刷新

      在FrameworkServlet父类HttpServletBean中的init()方法里,最后调用了一个模板方法initServletBean(),这个方法就是HttpServletBean供子类去重写,完成Servlet框架的初始化,来看看FrameworkServlet重写的这个方法:

@Override
public final void initServletBean() throws ServletException {
	// 输出初始化信息到日志中
	getServletContext().log("Initializing Spring FrameworkServlet '" + 
			getServletName() + "'");
	
	if (this.logger.isInfoEnabled()) {
		this.logger.info("FrameworkServlet '" + getServletName() + 
				"': initializationstarted");
	}
	
	// 初始化环境开始时间
	long startTime = System.currentTimeMillis();
	try {
		// 初始化Servlet环境
		this.webApplicationContext = initWebApplicationContext();
		
		// 调用模板方法,供子类实现,初始化指定的资源(例如具体实现在DispatcherServlet中)
		initFrameworkServlet();
	} catch (ServletException ex) {
		this.logger.error("Context initialization failed", ex);
		throws ex;
	} catch (RuntimeException ex) {
		this.logger.error("Context initialization failed", ex);
		throws ex;
	}
	
	if (this.logger.isInfoEnabled()) {
		// 初始化环境用时
		long elapsedTime = System.currentTimeMillis() - startTime;
		// 输出到日志信息中
		this.logger.info("FrameworkServlet '" + getServletName() + 
				"': initializationcomplete in " + elapsedTime + "ms");
	}
	
}

可以看到,initServletBean()方法关键在于第16行,调用initWebApplicationContext()方法,完成Web应用程序环境的加载(加载过程在上面已经贴出了图示)。接下来就看看,initWebApplicationContext()方法是如何实现的:

protected WebApplicationContext initWebApplicationContext() {
	// 首先查找这个Servlet(例如DispatcherServlet)是否配置有专用的根环境
	WebApplicationContext wac = findWebApplicationContext();
	
	// 如果没有,就加载默认的共享根环境
	if (wac == null) {
		// 加载默认的共享根环境,这个根环境通过关键字ROOT_WEB_APPLICATION
		// _CONTEXT_ATTRIBUTE保存在Servlet环境里
		WebApplicationContext parent = 
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		
		// 创建Servlet的子环境,并引用已经得到的著环境
		wac = createWebApplicationContext(parent);
	}
	
	// Servlet框架在初始化时为子类提供了初始化模板方法initFrameworkServlet()和onRefresh()
	if (!this.refreshEventReceived) {
		onRefresh(wac);
	}
	
	// 如果设置了发布环境属性,则把这个Web应用程序环境以ServletContextAttributeName的值
	// 作为关键字保存到Servlet环境里,以此来让其他Servlet共享这个Web应用程序环境
	if (this.publishContext) {
		// 将这个环境发布并存储到Servlet Context中
		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;
}

按照加载Web应用程序环境的过程,第3行首先判断在web.xml中是否有配置servlet的专用根环境,如果你没有在里面配置ContextLoaderListener,那么wac的值就为null。接下来第9行就会去加载一个默认的共享根环境,并把我们的Servlet子环境引用得到这个主环境。

      在Web应用环境初始化后,这个Servlet类就会作为Web应用程序环境的事件处理监听器,然后通过onRefresh()方法动态刷新来实现各种组件的初始化,例如DispatcherServlet就是通过onRefresh()方法来动态加载Spring MVC需要的组件。第17行的refreshEventReceived标识onRefresh()方法是否已经被调用过,如果没有,就调用onRefresh()。

最后,第23-31行,根据publishContext标识是否设置了发布环境属性,如果设置了,就把当前初始化的Web应用程序环境设置到Servlet环境中,供其他Servlet共享这个Web应用程序环境。

总的来说,FrameworkServlet的作用就是初始化WebApplicationContext,我们的Web应用程序环境(如Spring MVC环境)。

 

DispatcherServlet

最后来到我们的DispatcherServlet,由前面经过多级继承自HttpServlet,每一级都完成特定的初始化。Servlet提供初始化模板方法,GenericServlet保存了config配置,并实现了Servlet接口的通用协议的service()方法,下一级HttpServlet重写了service()方法,实现了支持HTTP协议的Servlet,完成对用户HTTP请求的分发。HttpServletBean根据<init-param>的配置,完成Servlet中成员变量的赋值,如contextConfigLocation、contextClass等。FrameworkServlet加载专用或默认的共享的应用程序环境,并提供onRefresh()方法供子类(如DispatcherServlet)完成组件的动态加载。来到DispatcherServlet,我们的前端控制器,作为整个继承链的最后一个类,它会在Web应用程序环境中查找Spring MVC的需要的组件,在接收到HTTP请求后,将其分发到响应的组件中进行处理。

根据其父类FrameworkServlet,当Web应用程序环境初始化完成之后,该类就会变成当前应用程序进行环境事件处理的监听器,所以当DispatcherServlet监听事件知道Web应用程序环境刷新后(调用了onRefresh()方法),便会在主环境和子环境中查找是否已经注册了Spring MVC需要的组件。Spring MVC的组件按照数量来划分,分为可选组件、单值组件和多值组件。在Web应用程序环境初始化和刷新时,首先调用initStrategies()方法,该方法完成组件的加载:

protected void initStrategies(ApplicationContext context) {
	// 初始化多部(multipart)请求解析器,没有默认的实现
	initMultipartResolver(context);
	
	// 初始化地域解析器,默认的实现是AcceptHeaderLocaleResolver
	initLocaleResolver(context);
	
	// 初始化主题解析器,默认的实现是FixedThemeResolver
	initThemeResolver(context);
	
	// 初始化处理器映射器集合,默认的实现是BeanNameUrlHandlerMapping和
	// DefaultAnnotationHandlerMapping
	initHandlerMappings(context);
	
	// 初始化处理器适配器集合,默认的实现是HttpRequestHandlerAdapter,
	// SimpleControllerHandlerAdapter和AnnotationMethodHandlerAdapter
	initHandlerAdapters(context);
	
	// 初始化处理器异常解析器集合,默认的实现是AnnotationMethodHandlerExceptionResolver、
	// ResponseStatusExceptionResolver和DefaultHandlerExceptionResolver
	initHandlerExceptionResolvers(context);
	
	// 初始化请求视图名解析器,默认的实现是DefaultRequestToViewNameTranslator
	initRequestToViewNameTranslator(context);
	
	// 初始化视图解析器集合,默认的实现是InternalResourceViewResolver
	initViewResolvers(context);
}

接下来看看里面的几个具体方法。

可选组件MultipartResolver

可选组件指的是在工作流中可能需要也可能不需要的组件,例如MultipartResolver,它负责处理文件上传:

private void initMultipartResolver(ApplicationContext context) {
	try {
		// 从配置的Web应用程序环境中查找多部请求解析器
		this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, 
				MultipartResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
		}
	} catch (NoSuchBeanDefinitionException ex) {
		// 如果没有查找到多部请求解析器,则忽略它,因为不是所有的应用程序都需要它,多部请求
		// 通常用在文件上传中
		this.multipartResolver = null;
		if (logger.isDebugEnabled()) {
			logger.debug("Unable to locate MultipartResolver with name '" + 
					MULTIPART_RESOLVER_BEAN_NAME + "': no multipart request handling provided");
		}
	}
}

它的初始化很简单,之间在Web应用程序环境中查找,如果没有找到,则会忽略这个组件。

单值组件LocalResolver

单值组件顾名思义就是整个工作流中只需要一个的组件,例如主题解析器ThemeResolver,地域解析器LocaleResolver和请求视图名解析器RequestToViewNameTranslator。与可选组件的初始化一样,都是直接在Web应用程序环境中查找,不同的是,如果查找失败,则会去查找Spring MVC的默认配置,并根据其默认配置来加载地域解析器:

private void initLocaleResolver(ApplicationContext context) {
	try {
		// 从配置的Web应用程序环境中查找地域请求解析器
		this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, 
				LocaleResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
		}
	} catch (NoSuchBeanDefinitionException ex) {
		// 如果没有查找到低于请求解析器,则查找默认的配置,并根据默认配置初始化地域解析器
		this.localResolver = getDefaultStrategy(context, LocaleResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Unable to locate LocaleResolver with name '" + 
					LOCALE_RESOLVER_BEAN_NAME + "': using default [" + this.localResolver + 
					"]");
		}
	}
}

多值组件HandlerMappings

多值组件也就是整个工作流中可以有多个实现组件,很容易想到,肯定有我们的处理器映射器,因为DispatcherServlet需要轮询查找出能够处理当前HTTP请求的处理器映射器,让其返回一个执行链。来看看多值组件的初始化代码:

private void initHandlerMappings(ApplicationContext context) {
	this.initHandlerMappings = null;
	
	if (this.detectAllhandlerMappings) {
		// 如果配置为自动检测所有的处理器映射器,则在加载的Web应用程序环境中查找
		// 所有实现处理器映射器接口的Bean
		Map<String, HandlerMapping> matchingBeans = 
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, 
						HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new 
					ArratList<HandlerMapping>(matchingBeans.values());
					
			// 根据这些Bean所实现的Order接口进行排序
			orderComparator.sort(this.handlerMappings);
		}
	} else {
		// 如果没有配置为自动检测所有的处理器映射器,则在Web应用程序环境中查找名为
		// "handlerMapping"的Bean作为处理器映射器
		try {
			HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, 
					HandlerMapping.class);
			
			// 构造单个Bean集合
			this.handlerMappings = Collections.singletonList(hm);
		} catch (NoSuchBeanDefinitionException ex) {
			// 忽略已成,后面讲根据引用是否为空判断是否查找成功
		}
	}
	
	if (this.handlerMappings == null) {
		// 如果仍然没有查找到注册的处理器映射器的实现,则使用默认的配置加载处理器映射器
		this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		if (logger.isDebugEnabled()) {
			logger.debug("No HandlerMappings found in servlet '" + getServletName() + 
					"': using default");
		}
	}
}

第4行首先判断是否配置为检测所有的处理器映射器,如果是,就在Web应用程序环境中查找所有的处理器映射器。若不是配置检测所有的处理器映射器,第17行则去加载一个名为”handlerMapping”的处理器映射器。第31行,若以上方式都没有查找到注册的处理器映射器,那么Spring MVC就会使用默认的配置,加载默认的处理器映射器。

      以上,就是DispatcherServlet的初始化过程,经过多级继承,逐层初始化,最后完成Spring MVC的组件注册后,DispatcherServlet就可以开始拦截用户的HTTP请求,将其分发到Spring MVC的工作流中进行响应处理了。

 

以上源码已上传GitHub:

https://github.com/justinzengtm/SSM-Framework/tree/master/SpringMVC_Project/source%20code

发布了97 篇原创文章 · 获赞 71 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/justinzengTM/article/details/102655055