一般使用SpringMVC都需要在Web.xml中配置这几个参数,下面来追踪一下具体的代码逻辑;
由于web项目的启动是由Tomcat启动的, 不清楚会先调用这个类的哪一个方法,所以我找了一个看起来重要的方法打断点,再追踪它的调用栈, 可以发现在启动时由tomcat调用了contextInitialized()方法;我们主要关注initWebApplicationContext()方法;
在ContextLoaderListener的static{}代码块中发现有这么一部分代码;
ContextLoader.properties 中包含这样的一个配置,由此我们可以猜测使用的Spring容器就是
XmlWebApplicationContext
以下的代码是经过我整理之后的,只保留了主要的业务逻辑; 可以发现主要做了两件事情;
1.创建ApplicationContext;
2.初始化,完成Spring的生命周期;
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
// 创建Spring上下文
if (this.context == null) {
// 默认情况下 context=XmlWebApplicationContext;
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
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 ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//完成Spring的生命周期
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
}
继续追踪configureAndRefreshWebApplicationContext(cwac, servletContext); 同样,我对代码进行了整理;
1.获得在Web.xml中配置的属性;
2.定制上下文,我们可以自己实现ApplicationContextInitializer接口,对appcontext做功能定制;
使用方法:在web.xml中配置
3.调用refrsh()方法;----Spring的主要方法;不属于SpringMVC 这里不做分析;
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
wac.setServletContext(sc);
//获取web.xml中配置的 contextConfigLocation 属性;
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
//定制上下文
customizeContext(sc, wac);
wac.refresh();
}
至此ContextLoaderListener启动完成; 在启动过程就会解析 classpath:applicationContext.xml 完成Spring容器初始化;
DispatcherServlet 也是由Tomcat调用的,不知道会调用其中哪一个方法; 找一个看似主要的方法打断点;
可以发现由tomcat调用了init() --> initServletBean()方法;
再来追踪initWebApplicationContext; 这里有很多的判断,我只留下了主流程,更容易梳理逻辑;
protected WebApplicationContext initWebApplicationContext() {
//从ServletContext 中拿到 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 在ContextLoaderListener中加入的;
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 创建ApplicationContext
wac = createWebApplicationContext(rootContext);
}
return wac;
}
createWebApplicationContext(rootContext);方法中主要是重新创建了一个ApplicationContext 并把之前创建的 作为parent;
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
//创建父子容器;
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
//执行context.refresh()
configureAndRefreshWebApplicationContext(wac);
return wac;
}
configureAndRefreshWebApplicationContext(wac); 设置一些属性; 着重注意红圈标注的;添加了一个ContextRefresh 事件监听器,在Spring容器启动完成后会调用!
Spring容器启动完成后, 如果你对Spring的事件有所了解,最后执行到这里; 完成SpringMVC重要组件的初始化; 这里我们主要关注一个 处理器映射器, 处理器适配器的初始化;
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* May be overridden in subclasses in order to initialize further strategy objects.
* 使用事件机制,在Spring 发布 ContextRefreshedEvent事件时执行;
*/
protected void initStrategies(ApplicationContext context) {
//初始化文件上传解析
initMultipartResolver(context);
//初始化国际化相关
initLocaleResolver(context);
//初始化主题相关
initThemeResolver(context);
//初始化处理器映射器
initHandlerMappings(context);
//初始化处理器适配器
initHandlerAdapters(context);
//初始化异常解析
initHandlerExceptionResolvers(context);
//
initRequestToViewNameTranslator(context);
//视图解析器
initViewResolvers(context);
//
initFlashMapManager(context);
}
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
getDefaultStrategies(context, HandlerMapping.class); 逻辑并不复杂,
1.从配置文件中获取所有的ClassName
2.加载Class
3.ApplicationContext.createBean(Class);
4.添加到lsit
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
//按照, 分割,获取所有的ClassName
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<>(classNames.length);
for (String className : classNames) {
try {
//加载类
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
//通过ApplicationContext创建Bean
Object strategy = createDefaultStrategy(context, clazz);
//添加并返回
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Unresolvable class definition for DispatcherServlet's default strategy class [" +
className + "] for interface [" + key + "]", err);
}
}
return strategies;
}
else {
return new LinkedList<>();
}
}
String value = defaultStrategies.getProperty(key); 从这属性中获取value; 这个属性是什么时候赋值的呢? 可以发现在DispatcherServlet 静态代码块中加载了DispatcherServlet.properties文件; 而这个文件就配置了HandlerMapping, HandlerAdapter 的各种实现类;
Spring中可以作为处理器的实现方式:
1.beanName = 以/开头, 实现Controller接口
2.beanName = 以/开头,实现 HttpRequestHandler
3.beanName = 以/开头,实现 HandlerFunction
4.添加@Controller注解
实例化流程与处理器映射器相同,自行追踪;
到了这里ContextLoaderListener,DispatcherServlet 都已经实例化结束了; 觉得还缺点什么嘛?
1.我们正常使用加了@Controller注解的类 是怎么被执行到的?
2.执行的时候是怎么处理@RequestParam 等注解的?
3.执行成功后是怎么处理@ResponBody等注解的?
处理请求是调用的 DispatcherServlet.doDispatch()方法;有以下几个主要流程:
1.processedRequest = checkMultipart(request); 检查请求是否为一个文件上传
2.mappedHandler = getHandler(processedRequest); 获得处理器执行链
3.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 获得adapter
4.mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 执行adapter.handler
5.processDispatchResult(); 处理返回结果
getHandler(processedRequest); 从所有的handlerMappings中遍历去获得handler; 还记得在什么时候初始化的handlerMappings吗? 我们取其中一个 BeanNameUrlHandlerMapping 举例子;
追踪 BeanNameUrlHandlerMapping.getHandler()方法;
会调用到其父类的方法
org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
继续追踪发现是一个抽象方法,那么追踪子类的实现;
这个方法从handlerMap 中获取,可以发现 handlerMap中存的就是我们自己写的 / 开头的 Controller;
有没有很疑惑? 在哪里解析的 / 开头的名字? 又在哪里添加进去的? 如果想知道这个流程,那我们得回到实例化HandlerMapping的时候; 这里就以 BeanNameUrlHandlerMapping 举例说明;
通过继承关系图,我们可以发现实现了很多Aware接口, 结合对Spring的了解;在Spring容器初始化过程中会调用各个实现了Aware接口的方法;我们尝试一个个追踪;
ApplicationContextAware 接口有setApplicationContext 方法; 我们在BeanNameUrlMapping 类中搜索这个方法的具体实现;最终在其父类发现
org.springframework.context.support.ApplicationObjectSupport#setApplicationContext
经过之前的套路,大家知道应该主要看哪个方法吗?
initApplicationContext(context);
@Override
public final void setApplicationContext(@Nullable ApplicationContext context) throws BeansException {
if (context == null && !isContextRequired()) {
// Reset internal context state.
this.applicationContext = null;
this.messageSourceAccessor = null;
}
else if (this.applicationContext == null) {
// Initialize with passed-in context.
if (!requiredContextClass().isInstance(context)) {
throw new ApplicationContextException(
"Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]");
}
this.applicationContext = context;
this.messageSourceAccessor = new MessageSourceAccessor(context);
initApplicationContext(context);
}
else {
// Ignore reinitialization if same context passed in.
if (this.applicationContext != context) {
throw new ApplicationContextException(
"Cannot reinitialize with different application context: current one is [" +
this.applicationContext + "], passed-in one is [" + context + "]");
}
}
}
org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping#initApplicationContext
先调用super;
处理了实现 MappedInterceptor 接口的;
找到了,在这里拿到Spring中所有的bean,判断如果beanName 以 / 开头,就保存起来;
逻辑并不复杂,大家自己追踪一下吧;
老套路,追踪aware的实现方法;
追踪 org.springframework.context.ApplicationContextAware#setApplicationContext 没发现特别的逻辑;
追踪 org.springframework.web.context.ServletContextAware#setServletContext 也没啥特别的;
追踪 org.springframework.beans.factory.BeanNameAware#setBeanName 也没啥??
org.springframework.context.EmbeddedValueResolverAware#setEmbeddedValueResolver 也没有…
继续观察集成关系图,发现在Spring生命周期中,还有一个InitializingBean接口; 追踪看看;
发现了好东西;
继续追踪会发现以下方法:
processCandidateBean(beanName);
加了@Controller 或者 @RequestMapping注解的类,才会处理;
下面这个方法使用了函数接口,递归 不容易读. 可以简单理解成: 获取beanType的所有父类,循环所有的方法, 筛选出添加了@RequstMapping ,@GetMapping 等注解的方法,包装成新的类型; 并添加到集合中;
HandlerMapping的实例化完成, 再接上前面处理请求时, 获得HandlerChain的逻辑;
获取到HandlerExecutionChain 后,要去获得Adapter; 逻辑差不多,调用各个adapter.supports()方法;匹配就返回;
默认的Adapter有以下几种类型:这里主要介绍两个
1.SimpleControllerHandlerAdapter
2.RequestMappingHandlerAdapter
SimpleControllerHandlerAdapter 逻辑很简单,判断handler是否实现了Controller
RequestMappingHandlerAdapter 比较复杂,下面是类继承关系图;
追踪一下这些方法做了什么事情;
1.处理了@ControllerAdvice
2.初始化了参数解析器; 在前端传来参数时,可以解析@RequestParam,@RequestBody,@RequestHeader等注解
3…不知道
4.初始化了返回结果处理器; 在执行完方法返回时;可以处理@ResponBody等注解;
5.我们经常使用的返回json数据,当然也是在这里初始化的; 当我们实例化RequestMappingHandlerAdapter时,在构造方法中初始化了MessageConverter;并且会依据我们是否加入
某些jar包,添加更多的消息转换器;
主要跟踪RequestMappingHandlerAdapter 的执行逻辑,其他的Adapter就是执行固定的接口方法;
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal()