Spring(学习笔记)

<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容器的启动过程完毕。

 

Spring是一个开源的Java框架,用于构建企业级应用程序。它提供了一种轻量级的、非侵入式的开发方式,通过依赖注入和面向切面编程等特性,简化了Java应用程序的开发过程。 以下是关于Spring学习的一些笔记: 1. IoC(控制反转):Spring通过IoC容器管理对象的创建和依赖关系的注入。通过配置文件或注解,将对象的创建和依赖关系的维护交给Spring容器来管理,降低了组件之间的耦合度。 2. DI(依赖注入):Spring通过依赖注入将对象之间的依赖关系解耦。通过构造函数、Setter方法或注解,将依赖的对象注入到目标对象中,使得对象之间的关系更加灵活和可维护。 3. AOP(面向切面编程):Spring提供了AOP的支持,可以将与业务逻辑无关的横切关注点(如日志、事务管理等)从业务逻辑中分离出来,提高了代码的可重用性和可维护性。 4. MVC(模型-视图-控制器):Spring提供了一个MVC框架,用于构建Web应用程序。通过DispatcherServlet、Controller、ViewResolver等组件,实现了请求的分发和处理,将业务逻辑和视图展示进行了分离。 5. JDBC和ORM支持:Spring提供了对JDBC和ORM框架(如Hibernate、MyBatis)的集成支持,简化了数据库访问的操作,提高了开发效率。 6. 事务管理:Spring提供了对事务的支持,通过声明式事务管理和编程式事务管理,实现了对数据库事务的控制和管理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值