Spring-SpringMVC-原理
系列文章目录
摘要
本文简要说下SpringMVC原理,会提到DispatcherServlet等。
0x01 基本概念
先上一张转的SpringMVC运行时的请求处理流程图。
0x02 DispatcherServlet初始化
2.1 DispatcherServlet简介
Spring-IOC一文分析了WebApplicationContext
的初始化,在其初始化完成之后就是web.xml
中定义的servlet的初始化。
这里我们讲下DispatcherServlet
的初始化过程。
2.2 DispatcherServlet加载
2.2.1 WebappClassLoader
我们测试是用的Tomcat启动Web服务,所以这里贴一下Tomcat的ClassLoader层级结构。具体Tomcat加载机制可以看我这篇文章Tomcat加载机制。
回归主题,我们的DispatcherServlet.class
由Tomcat的WebappClassLoader
进行加载。下图为证:
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
(处理器映射器)等,可以按需继承实现扩展类:
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中定义的@Controller
、interceptor
、VelocityConfigurer
、VelocityLayoutViewResolver
、CommonsMultipartResolver等
。 -
Servlet XmlWebApplicationContext将Root XmlWebApplicationContext作为他的ParentContext,在getBean的时候会先尝试自己获取,如果获取不到就委托给ParentContext的BeanFactory获取;反之不行。这也就说明了为什么Controller内能引用
@Service
,@Repository
等,但反之不行的道理。
0x04 SpringMVC 过滤器 拦截器处理流程
SpringMVC的机制是由同一个Servlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的。所以过滤器、拦截器、service()方法,dispatc()方法的执行顺序应该是这样的:
0x05 SpringMVC线程安全
- Spring MVC Controller的线程安全试验
Servlet单例多线程