最近在面试招人,很多同学对于DispatcherServlet不太熟悉,有的觉得没了解也没什么用,其实不然,SpringMVC的逻辑实现都在这里,而且这里有很多好的设计模式的应用值得我们借鉴和学习。有利于我们更深的理解SpringMVC。

了解Servlet

首先DispatcherServlet是servlet接口的一个实现类。servlet是基于http协议的,在服务端(如Tomcate)运行的,是按照servlet规范编写的一个Java类。做Java Web开发的应该都了解它。主要用于处理客户端的请求并将其结果发送至客户端。servlet的生命周期可分三个阶段:初始化、运行和销毁。

  • 初始化阶段
    (1) servlet容器加载servlet类,把servlet类的.class文件中的数据读至内存中。
    (2) servlet容器创建一个ServletConfig对象。
    (3) 创建一个servlet对象
    (4) servlet容器调用servlet对象的init方法进行初始化。
  • 运行阶段
    当servlet容器接收到一个请求时,servlet容器会针对该请求创建一个servletRequest和servletResponse对象,然后调用service方法。并把这两个参数传递给service方法。从servletRequest中获取请求信息,并处理请求。再通过servletResponse生成这个请求的响应结果。然后销毁对象。无论请求是get或者post,最终这个请求都会由service方法来处理。
  • 销毁阶段
    当Web应用被终止时,servlet容器会先调用servlet对象的destrory方法,然后销毁servlet对象。一般我们会在该方法中释放servlet所占用资源。如:数据库连接、关闭文件输入输出流等。
    servlet的框架是由两个Java包组成:javax.servlet和javax.servlet.http. HttpServlet是定义的采用Http通信协议的Servlet的特例。也是我们常用的。毕竟大部我们的Web服务都是走的Http协议。HttpServlet针对Http请求的delete、get、post、options、put和trace.定义的相应的方法。

Servlet例子:
(1) 建议Servlet

/**
 * @author lhn
 */
public class MyServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		handleTest(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		handleTest(req, resp);
	}

	@Override
	public void init()  {
		System.err.println("初始化喽");
	}

	private void handleTest(HttpServletRequest request,HttpServletResponse response) {
		System.err.println(" handleTest");
		ServletContext sc = getServletContext();
		RequestDispatcher rd = sc.getRequestDispatcher("/index.jsp");
		try {
			rd.forward(request, response);
		} catch (ServletException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
}

虽然只有一个类,但却是一个完整的Web处理过程。SpringMVC 的dispatcherServlet的最终目的也是在做这些事情。init方法在启动时被执行初始化。SpringMVC时该方法内执行了被始化initServletBean(). get/post()方法里处理了跳转逻辑。springMVC中也做了类似的逻辑,当然要比我们复杂的多。
(2) 添加配置

  <servlet>
  	<servlet-name>myservlet</servlet-name>
    <servlet-class>com.lhn.web.servlet.MyServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
      <servlet-name>myservlet</servlet-name>
    <url-pattern>*.action</url-pattern>
  </servlet-mapping>

请求:localhost/a.action

DispatcherServlet的初始化

通过上面我们可以了解到在Servlet初始化阶段会调用init方法,so我们先从DispatcherServlet类中的init方法入手,首先看它是否重写了该方法。在其父类HttpServletBean中找到了该方法。

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

	try {
			//解析web.xml中init-param 并封装至pvs中。
			PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
			//将当前的Servlet类转为Spring可操作的Wrapper类
			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类型实例转换为BeanWrapper类型实例,以便使用Spring提供的注入功能进行对应属性的注入。这些属性如contextAttribute、contextClass、nameSpace、contextConfigLocation等。都可以在web.xml文件中以初始化参数的方式配置在servlet声明中。DipatcherServlet继承自Frameworker,该类中包含了对应的同名属性,Spring会将这些参数注入到对应的值中。属性注入步骤如下:

1.封装初始化参数

上面代码中的第7行ServletConfigPropertyValues及做了该事情,同时也对属性进行验证。

	public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
			throws ServletException {

           Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?new HashSet<String>(requiredProperties) : null;

			Enumeration<String> en = config.getInitParameterNames();
			while (en.hasMoreElements()) {
				String property = en.nextElement();
				Object value = config.getInitParameter(property);
				addPropertyValue(new PropertyValue(property, value));
				if (missingProps != null) {
					missingProps.remove(property);
				}
			}

			// Fail if we are still missing properties.
			if (missingProps != null && missingProps.size() > 0) {
				throw new ServletException(
					"Initialization from ServletConfig for servlet '" + config.getServletName() +
					"' failed; the following required properties were missing: " +
					StringUtils.collectionToDelimitedString(missingProps, ", "));
			}
		}

该方法对初始化进行封装,通过requiredProperties参数验证servlet中配置的必须性。一旦没指定必须值,则异常。

2.将servlet实例转为BeanWrapper实例

3.注册相对于Resource的属性编辑器

如果有Resource类型的属性,就会使用ResouceEditor去解析。

4.属性注入

BeanWrapper自动注入属性。

5.servletBean的初始化

该过程由子类完成。在FrameworkServlet中覆盖了HttpServletBean中的initServletBean方法,如下

	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 {
			//关键初始化WebApplicationContext
			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()方法。

WebApplicationContext的初始化

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

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

		if (this.webApplicationContext != null) {
			// webApplicationContext 实例在构造函数中被注入
			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) {
			//根据contextAttribute属性加载
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			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;
	}

本方法中的初始化主要包含几个部分

1、寻找或者创建对应的webApplicationContext实例

webApplicationContext的寻找及创建包含以下几个步骤
(1)通过构造函数的注入进行初始化。
当进入initWebApplicationContext方法后通过判断this.webApplicationContext != null 后,
便可以确定this.webApplicationContext是否是通过构造函数来初始化的。由于DispatcherServlet只可被声明一次,而this.webApplicationContext除了initWebApplicationContext()方法可以创建外,还可以通过构造函数来初始化。
(2)通过contextAttribute进行初始化
通过web.xml文件中配置的servlet参数contextAttribute来查找ServletContext中对应的属性,默认为:WebApplicationContext.class.getName() + ".ROOT",也就是在ContextLoaderLister加载时会创建webApplicationContext实例,并将实例以WebApplicationContext.class.getName() + ".ROOT"为key放入ServletContext中,当然我们也可以重写初始化逻辑使用自己创建的WebApplicationContext,并在servlet的配置中通过初始化参数contextAttribute指定key.

	protected WebApplicationContext findWebApplicationContext() {
		String attrName = getContextAttribute();
		if (attrName == null) {
			return null;
		}
		WebApplicationContext wac =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
		if (wac == null) {
			throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
		}
		return wac;
	}

(3) 重新创建WebApplicationContext 实例
通过以上方法都没有拿到实例,则只能重新创建新的实例。

	protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
		return createWebApplicationContext((ApplicationContext) parent);
	}
	protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
	//获取servlet的初始化参数,contextClass,如果没有配置,则默认为:XmlWebApplicationContext.class
		Class<?> contextClass = getContextClass();
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Servlet with name '" + getServletName() +
					"' will try to create custom WebApplicationContext context of class '" +
					contextClass.getName() + "'" + ", using parent context [" + parent + "]");
		}
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException(
					"Fatal initialization error in servlet with name '" + getServletName() +
					"': custom WebApplicationContext class [" + contextClass.getName() +
					"] is not of type ConfigurableWebApplicationContext");
		}
		//通过反射方式实例化contextClass
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

		wac.setEnvironment(getEnvironment());
		wac.setParent(parent);
		wac.setConfigLocation(getContextConfigLocation());
		//初始化Spring环境加载配置文件
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}

configureAndRefreshWebApplicationContext

无论是通过构造方法注入还是通过单独创建,都免不了会调用configureAndRefreshWebApplicationContext方法来对已经创建的WebApplicationContext 实例进行刷新,下面看该方法做了哪些工作。

	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			// The application context id is still set to its original default value
			// -> assign a more useful id based on available information
			if (this.contextId != null) {
				wac.setId(this.contextId);
			}
			else {
				// Generate default id...
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
			}
		}

		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

		// the wac environment's #initPropertySources will be called in any case when
		// the context is refreshed; do it eagerly here to ensure servlet property sources
		// are in place for use in any post-processing or initialization that occurs
		// below prior to #refresh
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment)env).initPropertySources(getServletContext(), getServletConfig());
		}

		postProcessWebApplicationContext(wac);

		applyInitializers(wac);
		//加载配置文件及整合parent至wac
		wac.refresh();
	}

无论调用方法怎么变,都会调用父类提供的refresh()方法进行配置文件加载。

刷新

