SpringMVC(三)-DispatcherServlet(一)

本文深入解析SpringMVC中DispatcherServlet的工作原理,包括其结构、初始化过程及如何创建和初始化WebApplicationContext。文章详细介绍了DispatcherServlet的继承关系、关键属性如contextConfigLocation的作用,以及initWebApplicationContext方法在容器创建中的核心作用。

前言

前面2篇文章 主要聊了下SpringMVC Root容器的创建过程,今天去看DispatcherServlet是怎么去运作的,这个估计有点儿长,可能要分2篇博文,边写边说吧~

结构

  <!--Spring-webmvc中-->
  public class DispatcherServlet extends FrameworkServlet{}
  
  public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware{}
  
  /**
 * Simple extension of {@link javax.servlet.http.HttpServlet} which treats
 * its config parameters ({@code init-param} entries within the
 * {@code servlet} tag in {@code web.xml}) as bean properties.
 */
  public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware{}
  
  
  <!--javax.servlet-api 中-->
  public abstract class HttpServlet extends GenericServlet{}
  
  public abstract class GenericServlet 
    implements Servlet, ServletConfig, java.io.Serializable{}

从结构中可以看出 DispatcherServlet 最终还是一个Servlet对象

HttpServletBean

HttpServletBean 从类的继承关系上可以看出,继承了HttpServlet 还实现了EnvironmentAware接口,这个接口 在分析Spring的时候,aware接口应该会很熟悉,
这个是用来获取应用的配置信息的,

从HttpServletBean类的描述来看,这个类就是处理配置参数的,也就是我们在web.config 中配置Servelet init-param

我们主要看下他的init 方法

public final void init() throws ServletException {
		// Set bean properties from init parameters.
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}

		// Let subclasses do whatever initialization they like.
		initServletBean();
	}

从上面的注释描述中 我们可以得到这个方法就是为了设置servelet的properties的属性的,
使用了BeanWrapper这个工具类,是Spring中方便操作bean的中属性的类,这边说白了就是给FrameworkServlet中contextConfigLocation的赋值,里面的值就是 我们web.config 中Servlet的 init-param中里面contextConfigLocation的值,所以直达知道了吧 里面的配置名称不是随便定义的,给不能改的,改了的话 就没法属性赋值了!所以小伙伴们 是不是又get到了一个点!

initServletBean方法 是一个子类可以重写的类,HttpServletBean中没有具体的实现。

FrameworkServlet

FrameworkServlet 代码相对比较多,Servlet的WebApplicationContext就是这个类负责创建初始化的。

contextConfigLocation

/** Explicit context config location */
	private String contextConfigLocation;

	public void setContextConfigLocation(String contextConfigLocation) {
		this.contextConfigLocation = contextConfigLocation;
	}

	public String getContextConfigLocation() {
		return this.contextConfigLocation;
	}

这个地方就是 我上面一个章节HttpServletBean说到使用BeanWrapper 设置了FrameworkServlet的属性,

contextClass

public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
<!--创建webApplicationContext的类型-->
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;

这个类中 使用的默认使用webApplicatonContext的类型也是 XmlWebApplicationContext.class 这个和创建Root webApplicationContext的类型是一样的。

webApplicationContext

webApplicationContext 是FrameworkServlet的一个私有变量,我们看下有几种方式可以给其赋值,首先我们能看到FrameworkServlet有一个带参的构造函数,这个构造函数参数就是WebApplicationContext 这个和我们之前讲的ContextLoaderListener 差不多一个意思 都是可以支持传入一个WebApplicationContext对象的,

第二个地方 我们也应该能想到 这个类继承了ApplicationContextAware接口 这样很显然 可以获取或得到当前的容器,代码如下:

@Override
	public void setApplicationContext(ApplicationContext applicationContext) {
		if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
			this.webApplicationContext = (WebApplicationContext) applicationContext;
			this.webApplicationContextInjected = true;
		}
	}

