Spring加载普通的Bean的过程
首先先从配置:
<!-- 配置spring启动listener入口-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
入手初始化IOC容器。
ContextLoaderListener继承了ContextLoader并实现了ServletContextListener接口,当项目启动时,会收到初始化请求。
重写ServletContextListener的方法,并在方法中调用父类initWebApplicationContext方法来初始化。
此方法中:
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(org/springframework/web/context/ContextLoader);
servletContext.log("Initializing Spring root WebApplicationContext");
if(logger.isInfoEnabled())
logger.info("Root WebApplicationContext: initialization started");//是不是很熟悉
首先判断servletContext中是否已经注册WebApplicationContext然后开启日志写出我们很熟悉的started。
然后执行代码:
现创建了Context:
determineContextClass方法查具体的Context类读取初始化参数contextClass,一般我们不进行配置,然后ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);拿到默认的设置。(Spring写好的配置)
随后执行:configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)context, servletContext);来进行初始化。
此方法首先先设置一个默认的id。contextId,后面将配置(ConfigurableWebApplicationContext)set进拿到的上下文中。
接下来读取你的配置文件: String initParameter = sc.getInitParameter("contextConfigLocation");
极其熟悉的名字
<!-- spring配置文件路径 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:springAnnotation-core.xml</param-value>
<!-- <param-value>classpath*:config/springAnnotation-servlet.xml</param-value> -->
</context-param>
后面将得到的配置文件路径加入ConfigurableWebApplicationContext中并执行核心方法:wac.refresh();
prepareRefresh();记录时间输出日志,初始化一些东西。
接下来初始化BeanFactory:
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
首先:
refreshBeanFactory();
创建了bean工厂:DefaultListableBeanFactory实例
beanFactory.setSerializationId(getId());set进一个id
customizeBeanFactory(beanFactory);配置一些参数
重要的方法:
此方法会通过XmlBeanDefinitionReader加载bean定义。
然后根据配置初始化Reader。
具体的实现方法是在XmlBeanDefinitionReader.loadBeanDefinitions方法中定义的。主要就是加载Spring所有的配置文件,以备后面解析,注册用。
方法中有doLoadBeanDefinitions方法然后调用registerBeanDefinitions方法
创建完Reder后调用registerBeanDefinitions方法注册。后调用
doRegisterBeanDefinitions(Element root)方法
这里创建BeanDefinitionParserDelegate实例来解析XML。
parseBeanDefinitions(root, _flddelegate);来加载类
解析xml标签。
通过以上方法根据不同的XML节点,会委托NamespaceHandlerSupport找出合适的BeanDefinitionParser。
例如我们配置
<context:component-scan base-package="com.service.dao" />
那就调用
componentScanBeanDefinitionParser
这里定义了一个ClassPathBeanDefinitionScanner,通过它去扫描包中的类文件,注意:这里是类文件而不是类,因为现在这些类还没有被加载,只是ClassLoader能找到这些class的路径而已。
随后在doScan方法中Set candidates = findCandidateComponents(basePackage);
String packageSearchPath = (new StringBuilder("classpath*:"))
.append(resolveBasePackage(basePackage))
.append("/").append(resourcePattern).toString();
假设我们配置的需要扫描的包名为com.service,那么packageSearchPath的值就是classpath*:com.service/**/*.class,意思就是com.service包(包括子包)下所有class文件;如果配置的是*,那么packageSearchPath的值就是classpath*:*/**/*.class。这里的表达式是Spring自己定义的。Spring会根据这种表达式找出相关的class文件。
然后通过:Resource resources[] =resourcePatternResolver.getResources(packageSearchPath);拿到文件
进入findPathMatchingResources方法。在这里又把**/*.class去掉了,然后在调用getResources方法,然后在进入findAllClassPathResources方法。这里的参数只剩下包名了例如com/geeekr/service/。
最后一切工作完毕,该配置的配置了,该拿到的路径拿到了。
Spring调用Thread.currentThread().getContextClassLoader();。到此为止,就拿到class文件了。 Spring会将class信息封装成BeanDefinition,然后再放进DefaultListableBeanFactory的beanDefinitionMap中。
总结:
项目启动时先通过listener初始化容器,初始化WebApplicationContext,主要是讲创建好的ConfigurableWebApplicationContext放入上下文并读取配置文件。 然后创建beanfactory,配置好参数后创建一个Reader,然后再loadBeanDefinitions中利用刚才的Reader加载配置文件并注册,在
doRegisterBeanDefinitions方法中解析XML并根据不同的节点委托NamespaceHandlerSupport找出合适的BeanDefinitionParser。BeanDefinitionParser主要是拿到想应的class路径,该配置的配置好,路径全拿到
Spring调用Thread.currentThread().getContextClassLoader();。到此为止,就拿到class文件了。 Spring会将class信息封装成BeanDefinition,然后再放进DefaultListableBeanFactory的beanDefinitionMap中。
第一个笔记博客就那写么多。。好累,希望下次用到时能回忆起80%。