提出问题,思考问题,并提出自己的拙见。
1、启动根源web.xml的作用是什么?
Spring MVC 启动流程先从web.xml 说起,这是 Tomcat 服务器启动时指定加载的配置文件。有兴趣的可以研究Tomcat 源码。
2、Servlet 是Java定义的一套处理网络请求的规范,那Servlet 从哪里来?到哪里去?
3、Tomcat 捕捉到Http 事件,按Servlet 协议,只能促发方法 Service(ServletRequest,ServletResponse),那这个又是怎样被Spring 解析找到我们的Controller ->Method 的?
4、Spring MVC 启动的2个步骤 , 2个WebApplicationContext 是父子关系。
(1)启动Spring XmlWebApplicationContext 容器,加载dao、service 等业务层配置文件,注册业务层所有的Bean, 并且实例化和生成代理对象,是 父级Web Application Context, 保存在 ServletContext, 通过ServletContext 传递给子WebApplicationContext .
(2)启动Spring MVC,加载Spring MVC 的配置文件,解析Controller层,ClassPathBeanDefinitionScanner扫描指定路径,加载Controller ,解析@Controller @RequestMapping 注解,保存 path 和对应的 method 方法之间的关系。
一、web.xml 配置文件解读
1、 listener ->ContextLoaderListener ->init() 初始化, Spring 指定参数名【contextConfigLocation】需要加载解析的配置文件。解析注解,扫描指定路径,生成beanDefinition, 注册BeanDefinition, 注入bean definition, 实例化 bean definition.
2、DispatchServlet 启动,加在 Spring-MVC.xml , 扫描解析指定的Controller 路径,同上面一样的注册Bean definition逻辑。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/spring/spring-cache.xml
classpath*:/spring/spring-dao.xml
classpath*:/spring/spring-jms.xml
classpath*:/spring/spring-security.xml
classpath*:/spring/spring-service.xml
classpath*:/spring/spring-task.xml
classpath*:/spring/spring-session.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<servlet>
<servlet-name>spring-mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/spring/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>other</servlet-name>
<servlet-class>com.xx.OtherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>other</servlet-name>
<url-pattern>/other/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>spring-mvc</servlet-name>
<url-pattern>/</url-pattern> 指定“/*” 的路径 且不是 /other/* 的全部有DispatchServlet 处理。
</servlet-mapping>
二、ContextLoaderListener 启动方法是什么?干了什么事?
1、默认初始化的ApplicationContext 是 XmlWebApplicationContext (ContextLoader.properties配置的)
2、XmlWebApplicationContext 容器 和 DefaultListableBeanFactory 是一对好搭档,分工明确。
Context 里面注入了beanFactory,管理所有bean的生命循环,解析、注册、获取等;
context 保存上下文允许环境,实现容器的LifeCycle,例如配置文件路径;容器的启动、关闭、刷新等;
contextinitlized(ServletContextEvent )初始化webApplicationContext(SerletContext sc)
/**
* Initialize Spring's web application context for the given servlet context,
* using the application context provided at construction time, or creating a new one
* according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
* "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
* @param servletContext current servlet context
* @return the new WebApplicationContext
* @see #ContextLoader(WebApplicationContext)
* @see #CONTEXT_CLASS_PARAM
* @see #CONFIG_LOCATION_PARAM
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext"); 开始启动Spring 容器
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
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);父级ApplicationContext
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);//初始化容器,下图分析
}
} // key->value 保存父级容器 Root
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
/** 省略不重要的代码 **/
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
模板方法,refresh() Spring 容器构建的核心实现。
customizeContext(sc, wac);
wac.refresh();
}
3、XmlApplicationContext 类图
在父类 AbstractApplicationContext中实现 refresh(),正常流程12个步骤;异常2个步骤; 在下篇文章具体解读每个步骤是干什么。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory); 实例化所有的非延迟加载的bean
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
4、DispatchServlet 类图
(1)servlet ->init() -> initServletBean() -> 在FrameWorkServlet.initServletBean()
初始化时,解析@Controller 和@RequestMapping注解,构建<Url, MapperHandler> 类似结构
(2)实现Servlet 的接口,最终由 doDispatch(HttpServletRequest,HttpServletResponse) 方法处理。
重写Servlet 接口实现,因为拥有巨大的Bean容器->BeanFactory。
(3)怎样通过解析Request 找到对应的MapperHandler?
url ->模式匹配->从 bean容器里面寻找MapperHandler
@Override
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-mvc*.xml加载controller层定义
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | 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");
}
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);是否上传文件
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);传入Request,获取映射的handler,这就是Spring容器需要解决的问题。
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}