onRefresh是FrameworkServlet类中提供一个模版方法,在其子类DispatcherServlet中对它进行了重写,用于刷新Spring在Web功能实现中所必须使用的全局变量。我们说一下他的初始化过程以及使用的场景。

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

	protected void initStrategies(ApplicationContext context) {
		//(1)初始化MultipartResolver
		initMultipartResolver(context);
		//(2)初始化LocaleResolver
		initLocaleResolver(context);
		//(3)初始化ThemeResolver
		initThemeResolver(context);
		//(4)初始化HandlerMappings
		initHandlerMappings(context);
		//(5)初始化HandlerAdapters
		initHandlerAdapters(context);
		//(6)初始化HandlerExceptionResolvers
		initHandlerExceptionResolvers(context);
	    //(7)初始化RequestToViewNameTranslator
		initRequestToViewNameTranslator(context);
		//(8)初始化ViewResolvers
		initViewResolvers(context);
		//(9)初始化FlashMapManager
		initFlashMapManager(context);
	}

(1)初始化MultipartResolver

在Spring中,MultipartResolver主要用于处理文件上传。默认情况下,Spring是没有multipart处理的,因为一些开发者想要自己处理它们。如果想使用Spring的multipart,则需要在Web应用的上下文中添加Multipart解析器。这样每一个请求就会被检查是否包含Multipart。如果包含Multipart,则定义的MultipartResolver就会解析它,这样请求中的MultipartResolver属性就会被处理。配置如下:

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--该属性用于配置可上传文件的最大byte-->
<property name="maximumFileSize"><value>100000</value></property >
</bean>

CommonsMultipartResolver也提供也其它功能用于帮助用户完成上传功能。有空可以看一下。
MultipartResolver就是在initMultipartResolver方法中被加入至DispatcherServlet中的。

	private void initMultipartResolver(ApplicationContext context) {
		try {
			this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
			}
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Default is no multipart resolver.
			this.multipartResolver = null;
			if (logger.isDebugEnabled()) {
				logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
						"': no multipart request handling provided");
			}
		}
	}

在刷新前已经完成了Spring中配置文件的解析,这里只需要在 ApplicationContext通过getBean方法获取Bean就可。

(2)初始化LocaleResolver

Spring中配置国际化配置。

	private void initLocaleResolver(ApplicationContext context) {
		try {
			this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
			}
		}
		catch (NoSuchBeanDefinitionException ex) {
			// We need to use the default.
			this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
						"': using default [" + this.localeResolver + "]");
			}
		}
	}

查看LocaleResolver接口的实现可以看出有3种方式进行国际化。大家可以有兴趣可以看一下。

(3)初始化ThemeResolver

在Web中我们可能会遇到通过主题Theme来控制网页风格,每一个人或者一类人看到不同风格的网页以增加用户体验。
感觉兴趣的同学也可以看一下。

(4)初始化HandlerMappings

这个我们常与我们打交道,当客户端发出Request时DispatcherServlet会将Request提交给HandlerMapping,然后HandlerMapping根据ApplicationContext 的配置来回传给 DispatcherServlet相应的Controller。
在基于SrpingMVC的Web应用程序中,我们可以配置多个HandlerMapping。DispatcherServlet在选用HandlerMapping的过程中,将根据我们所指定的一系列的HandlerMapping的优先级进行排序,然后优先使用优先级在前的HandlerMapping。如果当前的HandlerMapping能够返回可用的Hander,DispatcherServlet则使用当前返回的Handler进行Web请求处理,而不再继续询问其它的HandlerMapping。否则将继续寻找,直到找到一个可用的Handler为止。

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.
				OrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// 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");
			}
		}
	}

从代码中可以看出默认情况下,SpringMVC将加载当前系统中所有实现了HandlerMapping接口的bean。如果只期望加载指定的HandlerMapping时,可以修改Web.xml中的DispatcherServlet的初始参数。

<init-param>
	<param-name>detectAllHandlerMappings</param-name>
	<param-value>false</param-value>
</init-param>

此时将执行以下代码

else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
			}
		}
	public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";

Spring将查找名为“handlerMapping”的bean,并作为当前系统中惟一的HandlerMapping。如果没有定义HandlerMapping的话,则执行以下代码

if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}

SpringMVC将按照org.springframework.web.servlet.DispatcherServlet所在目录下的DispatcherServlet.properties中所定义的org.springframework.web.servlet.HandlerMapping的内容来加载默认的HandlerMapping.

(5)初始化HandlerAdapters

	private void initHandlerAdapters(ApplicationContext context) {
		this.handlerAdapters = null;

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

		// Ensure we have at least some HandlerAdapters, by registering
		// default HandlerAdapters if no other adapters are found.
		if (this.handlerAdapters == null) {
			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
			}
		}
	}

(未完待续)

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