Spring版本 4.3.2,ssm框架java
代码过宽,能够shift + 鼠标滚轮 左右滑动查看web
<!--配置获取项目的根路径,java类中使用System.getProperty("web.root")--> <context-param> <param-name>webAppRootKey</param-name> <param-value>web.root</param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.util.WebAppRootListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
IOC容器初始化从ContextLoaderListener类开始,这个监听器是ServletContextListener的子类,在web.xml配置后会被容器(为避免和IOC容器混淆,后文用tomcat代称)调用。该类继承关系以下:spring
tomcat初始化ContextLoaderListener类时会先调用其父类ContextLoader的静态代码块数据库
static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. // 加载属性文件默认策略的实现,当前处于彻底内部环境,不能由开发者去自定义 try { // DEFAULT_STRATEGIES_PATH 常量,指向该类同一目录层级下的ContextLoader.properties文件 ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class); //用流读取文件中的key-value对,在这个地方肯定默认上下文的Class defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage()); } }
ContextLoader.properties文件中内容:数组
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
默认策略读取完成,此策略肯定未指定上下文时的默认类型,为XmlWebApplicationContext。tomcat
若是web.xml中有配置WebAppRootListener监听器,根据web.xml中的顺序,先调用WebAppRootListener的contextInitialized方法。app
public void contextInitialized(ServletContextEvent event) { WebUtils.setWebAppRootSystemProperty(event.getServletContext()); } public static void setWebAppRootSystemProperty(ServletContext servletContext) throws IllegalStateException { Assert.notNull(servletContext, "ServletContext must not be null"); //获取项目在主机上的完整路径root String root = servletContext.getRealPath("/"); if (root == null) { throw new IllegalStateException( "Cannot set web app root system property when WAR file is not expanded"); } //WEB_APP_ROOT_KEY_PARAM为常量:webAppRootKey。在web.xml中获取key为webAppRootKey的value值 String param = servletContext.getInitParameter(WEB_APP_ROOT_KEY_PARAM); //DEFAULT_WEB_APP_ROOT_KEY为常量:webapp.root,如没有指定WEB_APP_ROOT_KEY_PARAM则取此常量 String key = (param != null ? param : DEFAULT_WEB_APP_ROOT_KEY); //无论拿的是DEFAULT_WEB_APP_ROOT_KEY仍是WEB_APP_ROOT_KEY_PARAM,以其value做为key,找到对应的root String oldValue = System.getProperty(key); //确保root惟一 if (oldValue != null && !StringUtils.pathEquals(oldValue, root)) { throw new IllegalStateException( "Web app root system property already set to different value: '" + key + "' = [" + oldValue + "] instead of [" + root + "] - " + "Choose unique values for the 'webAppRootKey' context-param in your web.xml files!"); } //设置root System.setProperty(key, root); servletContext.log("Set web app root system property: '" + key + "' = [" + root + "]"); }
WebAppRootListener监听器初始化任务结束,而后调用ContextLoaderListener监听器的contextInitialized方法框架
/** * Initialize the root web application context. * * 初始化根上下文 */ @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }
initWebApplicationContext方法在ContextLoaderListener的父类ContextLoader中webapp
/** * 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. * * 经过给定的 servlet context 初始化 Spring 的 web application context, * 这个 application context 要么是在构造时被提供,要么是根据 contextClass * 和 contextConfigLocation 两个上下文参数从新建立的。 */ public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { // servlet context 中只能有一个 root web application context。 // root web application context 初始化完成后会放入 servlet context 中 if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { // 不能初始化 context ,由于已经有一个 root web application context存在 // 检查你是否在你的web.xml中定义了多个ContextLoader 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); // 初始化 spring root WebApplicationContext servletContext.log("Initializing Spring root WebApplicationContext"); if (logger.isInfoEnabled()) { // Root WebApplicationContext 初始化开始 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. // 在这个实例变量中保存 context, // 以确保在 ServletContext 关闭时可以用到 if (this.context == null) { // 1.建立 WebApplicationContext this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; // 默认 WebApplicationContext 没有激活 if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc // 此时 context 尚未被刷新 -> 提供设置 parent context 、application context id // 等服务。 if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> // determine parent for root web application context, if any. // 2.这个 context 实例被注入时没有一个明确的 parent -> 若是有的话, // 须要为 root web application context 肯定 parent ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } // 3.配置并刷新 WebApplicationContext(超级巨大的方法) configureAndRefreshWebApplicationContext(cwac, servletContext); } } //将 root web application context 添加到 servletContext 中 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); } if (logger.isDebugEnabled()) { logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); } if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); } return this.context; } catch (RuntimeException ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } catch (Error err) { logger.error("Context initialization failed", err); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); throw err; } }
从标1的方法开始,这个方法用来建立 root web application context ,并用ContextLoader的context变量接收ide
// 1.建立 WebApplicationContext this.context = createWebApplicationContext(servletContext); // 看下ContextLoader类的context属性 /** * The root WebApplicationContext instance that this loader manages. * * 由ContextLoader管理的 root web application context. */ private WebApplicationContext context; /** * Instantiate the root WebApplicationContext for this loader, either the * default context class or a custom context class if specified. * <p>This implementation expects custom contexts to implement the * {@link ConfigurableWebApplicationContext} interface. * Can be overridden in subclasses. * <p>In addition, {@link #customizeContext} gets called prior to refreshing the * context, allowing subclasses to perform custom modifications to the context. * * 为这个 loader 实例化 root WebApplicationContext , * 要用采用默认的 context class,要么使用指定的自定义的 context class。 * 对于自定义的 contexts class 来讲, * 但愿实现 ConfigurableWebApplicationContext 接口,也能够重写他的子类。 * 另外,customizeContext方法 会在 context 被刷新前调用, * 容许子类执行对 context 的自定义修改。 */ protected WebApplicationContext createWebApplicationContext(ServletContext sc) { // 肯定 Context Class Class<?> contextClass = determineContextClass(sc); // 确保自定义 context 是 ConfigurableWebApplicationContext 的子类 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } // 实例化 root WebApplicationContext return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); } /** * Return the WebApplicationContext implementation class to use, either the * default XmlWebApplicationContext or a custom context class if specified. * * 返回 WebApplicationContext 接口的实现, * 要么使用默认的 XmlWebApplicationContext ,要么使用自定义的 context */ protected Class<?> determineContextClass(ServletContext servletContext) { // CONTEXT_CLASS_PARAM -> contextClass // 若是web.xml中配置了contextClass这个属性,那么就取自定义context Class String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName != null) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load custom context class [" + contextClassName + "]", ex); } } else { // 若是没有配置自定义 context Class, // 则取默认策略中的 context Class,也就是 XmlWebApplicationContext contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); try { //实例化返回 return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load default context class [" + contextClassName + "]", ex); } } }
这样标1的方法也就执行完了。看一下XmlWebApplicationContext的继承关系:
跟踪标记2的方法。
此方法在ContextLoader类中实现。
// 2.这个 context 实例被注入时没有一个明确的 parent -> 若是有的话, // 须要为 root web application context 肯定 parent ApplicationContext parent = loadParentContext(servletContext); /** * Template method with default implementation (which may be overridden by a * subclass), to load or obtain an ApplicationContext instance which will be * used as the parent context of the root WebApplicationContext. If the * return value from the method is null, no parent context is set. * <p>The main reason to load a parent context here is to allow multiple root * web application contexts to all be children of a shared EAR context, or * alternately to also share the same parent context that is visible to * EJBs. For pure web applications, there is usually no need to worry about * having a parent context to the root web application context. * <p>The default implementation uses * {@link org.springframework.context.access.ContextSingletonBeanFactoryLocator}, * configured via {@link #LOCATOR_FACTORY_SELECTOR_PARAM} and * {@link #LOCATOR_FACTORY_KEY_PARAM}, to load a parent context * which will be shared by all other users of ContextsingletonBeanFactoryLocator * which also use the same configuration parameters. * * 一个默认实现的模板方法(可能被子类覆盖), * 用来加载和得到一个 ApplicationContext 的实例,这个实例被用来 * 做为 root WebApplicationContext 的 parent context 。 * 若是此方法返回的是null,那么 root WebApplicationContext 就没有 parent context 。 * 加载 parent context 的主要缘由是为了让多个 root web application contexts * 成为共享 EAR context 的 children, * 或者共享一个对 EJBs 可见的 parent context。 * 为了web applications 的纯洁性,一般不须要关心 root web application context 的 parent context。 * 默认实现使用 ContextSingletonBeanFactoryLocator,经过两个参数去配置,去加载一个被全部 * ContextsingletonBeanFactoryLocator 其余用户所共享的 parent context,它们也使用同样的配置参数。 */ protected ApplicationContext loadParentContext(ServletContext servletContext) { ApplicationContext parentContext = null; String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM); String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM); // 这里没有配置这两个参数,因此此 context 没有 parent if (parentContextKey != null) { // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml" BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector); Log logger = LogFactory.getLog(ContextLoader.class); if (logger.isDebugEnabled()) { logger.debug("Getting parent context definition: using parent context key of '" + parentContextKey + "' with BeanFactoryLocator"); } this.parentContextRef = locator.useBeanFactory(parentContextKey); parentContext = (ApplicationContext) this.parentContextRef.getFactory(); } return parentContext; }
跟踪标记3的方法。
此方法在ContextLoader类中实现。
// 3.配置并刷新 WebApplicationContext(超级巨大的方法) configureAndRefreshWebApplicationContext(cwac, servletContext); protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { // 在类AbstractApplicationContext中,定义了 context 的id // private String id = ObjectUtils.identityToString(this); // id是 context 的惟一标识 // identityToString 方法返回的是 context 的类名 + "@" + 十六进制的哈希值 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 // 这个 application context id 仍然设置为他的初始默认值-->基于可利用的信息分配给他一个更有用的id // 若是 servlet context 中设置了 CONTEXT_ID_PARAM -> contextId属性, // 那么就采用这个做为id String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { // Generate default id... // 不然生成默认的id // "org.springframework.web.context.WebApplicationContext:" + 项目名 wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } } wac.setServletContext(sc); // CONFIG_LOCATION_PARAM -> contextConfigLocation, // 从sevletContext中拿的这个属性,就是咱们常常在web.xml中配置的属性: // classpath:spring/applicationContext.xml String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam != null) { // 以数组的形式将 contextConfigLocation 保存在 // root web application context 的 configLocations 属性中, // 保存时会先对路径进行解析,替换占位符,而这个操做须要 Environment 对象来完成。 // context 中有属性 environment,environment 默认为 StandardServletEnvironment 实现, // 实例化 StandardServletEnvironment 时其父类 AbstractEnvironment // 会调用 customizePropertySources 方法, // 这个方法会将 systemEnvironment、systemProperties 啥的键值对以及jndiProperties保存在实例中, // 后续还会将 servletContextInitParams 、servletConfigInitParams 等属性保存进来 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 // 当 context 被刷新时,wac 的 environment 的 initPropertySources 方法在任何状况下都会被调用。 // 之因此这么急切的调用是为了确保 servlet 属性源在一些 post-processing 中或者发生在refresh方法 // 以前的初始化中可以到位使用。 ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { //将 servletContextInitParams、servletConfigInitParams 等属性保存进 environment 对象中 ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); } // 3.1对 Context 的自定义操做 customizeContext(sc, wac); // 3.2整个IOC的重点,内容很是多,里面的每一个方法都会分一篇文章单独讲 wac.refresh(); }
跟踪标记3.1的方法。
此方法在ContextLoader类中实现。
// 3.1对上下文的自定义操做 customizeContext(sc, wac); /** * Customize the {@link ConfigurableWebApplicationContext} created by this * ContextLoader after config locations have been supplied to the context * but before the context is <em>refreshed</em>. * <p>The default implementation {@linkplain #determineContextInitializerClasses(ServletContext) * determines} what (if any) context initializer classes have been specified through * {@linkplain #CONTEXT_INITIALIZER_CLASSES_PARAM context init parameters} and * {@linkplain ApplicationContextInitializer#initialize invokes each} with the * given web application context. * <p>Any {@code ApplicationContextInitializers} implementing * {@link org.springframework.core.Ordered Ordered} or marked with @{@link * org.springframework.core.annotation.Order Order} will be sorted appropriately. * * 当 config locations 被提供给 context 后, * 由此 ContextLoader 建立的 ConfigurableWebApplicationContext 能够进行自定义化, * 可是必须在 context 被刷新以前进行。 * 这个默认的实现,determineContextInitializerClasses 方法, * 经过两个参数以及给定的 web application context 肯定了 context initializer classes,这两个参数分别是 * CONTEXT_INITIALIZER_CLASSES_PARAM(上下文初始化参数)和ApplicationContextInitializer(调用每个)。 * 任何实现了 Ordered 或者有 Order 注解的 ApplicationContextInitializers 都将被适当的排序 */ protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) { // 3.1.1web.xml中没有自定义参数,因此此处返回空list List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(sc); // 没有自定义操做,因此跳过。有则实例化 Initializer for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) { Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class); if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) { throw new ApplicationContextException(String.format( "Could not apply context initializer [%s] since its generic parameter [%s] " + "is not assignable from the type of application context used by this " + "context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(), wac.getClass().getName())); } this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass)); } // 若是有多个 contextInitializers,会根据 Order 作一个排序 AnnotationAwareOrderComparator.sort(this.contextInitializers); for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) { // contextInitializers 对 context 进行初始化 initializer.initialize(wac); } }
跟踪标记3.1.1的方法。
此方法在ContextLoader类中实现。
// 3.1.1web.xml中没有定义,因此此处返回空list List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(sc); /** * Return the {@link ApplicationContextInitializer} implementation classes to use * if any have been specified by {@link #CONTEXT_INITIALIZER_CLASSES_PARAM}. * * 若是指定了 CONTEXT_INITIALIZER_CLASSES_PARAM 参数, * 将会返回 ApplicationContextInitializer 接口的实现类的Class对象以供使用 */ protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> determineContextInitializerClasses(ServletContext servletContext) { List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes = new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>(); //是否有自定义GLOBAL_INITIALIZER_CLASSES_PARAM参数,没有跳过 String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM); if (globalClassNames != null) { for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) { classes.add(loadInitializerClass(className)); } } //是否有自定义CONTEXT_INITIALIZER_CLASSES_PARAM参数,没有跳过 String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM); if (localClassNames != null) { for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) { classes.add(loadInitializerClass(className)); } } //web.xml中没有定义,因此此处返回空list return classes; }
跟踪标记3.2的方法。
此方法在AbstractApplicationContext类中实现。
// 3.2刷新操做 wac.refresh(); /** * Load or refresh the persistent representation of the configuration, * which might an XML file, properties file, or relational database schema. * <p>As this is a startup method, it should destroy already created singletons * if it fails, to avoid dangling resources. In other words, after invocation * of that method, either all or no singletons at all should be instantiated. * * 加载或者刷新配置的持久性表示, * 这些配置可能在xml文件上,properties文件上,或者相关数据库上。 * 由于这是一个启动方法,因此若是他失败的话那么已经被建立的单例会被销毁,避免占用资源。 * 换句话说,在这个方法被调用以后,要么全部单例都被实例化,要么所有都没有。 */ @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); // 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(); } } }
这上面的方法会一个个跟踪。 未完····
参考: prepareRefresh方法:https://www.jianshu.com/p/c7364aeb0443 obtainFreshBeanFactory方法:https://www.jianshu.com/p/144af98965d9 prepareBeanFactory方法:https://www.jianshu.com/p/3468118a31f9 postProcessBeanFactory方法:https://www.jianshu.com/p/c05aea93b939
——————————————————————————————————
——————————————————————————————————