Spring-SpringMVC-原理

Spring-SpringMVC-原理

系列文章目录

  1. Spring-IOC
  2. Spring-SpringMVC-原理
  3. Spring-拦截器
  4. Spring-SpringMVC-全局异常处理

摘要

本文简要说下SpringMVC原理,会提到DispatcherServlet等。

0x01 基本概念

SpringMVC运行流程图
先上一张转的SpringMVC运行时的请求处理流程图。

0x02 DispatcherServlet初始化

2.1 DispatcherServlet简介

DispatcherServlet
Spring-IOC一文分析了WebApplicationContext的初始化,在其初始化完成之后就是web.xml中定义的servlet的初始化。

这里我们讲下DispatcherServlet的初始化过程。

2.2 DispatcherServlet加载

2.2.1 WebappClassLoader

我们测试是用的Tomcat启动Web服务,所以这里贴一下Tomcat的ClassLoader层级结构。具体Tomcat加载机制可以看我这篇文章Tomcat加载机制
Tomcat
回归主题,我们的DispatcherServlet.class由Tomcat的WebappClassLoader进行加载。下图为证:
DispatcherServlet.class

2.2.2 DispatcherServlet.<clinit>
// org.springframework.web.servlet.DispatcherServlet.CONTEXT
// 放在request中的WebApplicationContext中的Key
public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT";
// 默认配置所在配置文件
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
// 默认配置
private static final Properties defaultStrategies;

static {
	// 从org/springframework/web/servlet/DispatcherServlet.properties加载默认的实现类
	try {
		ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
		defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
	}
	catch (IOException ex) {
		throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
	}
}

主要就是加载DispatcherServlet用到的一些默认实现类,比如ViewResolver(视图解析器),HandlerAdapter(Handler适配器),HandlerMapping(处理器映射器)等,可以按需继承实现扩展类:
DispatcherServletProperties

2.2.3 DispatcherServlet.<init>

DispatcherServlet.<init>方法主要是声明了一些对象属性,如

// 处理器映射器list
private List<HandlerMapping> handlerMappings;

// 处理器适配器list
private List<HandlerAdapter> handlerAdapters;

// 视图解析器list
private List<ViewResolver> viewResolvers;

// 而构造方法其实没啥内容,但会调用父类FrameworkServlet构造器
public DispatcherServlet() {
	super();
}
2.2.4 FrameworkServlet
// 很重要的实例变量webApplicationContext
private WebApplicationContext webApplicationContext;
// 当前ServletContext中的存放独有的WebApplicationContext的属性名
private String contextAttribute;
public FrameworkServlet() {
}

2.3 子WebApplicationContext创建

2.3.1 FrameworkServlet.initServletBean

由Web容器触发org.springframework.web.servlet.HttpServletBean.init()方法,会调用FrameworkServlet.initServletBean()方法,核心代码如下:

protected final void initServletBean() throws ServletException {
	// 初始化Servlet专用的WebApplicationContext
	this.webApplicationContext = initWebApplicationContext();
	// 
	initFrameworkServlet();
}
2.3.2 FrameworkServlet.initWebApplicationContext
protected WebApplicationContext initWebApplicationContext() {
	// 从ServletContext中获取root WebApplicationContext,
	// 就是我们Spring-IOC文章中提到的从applicationContext.xml中创建的那个
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
			
	// 当前Servlet的WebApplicationContext
	WebApplicationContext wac = null;

	if (this.webApplicationContext != null) {
	// 这个分支是已经有有context实例在构建时被注入了,直接使用它
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			if (!cwac.isActive()) {
			// context还未refresh
				if (cwac.getParent() == null) {
					// 此时把rootContext设为该context的父context
					cwac.setParent(rootContext);
				}
				// 然后进行一些初始化配置,并执行refresh
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	if (wac == null) {
		// 尝试从当前servlet中查找root WebApplicationContext
		wac = findWebApplicationContext();
	}
	if (wac == null) {
		// 没有在当前servlet配置context,就创建个新的context
		// 1.具体是获取XmlWebApplicationContext的构造方法,反射方式newInstance
		// 2.随后将当前rootContext设为新建的wac的父context
		// 3.最后执行configureAndRefreshWebApplicationContext(wac)
		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;
}
2.3.3 FrameworkServlet.configureAndRefreshWebApplicationContext
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 {
		// 我们初次分析此方法时,contextId=null,走这个分支
		
			// Generate default id...
			// 获取servletContext
			ServletContext sc = getServletContext();
			if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
				// servlet版本小于2.5...
			}
			else {
				// Servlet 2.5
				// 可把id给wac
				// org.springframework.web.context.WebApplicationContext:/spring-web
				// spring-web是我们配置的mapping为 / 的servlet
				// 在WEB-INF下面有spring-web-servlet.xml,定义controller scan
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()) + "/" + getServletName());
			}
		}
	}

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

	// wac的环境(StandardServletEnvironment)中的initPropertySources方法
	// 会在context refreshed时的所有场景中被调用,
	// 所以这里我们提前调用,以确保servlet属性被正确放置,
	// 这样就能随时可对后处理或发生在refresh之前的初始化操作可用
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
	}
	// 默认为空的后置处理方法,可在子类重写
	postProcessWebApplicationContext(wac);
	applyInitializers(wac);
	// 触发wac refresh事件,进行一系列context初始化工作,可参阅
	// https://blog.csdn.net/baichoufei90/article/details/86421335#refresh
	wac.refresh();
}

