<context:annotation-config/>是 Spring 配置文件中的一个标签,用于开启注解配置功能。这个标签可以让 Spring 容器识别并处理使用注解定义的 bean。例如,可以使用 @Autowired 注解自动装配 bean,或者使用 @Component 注解将类标记为 bean 等。
为什么加载上下文的时候,被包含的Bean它的构造函数的代码会执行呀?好像是很多代码都执行了!!!
没有无参构造,有有参构造会报错!
也就是说必须要有无参构造!这是为什么呀 !
@Value注解:
@Value("Essence") private String name;
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
<bean id="people" class="pojo.People" autowire="byType"> <property name="name" value="Durant"/> <property name="dog" ref="dog"/> <property name="cat" ref="cat"/> </bean> <bean id="cat" class="pojo.Cat"/> <bean id="dog" class="pojo.Dog"/>
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
<bean id="hello" class="pojo.Hello"> <property name="str" value="Spring"/> </bean>
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
<bean id="userT" class="pojo.UserT" name="user2,u2"> <property name="name" value="张恒"/> </bean>
getBean();中可以输入u2,user2
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
<bean id="address" class="pojo.Address"> <property name="address" value="西安"/> </bean> <bean id="student" class="pojo.Student"> <property name="name" value="张恒"/> <property name="address" ref="address"/> <property name="books"> <array> <value>红楼梦</value> <value>西游记</value> <value>水浒传</value> <value>三国演义</value> </array> </property> <property name="hobbys"> <list> <value>听歌</value> <value>看电影</value> <value>敲代码</value> </list> </property> <property name="card"> <map> <entry key="身份证" value="411628"/> </map> </property> <property name="games"> <set> <value>穿越火线</value> </set> </property> <property name="wife"> <null/> </property> <property name="info"> <props> <prop key="学号">2020</prop> <prop key="性别">男</prop> <prop key="姓名">小明</prop> </props> </property> </bean>
- @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
- 其次再进行默认的byName方式进行装配;
- 如果以上都不成功,则按byType的方式自动装配。
- 都不成功,则报异常。
Spring IoC底层源码分析
Spring IoC容器的启动可以概括为一下两步:
- 创建BeanFactory
- 实例化Bean对象
在SourceCodeLearning类中设置好断点后,下面一步步进入Spring底层代码。
ApplicationContext applicationContext = new FileSystemXmlApplication("classpath:spring.xml");
通过FileSystemXmlApplicationContext跟踪上述构造器可以发现,其主要完成了一下三个步骤:
- 初始化父容器AbstractApplicationContext
- 设置资源文件的位置setConfigLocations
- 使用核心方法refresh(),其实是在超类AbstractApplicationContext中定义的一个模版方法(模版方法设计模式)。
refresh()方法的定义--ConfigurationApplicationContext接口中定义了该方法。
ConfigurationApplicationContext的基类是BeanFactory。
AbstractApplicationContext类实现了ConfigurationApplicationContext接口,重写了refresh()方法。部分重要内容如下:
AbstractApplicationContext.refresh()方法是个模版方法,定义了需要执行的一些步骤。并不是实现了所有的逻辑,只是充当了一个模版,由其子类去实现更多个性化的逻辑。
模版方法refresh()中最核心的两步:
(1)创建BeanFactory:
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
(2)实例化Bean:
finishBeanFactoryInitialization(beanFactory);
创建BeanFactory
创建BeanFactory重点分析AbstractApplicationContext.obtainFreshBeanFactory()方法。其代码实现如下:
从以上代码可以发现,AbstractApplicationContext.obtainFreshBeanFactory()方法分为以下两步:
- 刷新BeanFactory,即refreshBeanFactory()。
- 获取BeanFactory,即getBeanFactory()。
这两步中刷新BeanFactory的方法refreshBeanFactory()是核心,接下来进一步分析refreshBeanFactory()方法。这个方法定义在AbstractApplicationContext中,是一个抽象方法,也是一个模版方法,需要AbstractApplicationContext的子类来实现逻辑。其具体实现是在其子类AbstractRefreshableApplicationContext中完成的。refreshBeanFactory()方法实现的部分代码如下:
可以发现,在refreshBeanFactory()方法的实现中,首先检查当前上下文是否已经存在BeanFactory。如果已存在BeanFactory,先销毁Bean和BeanFactory,然后创建新的BeanFactory。
DefaultListableBeanFactory beanFactory = createBeanFactory();这行代码只是创建了一个空的BeanFactory,其中没有任何Bean。因此refreshBeanFactory()方法的核心功能是在loadBeanDefinitions(beanFactory);这行代码中实现的。
loadBeanDefinitions()的具体实现是在AbstractXmlApplication类中。
loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法中,通过上一步创建的空的BeanFactory来创建一个XmlBeanDefinitionReader对象。XmlBeanDefinitionReader是用来解析XML中定义的bean的。
下面重点讲解loadBeanDefinitions(beanDefinitionReader)方法,这是一个重载的方法,这个方法的入参是刚刚生成的XmlBeanDefinitionReader对象。下面进入重载的loadBeanDefinitions方法进行分析,代码如下:
这个方法主要的功能是解析资源文件的位置,然后调用XmlBeanDefinitionReader对象的loadBeanDefinitions方法解析Bean的定义。
下面将对reader.loadBeanDefinitions(cinfigLocations);这段代码进行解析。
分析AbstractBeanDefinitionReader的方法loadBeanDefinitions,其方法实现如下:
可以发现loadBeanDefinition()方法会遍历资源数组,最终会调用重载方法loadBeanDefinition(),重载方法的部分实现代码如下:
这个方法会解析资源文件的路径,得到Resource[]资源数组,核心逻辑是调用loadBeanDefinitions(resource)方法,进入这个方法查看其代码如下:
loadBeanDefinitions内部工作原理是遍历每个资源,依次调用loadBeanDefinitions(Resource resource)重载的方法。该重载的方法在顶层接口BeanDefinitionReader中
该方法会调用重载方法loadBeanDefinitions(EncodedResource encodedResource)。
loadBeanDefinitions(EncodeResource encodedResource)方法以流的方式读取资源文件,调用doLoadBeanDefinition()方法。doLoadBeanDefinition()是载入定义Bean的核心方法。其部分代码如下:
从doLoadBeanDefinition(InputSource inputSource, Resource resource)方法的定义可以看出,最终注册Bean的地方是在registerBeanDefinitions(doc, resource);这行代码。其代码如下:
registerBeanDefinitions(Document doc, Resource resource)方法的核心逻辑是在documentReader.registerBeanDefinitions(doc, createReaderContext(resource));这一行,这里发生了对Bean的注册。registerBeanDefinitions(Document doc , XmlReaderContext readerContext)方法代码如下:
registerBeanDefinitions(Document doc , XmlReaderContext readerContext)方法是在DefaultBeanDefinitionDocumentReader中实现的。核心是通过doRegisterBeanDefinitions()方法实现的。其代码实现如下:
doRegisterBeanDefinitions(Element root)方法的核心逻辑在parseBeanDefinition(root,this.delegate);这个方法中处理。其代码如下:
parseBeanDefinition(root,this.delegate)方法的核心逻辑是依赖parseDefaultElement(ele, delegate);方法实现的,其代码如下:
根据不同Bean的配置不同,进入不同分支执行。本书的示例是进入processBeanDefinition(ele,delegate)方法。其代码如下:
从上述方法中可知,最关键的是BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry());的调用。这是注册Bean的关键代码,其代码如下:
registry.registerBeanDefinition(beanName,definitionHolder.getBeanDefinition());这行是将Bean的名字和BeanDefinition对象进行注册的地方。该方法的定义是在BeanDefinitionRegistry中。
本例将进入BeanDefinitionRegistry接口的实现类DefaultListableBeanFactory中,其部分代码如下:
从registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法代码可以看出,先从beanDefinitionMap这个ConcurrentHashMap对象根据beanName查找是否已经有同名的bean,如果不存在,则会调用beanDefinitionMap.put(beanName,beanDefinition)方法,以beanName为key,beanDefinition为value注册,将这个Bean注册到BeanFactory中,并将所有的BeanName保存到beanDefinitionNames这个ArrayList中。
到此,完成了IoC第一部分——创建BeanFactory的代码解析。但是,此时Bean只是完成了Bean名称和BeanDefinition对象的注册,并没有实现Bean的实例化和依赖注入。下面将要分析IoC的第二个关键部分Bean的初始化。
实例化Bean
在创建BeanFactory的过程中,BeanDefinition注册到了BeanFactory中的一个ConcurrentHashMap对象中了,并且以BeanName为key,BeanDefinition为value注册。下面将要分析实例化Bean的过程,即从上文提到的AbstractApplicationContext类的refresh()方法中的finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory)方法开始向底层分析。
首先进入finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory)方法,查看其部分代码如下:
从上述代码可知,beanFactory.preInstantiateSingletons();这行代码是实例化Bean的。
打开preInstantiateSingletons()方法如下:
该方法遍历beanDefinitionNames这个ArrayList对象中的BeanName,循环调用getBean(beanName)方法。该方法实际上就是创建Bean并递归构建Bean间的依赖关系。getBean(beanName)方法最终会调用doGetBean(name,null,null,false),进入该方法查看doGetBean方法的部分代码如下:
可以看到,该方法首先会获取当前Bean依赖关系mbd.getDependsOn();接着根据依赖的BeanName递归调用getBean()方法,直到调用到getSingleton()方法返回依赖Bean,即当前正在创建的Bean ,不断探寻依赖的Bean,直到依赖关系最底层的Bean 没有依赖的对象了,至此整个递归过程结束。getSingleton()方法的参数是createBean()方法的返回值。createBean()是在AbstractAutowireCapableBeanFactory中实现的。createBean(String beanName, RootBeanDefinition mbd,@Nullable Object[] args)方法部分代码如下:
该方法的核心是doCreateBean(beanName,mdbToUse,args)这个方法,doCreateBean将会返回Bean对象的实例。查看doCreateBean的部分代码如下:
这个方法中最重要的两行代码:
(1)instanceWrapper = createBeanInstance(beanName,mbd,args)用来创建实例。
(2)方法populateBean(beanName,mbd,instanceWrapper)用于填充Bean,该方法可以说就是发生了依赖注入的地方。
先看看createBeanInstance()方法其核心实现如下:
createBeanInstance()方法会调用instantiateBean()方法,其部分实现如下:
instantiateBean()方法核心逻辑是beanInstance = getInstantiationStrategy().instantiate(),发挥作用的策略对象是SimpleInstantiationStrategy,在该方法内部调用了静态方法BeanUtils.instantiateClass(),这个方法的部分实现如下:
该方法会判断是否是Kotlin类型,如果不是,则会调用Constructor的newInstance方法,也就是最终使用反射创建了该实例。
到这里,Bean的实例已经创建完成。但是Bean实例的依赖关系还没有设置,下面回到doCreateBean()方法中的populateBean()方法,该方法用于填充Bean,该方法可以说就是发生依赖注入的地方。回到AbstractAutowireCapableBeanFactory类中看一下populateBean()方法的实现。populateBean()部分代码如下:
整个方法的核心逻辑是PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues():null);这行代码,即获取该bean的所有属性,就是配置property元素,即依赖关系。最后执行applyPropertyValues()方法,其实现如下:
关键代码Object resolveValue = valueResolver.resolveValueIfNecessary(pv,originalValue);该方法是获取property对应的值。resolveValueIfNecessary()方法部分代码如下:
resolveValueIfNecessary()方法的核心是resolveReference(),该方法是解决Bean依赖关系的。进入该方法,其代码如下:
这段代码的核心是以下这一行:
bean = this.beanFactory.getParentBeanFactory().getBean();
这里将会发生递归调用,根据依赖的名称,从BeanFactory中递归得到依赖。到这段结束,就可以获取到依赖的Bean。回到applyPropertyValues入口处,获取到依赖的对象值后,将会调用bw.setPropertyValues()方法,这是将依赖值注入的地方。此方法会调用AbstractPropertyAccessor类的setPropertyValues方法,查看AbstractPropertyAccessor.setPropertyValues方法的实现,其部分代码如下:
该方法会循环Bean的属性列表,循环中调用setPropertyValue()方法,该方法是通过AbstractPropertyAccessor.setPropertyValues()方法来实现的,进入该方法的代码,其部分实现如下:
其核心是最后一行nestedPa.setPropertyValue()代码,其部分代码实现如下:
进入processLocalProperty()方法的代码,该方法非常复杂,其核心实现如下:
上述代码调用的ph.setValue()方法是BeanWrapperImpl.setValue()方法,进入这个方法的代码,查看其部分实现如下:
该方法是最后一步,这里可以看到该方法会找到属性的set方法,然后调用Method的invoke方法,完成属性注入。至此IoC容器的启动过程完毕。