spring mvc之启动过程源码分析
- 简介
这两个星期都在看spring mvc源码,看来看去还是还是很多细节没了解清楚,在这里把看明白的记录下,欢迎在评论中一起讨论。
一、铺垫
spring mvc是基于servlet的,在正式分析之前,我们来看一下servlet的知识。servlet的生命周期通过三个方法init、service、destory来构建的。
- init():
在Servlet的生命周期中,仅执行一次init()方法。它是在服务器装入Servlet时执行的,负责初始化Servlet对象。可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。无论有多少客户机访问Servlet,都不会重复执行init()。
- service():
它是Servlet的核心,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,该对象的Service()方法就要调用,而且传递给这个方法一个“请求”(ServletRequest)对象和一个“响应”(ServletResponse)对象作为参数。在HttpServlet中已存在Service()方法。默认的服务功能是调用与HTTP请求的方法相应的do功能。
- destroy():
仅执行一次,在服务器端停止且卸载Servlet时执行该方法。当Servlet对象退出生命周期时,负责释放占用的资源。一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确认在调用destroy()方法时,这些线程已经终止或完成。
二、spring mvc启动过程
spring mcv的入口是DispatcherServlet,顾名思义就是调度servlet是服务请求调度,它的继承结构如下:
在整合spring mvc时,web.xml有这样配置,相信用过的都知道
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc-dispatcher.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
那根据servlet的知识,在服务器启动,创建dispatcherServlet对象,会执行init方法,根据DispaterServlet的继承关系,找到init方法在HttpServletBean中,下面我们来看一下这个
方法
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
} // Set bean properties from init parameters.
try {
/*PropertyValuesz是封装在web.xml配置servlet参数信息
* <init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc-dispatcher.xml</param-value>
</init-param>
*/
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw);
//将配置的初始化值设置到DispatcherServlet
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
} //初始化spring mvc容器方法webApplicationContext,由子类的FrameworkServlet来实现
initServletBean(); if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
2.1、接下来看FrameworkServlet的initServletBean(),主要调了initWebApplicationContext()方法
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 {
//初始化spring mcv容器
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");
}
}
2.2、initWebApplicationContext方法
protected WebApplicationContext initWebApplicationContext() {
/*
在spring启动过程中,ContextLoaderListener回监听到,实例化IoC容器,并将此容器实例注册到ServletContext中,现在把IOC容器取出来
*/
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null; if (wac == null) {
//把IOC容器传进去,创建spring mvc自己的容器
wac = createWebApplicationContext(rootContext);
}
return wac;
if (this.publishContext) { String attrName = getServletContextAttributeName();
//把创建好的spring mcv自己的容器设置到ServletContext容器中
getServletContext().setAttribute(attrName, wac); }
}
2.3、createWebApplicationContext方法
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
/*根据web.xml中的配置决定使用何种WebApplicationContext。默认情况下使用XmlWebApplicationContext
web.xml中相关的配置context-param的名称“contextClass” */
Class<?> contextClass = getContextClass();
//获得spring mvc自己容器
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment());
//指定父容器为IOC容器
wac.setParent(parent);
//指定spring mcv 核心配置文件的位置classpath:mvc-dispatcher.xml
wac.setConfigLocation(getContextConfigLocation());
//spring mcv 容器继续初始化
configureAndRefreshWebApplicationContext(wac); return wac;
}
2.4、配置和刷新spring mvc自己容器方法configureAndRefreshWebApplicationContext(wac),就是针对spring mcv容器对象一些属性,然后初始化
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
//设置ServletContext
wac.setServletContext(getServletContext());
//设置ServletConfig
wac.setServletConfig(getServletConfig()); postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh(); }
2.5、接下来,我们看看刷新方法,做了什么事
public void refresh() throws BeansException, IllegalStateException {
//完成Bean工厂初始化
finishBeanFactoryInitialization(beanFactory);
//完成刷新spring mvc自己的容器功能
finishRefresh();
}
在这里主要看完成BeanFactory初始化工作
2.6、接下来我们看看初始化BeanFactory工作finishBeanFactoryInitialization,主要看到实例化单例对象
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
//实例化单例对象
beanFactory.preInstantiateSingletons();
}
2.7、接下来实例化单例对象方法preInstantiateSingletons()
public void preInstantiateSingletons() throws BeansException {
//获得所有定义好beanNames名称
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
else {
getBean(beanName);
}
}
} for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
smartSingleton.afterSingletonsInstantiated();
return null;
}
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
}
这个方法beanNames有以下
是通过getBean(beanName)来实例化对象的,在这里我主要讲spring mvc实例化RequestMappingHandlerMapping做了什么事,不要急,我们先来看一下,RequestMappingHandlerMapping的继承结构
可以看到RequestMappingHandleMapping继承了AbstractHandlerMethodMapping,在初始化的时候,会执行到AbstractHandlerMethodMapping的构造方法,看看
AbstractHandlerMethodMapping这个
实现了InitializingBean,所以这个类在初始化完成时会执行到afterPropertiesSet()方法
2.8、接下来我们看看initHandlerMethods方法到底做了什么事
protected void initHandlerMethods() {
//获得所有的beanNames
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
//遍历取出每个beanName
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
//获得BeanName对应的Class对象
beanType = getApplicationContext().getType(beanName);
}
//判断这个Class时候否有@Controller和@RequestMapping()
if (beanType != null && isHandler(beanType)) {
//如果类上含有@Controller和@RequestMapping()注解,执行探测处理器方法
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
2.9、接下来我们看看detectHandlerMethods(beanName)这个方法到底干了什么事
protected void detectHandlerMethods(final Object handler) {
//传进来的handle是个字符串类型的BeanName,首先根据Bean名称创建Class对象
Class<?> handlerType = (handler instanceof String ?
getApplicationContext().getType((String) handler) : handler.getClass());
final Class<?> userType = ClassUtils.getUserClass(handlerType);
//获得Controller类对应methods对应的Map
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
new MethodIntrospector.MetadataLookup<T>() {
@Override
public T inspect(Method method) {
return getMappingForMethod(method, userType);
}
}); //遍历Map,取出Controller类的每一个方法和对应的Mapping
for (Map.Entry<Method, T> entry : methods.entrySet()) {
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
下图是Map<Method, T> methods的值
可以看到T封装了Controller类的请求方式,和匹配条件等等数据
2.10、接下来我们来看看spring mvc是怎么注册Controller方法
registerHandlerMethod(handler, invocableMethod, mapping)
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
2.11、接下来我们看看mappingRegistry的register方法
public void register(T mapping, Object handler, Method method) {
//拿到写锁
this.readWriteLock.writeLock().lock();
try {
//根据handler(是你的controller类的类名)和controller的method方法创建handlerMethod对象
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
//存储到名称为mappingLookup的Map中
this.mappingLookup.put(mapping, handlerMethod);
//然后从mapping中取出请求url
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
//然后我们再把url和mapping存储到MultiValueMap类型的urlLookup中,所以我们可以通过url找到mapping,再通过mapping找到handleMethod
this.urlLookup.add(url, mapping);
}
//把mapping和appingRegistration存储到HashMap类型的registry中
this.registry.put(mapping, new AbstractHandlerMethodMapping.MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
}
finally {
//释放写锁
this.readWriteLock.writeLock().unlock();
}
}
这里解释下传进来3个参数 mapping封装了匹配条件,请求方式等数据,handler是我们的controller类名(字符串),method(controller类的方法对象)
2.12、接下来我们回到的2.8的initHandlerMethods方法
protected void initHandlerMethods() {
//获得所有的beanNames
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
//遍历取出每个beanName
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
//获得BeanName对应的Class对象
beanType = getApplicationContext().getType(beanName);
}
//判断这个Class时候否有@Controller和@RequestMapping()
if (beanType != null && isHandler(beanType)) {
//如果类上含有@Controller和@RequestMapping()注解,执行探测处理器方法 detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
执行完了detectHandlerMethods(beanName);方法,继续往下执行handlerMethodsInitialized(getHandlerMethods())方法,这个方法是个 是个模板 方法,什么都没执行,到这里
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
}
afterPropertiesSet方法就算执行完了。
2.13、接下来执行继续回到2.7的实例化单例对象方法preInstantiateSingletons();
接下没什么什么好说,preInstantiateSingletons()结束后,finishBeanFactoryInitialization()方法也就结束了,回到了refresh(),继续完成刷新spring mvc自己的容器功能
public void refresh() throws BeansException, IllegalStateException {
//完成Bean工厂初始化
finishBeanFactoryInitialization(beanFactory);
//完成刷新spring mvc自己的容器功能
finishRefresh();
}
2.14、finishRefresh();这一步会触发一个ApplicationEvent:
protected void finishRefresh() {
//其中this是指XmlWebApplicationContext对象
publishEvent(new ContextRefreshedEvent(this));
2.15、接下来解析初始化DispatcherServlet类的各种成员变量
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
//初始化handlerMappings
initHandlerMappings(context);
//handlerAdapters
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
//初始化视图解析器
initViewResolvers(context);
initFlashMapManager(context);
}
2.16、接下来回到2.1的FrameworkServlet的initServletBean()方法中,然后initServletBean()执行完之后,HttpServletBean中的init初始化方法也就执行完了,到这里spring mvc启动的源码已经分析完成,
希望你看会有收获。
三、总结
其实分析完spring mvc自己启动的源码会发现,大部分内容都在构建spring mcv自己的容器,构建完成之后将容器放到SevletContext中
spring mvc之启动过程源码分析的更多相关文章
- Spring Boot与Spring MVC集成启动过程源码分析
开源项目推荐 Pepper Metrics是我与同事开发的一个开源工具(https://github.com/zrbcool/pepper-metrics),其通过收集jedis/mybatis/ht ...
- spring mvc之请求过程源码分析
简介 上一篇,我们分析了spring mvc启动过程的源码,这一节,来一起分析下在用户请求controller的过程中,spring mvc做了什么事? 一.准备 我写这么一个controller p ...
- 【Spring boot】启动过程源码分析
启动过程结论 推测web应用类型. spi的方式获取BootstrapRegistryInitializer.ApplicationContextInitializer.ApplicationCont ...
- Spring启动过程源码分析基本概念
Spring启动过程源码分析基本概念 本文是通过AnnotationConfigApplicationContext读取配置类来一步一步去了解Spring的启动过程. 在看源码之前,我们要知道某些类的 ...
- Spring IOC容器启动流程源码解析(四)——初始化单实例bean阶段
目录 1. 引言 2. 初始化bean的入口 3 尝试从当前容器及其父容器的缓存中获取bean 3.1 获取真正的beanName 3.2 尝试从当前容器的缓存中获取bean 3.3 从父容器中查找b ...
- Android系统默认Home应用程序(Launcher)的启动过程源码分析
在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还须要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应 ...
- Android Content Provider的启动过程源码分析
本文參考Android应用程序组件Content Provider的启动过程源码分析http://blog.csdn.net/luoshengyang/article/details/6963418和 ...
- Flume-NG启动过程源码分析(二)(原创)
在上一节中讲解了——Flume-NG启动过程源码分析(一)(原创) 本节分析配置文件的解析,即PollingPropertiesFileConfigurationProvider.FileWatch ...
- 10.4 android输入系统_框架、编写一个万能模拟输入驱动程序、reader/dispatcher线程启动过程源码分析
1. 输入系统框架 android输入系统官方文档 // 需FQhttp://source.android.com/devices/input/index.html <深入理解Android 卷 ...
随机推荐
- nginx无法启动异常
Nginx安装过程中可能会报如下错误: /usr/local/nginx/sbin/nginx -t /usr/local/nginx/sbin/nginx: error while loading ...
- Windbg+Procdump解决w3wp.exe CPU过百问题
最近发布在windows server2012 IIS8.0上的一个WebAPI项目,才几十个人在线,CPU就会出现过百情况,并且CPU一旦过百应用程序池就自动暂停掉,看到这个问题我感觉应该是程序 ...
- (麻省理工免费课程)C语言内存管理和C++面向对象编程
此课程有全部讲义和习题. 课程描述实在得令人发指.翻译如下: 您是否由于自己的Python程序比同僚们的C程序慢而垂头丧气?你是否想不用JAVA实现面向对象?加入我们,学习C和C++吧!我们带您从简单 ...
- org.hibernate.exception.ConstraintViolationException: could not insert:
org.hibernate.exception.ConstraintViolationException: could not insert: 报错原由于xxx.hbm.xml文件里的主键类型设置有问 ...
- Desugar Scala(17) -- Option和for,以及脑子里发生的事情
欢迎关注我的新博客地址:http://cuipengfei.me/blog/2014/08/30/options-for/ Scala里的forkeyword是个非常有趣的东西. 能够用来把多层嵌套f ...
- 如何利用IPv6进行远程桌面连接
如何利用IPv6进行远程桌面连接 学校是教育网,其中寝室和实验室的IPv4地址被划分成了两个VLAN,所以没法使用windows的远程连接功能.今天突然想到学校的IPv6地址可能并未划分成两个VLAN ...
- Google Mesa概览
Google Mesa的文章:https://research.google.com/pubs/pub42851.html https://gigaom.com/2014/08/07/google- ...
- 如何使用ODBC搭配dsn链接数据库
{ OdbcConnection cn; OdbcCommand cmd; string MyString; MyString="Select * from Customers"; ...
- 基于Docker的负载均衡和服务发现
应用的容器化和微服务化带来的问题 在缺省网络模型中,容器每次重启后,IP会发生变动,在一个大的分布式系统保证IP地址不变是比较复杂的事情 IP频繁发生变动,动态应用部署无法预知容器的IP地址,clie ...
- hdoj 1874 畅通project续【SPFA】
畅通project续 Time Limit : 3000/1000ms (Java/Other) Memory Limit : 32768/32768K (Java/Other) Total Su ...