initServletBean

这个方法 我们在HttpServletBean中也聊到过 就是 子类 可以去重写实现的类,这边就是重写了HttpServletBean#initServletBean的方法,
这个方法 里面的内容 主要就是执行了initWebApplicationContext 方法 其余的也没有什么,initFrameworkServlet方法也是一个没有任何实现的类,子类可以去重写实现,可以看下注释 说的也很清楚

    protected final void initServletBean() throws ServletException {
		long startTime = System.currentTimeMillis();
		try {
		<!--主要的就是 执行initWebApplicationContext这个方法-->
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}
	}
	
	/**
	 * This method will be invoked after any bean properties have been set and
	 * the WebApplicationContext has been loaded. The default implementation is empty;
	 * subclasses may override this method to perform any initialization they require.
	 * @throws ServletException in case of an initialization exception
	 */
	protected void initFrameworkServlet() throws ServletException {
	}

initWebApplicationContext

这个方法将是 整个创建WebApplicationContext的核心,先上下代码吧:

  protected WebApplicationContext initWebApplicationContext() {
       <!--这个地方 从变量的命名 我们也能看出 是得到root容器,也就是我们上篇博文里面讲到的,ContextLoaderListener方法里面创建的Root WebApplicationContext -->
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			//这个就是 我刚才上面讲的 webApplicationContext 赋值的2个地方,如果构造函数里面或者Aware接口里面的赋值
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				<!--说明容器还没激活,执行刷新流程-->
				if (!cwac.isActive()) {
				<!--如果当前容器 没有父级容器  就用上面我们获取到的rootContext 设置-->
					if (cwac.getParent() == null) {
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		<!--如果wac 为空 就调用findWebApplicationContext方法 具体后面聊-->
		if (wac == null) {
			wac = findWebApplicationContext();
		}
		<!--如果上面还是没有获取到 就调用createWebApplicationContext方法-->
		if (wac == null) {
			wac = createWebApplicationContext(rootContext);
		}
        
        <!--这边判断了下 是否刷新了容器  如果没有就刷新-->
		if (!this.refreshEventReceived) {
			onRefresh(wac);
		}
        
        <!--这边 和之前的Root容器一样 最终会 附加到Servlet的属性中 -->
		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}
rootContext

这个 看变量名的定义 就知道 是获取根容器的 获取的方法是: WebApplicationContextUtils.getWebApplicationContext(getServletContext());

getServletContext() 方法 HttpServletBean 中的方法,我们可以看下 是怎么做的

    @Override
	public final ServletContext getServletContext() {
		return (getServletConfig() != null ? getServletConfig().getServletContext() : null);
	}

这个方法是重写了GenericServlet中的方法,从方法中可以看出 ServletContext 是ServletConfig的一个属性,也就是说得到ServletConfig就可以得到ServletContext,其实这点 我在第一篇文章中 也聊到过 ServletContext的创建 其实就是在ServletConfig中创建的 记住 这个流程~ get一下

然后在回到getWebApplicationContext这个方法上,看下 里面是怎么实现的:

    public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
		return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
	}

ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 值就是 WebApplicationContext.class.getName() + “.ROOT”; 看的这个 其实 如果看过第二篇文章的小伙伴 或者去看源码的应该知道 ,root WebApplicationContext 被添加到ServletContext中就是用的这个属性名词,所以 这边是有关联性的。

findWebApplicationContext

这个方法 也简单 就是去寻找webApplicationContext ,从哪里找 怎么去找呢?看下代码

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

这个getContextAttribute 其实就是和我们上文中说过的getContextConfigLocation 是一样的 如果我们在servlet#init-param 中配置了contextAttribute 的话 这边 我们就能获取到值,但是 我们正常都没配置过 ,所以说 不细聊了。。

createWebApplicationContext

最后 看下创建WebApplicationContext的方法

   protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
		return createWebApplicationContext((ApplicationContext) parent);
	}
	
	protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
	    <!--这个contextClass 就是 之前上面说过的 默认是设置的是XmlWebApplicationContext-->
		Class<?> contextClass = getContextClass();
		<!--判断contextClass是否继承了ConfigurableWebApplicationContext 没有则报错-->
		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);

        <!--设置创建好的WebApplicationContext Environment,Parent,ConfigLocation  这三 都有介绍过-->
		wac.setEnvironment(getEnvironment());
		wac.setParent(parent);
		wac.setConfigLocation(getContextConfigLocation());
        
        <!--配置和刷新 新创建的web容器对象-->
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}
onRefresh

