提出问题,思考问题,并提出自己的拙见。
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); } } } }