wac.refresh之后,会初始化一系列bean,一般包括类似web-demo/src/main/webapp/WEB-INF/spring-web-servlet.xml中定义的@Controller、interceptor、VelocityConfigurer、VelocityLayoutViewResolver、CommonsMultipartResolver等。

2.4 DispatcherServlet.initStrategies

该阶段会进行默认实现类的赋值。

2.4.1 refresh事件分发

AbstractApplicationContext.finishRefresh中会执行publishEvent(new ContextRefreshedEvent(this)),将对ContextRefreshedEvent事件进行分发。会调用SimpleApplicationEventMulticaster.multicastEvent

public void multicastEvent(final ApplicationEvent event) {
// 这里就会遍历所有继承了如ApplicationListener<ContextRefreshedEvent>
// 这样的Listener
for (final ApplicationListener listener : getApplicationListeners(event)) {
	Executor executor = getTaskExecutor();
	if (executor != null) {
		executor.execute(new Runnable() {
			public void run() {
				listener.onApplicationEvent(event);
			}
		});
	}
	else {
	    // 触发相应事件
		listener.onApplicationEvent(event);
	}
}
}
2.4.2 DispatcherServlet.onRefresh

跟这代码,会到达这个方法:

protected void onRefresh(ApplicationContext context) {
    // 初始化策略
	initStrategies(context);
}
2.4.4 DispatcherServlet.initStrategies
// 使用context初始化策略
protected void initStrategies(ApplicationContext context) {
	initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);
	initHandlerMappings(context);
	initHandlerAdapters(context);
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}
2.4.5 DispatcherServlet.initMultipartResolver

我们这里以initMultipartResolver为例子进行分析,其他就不展开了。

请注意,这里传入的context使我们DispatcherServlet所创建的那个子Context,他拥有父Context。

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

0x03 RootContext和ServletContext关系

  • Root XmlWebApplicationContext最先创建,一般会初始化非Controller的类,类似以下applicationContext.xml配置:

    <context:component-scan base-package="com.chengc.demos.web.demo1.*">
          <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    
  • Servlet XmlWebApplicationContext会初始化一系列bean,一般包括类似web-demo/src/main/webapp/WEB-INF/spring-web-servlet.xml中定义的@ControllerinterceptorVelocityConfigurerVelocityLayoutViewResolverCommonsMultipartResolver等

  • Servlet XmlWebApplicationContext将Root XmlWebApplicationContext作为他的ParentContext,在getBean的时候会先尝试自己获取,如果获取不到就委托给ParentContext的BeanFactory获取;反之不行。这也就说明了为什么Controller内能引用@Service,@Repository等,但反之不行的道理。

0x04 SpringMVC 过滤器 拦截器处理流程

SpringMVC的机制是由同一个Servlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的。所以过滤器、拦截器、service()方法,dispatc()方法的执行顺序应该是这样的:
在这里插入图片描述
在这里插入图片描述

0x05 SpringMVC线程安全

参考文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值