onRefresh 这个方法 是一个需要子类实现的方法 这个方法在DispatcherServlet中实现

FrameworkServlet.java
    protected void onRefresh(ApplicationContext context) {
		// For subclasses: do nothing by default.
	}
	
    在DispatcherServlet.java
    @Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}
	<!--这边做的就是 初始化话SpringMVC中的各个组件-->
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

那这个方法 调用的途径有2个地方
一种是 我们刚才代码里面看到的 如果当this.refreshEventReceived 这个是为false的时候 这个时候会条刷新方法onRefresh,refreshEventReceived这个方法默认是false,如果收到过刷新事件 就会为true

另外一种就是 我上文没有写的 记得在initWebApplicationContext中 如果webApplicationContext是存的的 并且没有激活 就会调用configureAndRefreshWebApplicationContext 这个方法

configureAndRefreshWebApplicationContext

那我们就进入这个方法 看下

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
         <!--设置容器的ID -->
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			if (this.contextId != null) {
				wac.setId(this.contextId);
			}
			else {
			    
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
			}
		}
        <!--设置容器的 相关属性 -->
		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
		<!--在web容器中添加一个监听事件 ContextRefreshListener  -->
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}

		postProcessWebApplicationContext(wac);
		applyInitializers(wac);
		wac.refresh();//Spring 容器的初始化
	}

我们看下 这个监听事件ContextRefreshListener 这个类是一个内部类

     /**
	 * ApplicationListener endpoint that receives events from this servlet's WebApplicationContext
	 * only, delegating to {@code onApplicationEvent} on the FrameworkServlet instance.
	 */
	private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

		@Override
		public void onApplicationEvent(ContextRefreshedEvent event) {
			FrameworkServlet.this.onApplicationEvent(event);
		}
	}

看注释 我们也能知道 最终接受到事件 执行的是 onApplicationEvent方法
在去看下 onApplicationEvent 代码是怎么写的

    /**
	 * Callback that receives refresh events from this servlet's WebApplicationContext.
	 * <p>The default implementation calls {@link #onRefresh},
	 * triggering a refresh of this servlet's context-dependent state.
	 * @param event the incoming ApplicationContext event
	 */
	public void onApplicationEvent(ContextRefreshedEvent event) {
		this.refreshEventReceived = true;
		onRefresh(event.getApplicationContext());
	}

看下 这个英文的注释,这边就是WebApplicationContext执行刷新refresh方法的时候的回调事件,这样 我们整个就穿起来了 也就是说 Web容器 在执行刷新的时候 回回调我们的onRefresh方法 去初始化SpringMVC中的各个组件。

结束

OK 这篇写完了 最后一篇 说下SpringMVC中的几个核心组件

当你真正的揭开SpringMVC 的时候 发现 真的不难,说白了 就是2个web容器 一个是Root容器,一般我们的Service,Dao 层的bean 会在这个里面 ,还有一个就是今天我们讲的Servlet 容器 这个里面存储了一个map 里面存储了 每个url 要执行的hander 和一些拦截器等等 其核心还是利用了Spring 的AOP 还有就是IOC 容器。

好了 不聊了 ,吃饭 ,跑步去了。端午节 也结束了~欧拉

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值