Spring学习笔记 IoC容器和XML的IoC装配

Spring依赖的包,Spring的项目实在太多,如果单独看Spring的项目的核心框架,可以先看一个包,spring-context这个依赖这个是spring核心组件所需依赖,其他核心组件都靠它一起引进。

IoC

inversion of control 控制反转这个中文名其实起的挺好的,你要看字面直译可能不一定符合·,reverson虽然有·反转的意思,但是一搜第一个意思是"恢复"。

控制反转就是把之前写代码要自己创建对象,给这个对象赋值。有了Spring你就可以把这些交给Spring来管理对象和属性赋值管理。spring负责控制对象的生命周期和属性控制还有对象之间的关系。

spring管理对象的容器,叫做IoC容器,IoC容器负责创建,配置和组装bean。(org.springframework,bean和org.springframework.context是IoC容器的基础。)IoC是一个概念,你要落地最后肯定是还要代码。BeanFactory就是大多数IoC容器的核心。BeanFactory是一个接口,当然他只是源头,后面很多都是他的子类或者他的实现,丰富了整个结构。

BeanFactory就是一个超级接口,真正实现容器的不是他,都是他的子类和实现类。这里最重要的有两个例子,一个是ApplicationContext(接口)和DefaultListAbleBeanFactory(类)

DefaultListableBeanFactory类

DefaultListAbleBeanFactory类是一个IoC容器的真正的实现,毕竟是一个类,里面的方法都是具体是实现。

DefaultListableBeanFactory加载Bean的方式是懒加载,所谓的懒加载就是只加载配置文件的的配置信息,而且不会创建Bean的实例,只有你getBean的时候他才创建Bean对应的对象。

同理还有一个XmlBeanFactory容器,继承了DefaultListableBeanFactory,实际上就是对于DefaultListableBeanFactory和XmlBeanDefinitionReader的封装调用,spring3.1废弃(看下面截图,这个类已经标记@Deprecated),他和DefaultBeanListableBeanFactory都是适合单体应用。

ApplicationContext接口

ApplicationContext接口是BeanFactory的子接口,继承了BeanFactory的全部功能。还添加Spring AOP的功能面向切面编程,应用层的上下文,资源处理,事件发布等新功能。ApplicationContext通过读取配置元数据,进行实例化,配置,和组装对象。这个元数据可以是Java配置文件(@Configuration,@AutoCOnfiguration),XML配置文件(XML配置),注解的方式(@Component和他的衍生注解,比如说@ComponentScan这个是发现@Component这些注解的,还有@Controller,@service,和@responsity这些都是@Component的进一步变种)。现在Spring推荐用ApplicationContext来做IoC容器。

以下是ApplicationContext来作为容器的实现:

ClassPathXmlApplicationContext主要是从类路径去加载配置文件,也支持从文件路径加载配置文件(这个就是ApplicationContext一层又一层的继承和实现的一个。)

FileSystemXMLApplicationContext也是ApplicationContext的一个实现的容器,就是名字一样,在文件路径下读取配置文件

ApplicationContext采用的是非消极加载,在IoC容器启动的时候,将配置的所有的默认对象,创建起来保存在容器中,对象是单例的构造方法只是被调用一次。

Bean

Spring我们经常说一个词Bean,Bean就是对象,或者说Bean就是由Spring管理的对象,Bean是一个Spring IoC容器的实例化,组装和管理的对象。

配置元数据,定义的Bean的以及他们的依赖关系,称为配置元数据。配置元数据可以是XML,Java注释或者是Java代码来表示,定义组成应用程序的对象与这些对象之间的丰富关系。

基于XML的配置:最原始的XML文件配置

基于注解的配置:对于配置元数据的注解,@Autowire,@PostContruct,@PreDestory,这些是Spring2.5开始(@Autowired是将在Spring的容器中管理的bean注入到对象,构造方法中去,省去set,get方法。@PostConstuct和@Autowired对比,就是Bean初始化完成的后执行的方法,在@Autowired之后,在Bean正式使用之前。)

基于Java的配置:就是用@Configuration和这些注解,来替代XML文件,整个Java文件也是一个配置类,这个是Spring3 开始。(还有AutoCOnfiguration这种starter的自动配置。)相关注解有@Bean、@Import和@DependsOn注解。

配置元数据加载到Spring中,会被转换为BeanDefintion(就是通过上面那些方式),BeanDefintion就是解析Bean的定义。

BeanDefinition包括很多信息,比如说他的实现类型,他的依赖项(就是需要其他的Bean),bean行为配置元素,容器作用范围,生命周期回调函数。只有这些BeanDefintion被记录到容器只能够,IoC容器启动,才会有Bean的初始化(依赖注入的过程),在这过程中才会有依赖关系注入到Bean中。

基于XML的Bean的装配

这个是最早的配置文件,现在用的比较少了,有一个Beans标签,下面有很多bean标签,这些bean标签就是交给IoC容器管理的Bean,可以设置别名,范围这些。

配置文件可以多个,可以传多个地址参数给ApplicationContext,让其去管理Bean。若果不想传额外的参数,也可以用import标签,把这些xml文件,汇总到一个XML上,spring-config.xml上,默认加载这个配置文件。

javascript 复制代码
<import resource="spring-config2.xml"/>

实际Java配置文件也一样,只不过更灵活一点,而且里面注解的意思也都差不多,@Bean,@import这些。(现在新的第三方包,很好看到xml但是Java配置文件明显比较多了。)

命名Bean

在XML的bean标签里面有name和id属性,二者是有差别的,id是全局唯一的,但是name可以有多个,多个name可以通过逗号,分号,甚至空格来分隔,但是要注意的是name也要与别的bean的不一样。你没有给Bean命名ID或者name,IoC容器会给分配一个。(所谓的ref注入,就是指XML注入Bean的同时又要引入另一个Bean,这个name,你必须要填一下)

javascript 复制代码
<!-- 定义两个Bean -->
<bean id="userService" class="com.example.UserService">
    <!-- 通过ref引用另一个Bean -->
    <property name="userDao" ref="userDao"/>
</bean>

<bean id="userDao" class="com.example.UserDao"/>

XML注入Bean(也就是标签),IoC的规则就是不考虑你内部引入另一个Bean,使用"类的全路径名#0"、"类的全路径名#1"、"类的全路径名#2"......的方式来为bean命

实例化Bean

XML上的Bean标签是一个描述创建一个或者多个Bean的方式,IoC容器在帮你实例化Bean对象方法有构造器,静态工厂,和实例工厂来实例化Bean,这三种实例方式我们可以自己去选择。

(1)构造实例化方式

IoC容器通过构造器来实例化Bean,底层是基于反射机制。所以要提供无参构造器或者有参构造器和对应的参数。

内部类实例化,想在XML中配置一个类的内部类,比如说一个类HelloSpring,在他内部有两个内部类如下:

javascript 复制代码
public static class StaticInnerClass {
    public StaticInnerClass() {
        System.out.println("静态内部类初始化");
    }
}

public class InnerClass {
    public InnerClass() {
        System.out.println("内部类初始化");
    }
}

在配置类中要想在HelloSpring的两个内部类一起实例化,就要用ref这种形式。

javascript 复制代码
<!--静态内部类的初始化,和外部类一样的。 使用.或者$将内部类名与外部类名分开都行-->
<bean class="com.spring.core.HelloSpring.StaticInnerClass"/>
<bean class="com.spring.core.HelloSpring.StaticInnerClass"/>

<bean class="com.spring.core.HelloSpring$StaticInnerClass"/>
<bean class="com.spring.core.HelloSpring$StaticInnerClass"/>


<!--非静态内部类的初始化需要依赖外部类对象-->
<bean name="helloSpring " class="com.spring.core.HelloSpring"/>

<bean class="com.spring.core.HelloSpring.InnerClass">
    <!--属性依赖注入,后面会讲-->
    <constructor-arg ref="helloSpring"/>
</bean>
<bean class="com.spring.core.HelloSpring$InnerClass">
    <!--属性依赖注入,后面会讲-->
    <constructor-arg ref="helloSpring"/>
</bean>

(2)静态工厂实例化

静态工厂就是我们自己创建Bean,但是由IoC容器来调用实例化Bean,将创建的对象放入容器里,常见对象的静态工厂不会被实例化。使用静态工厂方法实例化bean时,在< bean />标签中的class属性不再是要获取的bean的全路径类名,而是静态工厂的全路径类名,同时使用名为factory-method的属性指定获取bean对象的工厂方法的名称(注意该方法必须是静态方法)。

创建一个静态工厂类,同样是一个HelloSpring类

javascript 复制代码
/**
 * @author lx
 */
public class HelloSpringStaticFactory {

    private static HelloSpring helloSpring = new HelloSpring();

    /**
     * 静态工厂方法
     *
     * @return 返回HelloSpring实例
     */
    public static HelloSpring getHelloSpring() {
        System.out.println("静态工厂方法");
        return helloSpring;
    }

    public HelloSpringStaticFactory() {
        System.out.println("静态工厂不会初始化");
    }
}

配置文件

javascript 复制代码
<!--class表示静态工厂的全路径类名-->
<!--factory-method表示静态工厂方法-->
<bean name="helloSpring" class="com.spring.core.HelloSpringStaticFactory" factory-method="getHelloSpring"/>

静态工厂这种自己编写一个静态工厂类,让Spring启动和Spring的初衷不符合,本身就是让你不再关心Bean的创建,你这个自己有套了一层,反而更麻烦。

(3)实例工厂实例化

实例工厂就是从非静态方法获取所需的Bean,实例工厂实例化Bean时,bean属性的class属性置空,使用factory-bean属性指定实例化工厂的名字。

实例工厂类

javascript 复制代码
/**
 * @author lx
 */
public class HelloSpringInstanceFactory {

    private static HelloSpring helloSpring = new HelloSpring();

    /**
     * 静态工厂方法
     *
     * @return 返回HelloSpring实例
     */
    public  HelloSpring getHelloSpring() {
        System.out.println("实例工厂方法");
        return helloSpring;
    }

    public HelloSpringInstanceFactory() {
        System.out.println("实例工厂会初始化");
    }
}
javascript 复制代码
<!--实例化工厂-->
<bean id="helloSpringInstanceFactory" class="com.spring.core.HelloSpringInstanceFactory"/>
<!--factory-bean表示实例工厂的名字-->
<!--factory-method表示实例工厂方法-->
<bean name="helloSpring" factory-bean="helloSpringInstanceFactory" factory-method="getHelloSpring"/>

实际上,一个静态工厂或者实例工厂都可以配置多个工厂方法,那样更方便管理bean!另外,采用实例工厂方法实例化时,bean的初始化可以写在配置文件中(后面讲属性注入的时候会讲到),相比静态工厂方法更加灵活

基于XML的Bean的依赖装配

依赖项注入DI,对象仅仅通过构造函数参数,工厂方法的参数或从工厂方法构造或返回对象实例,在其上设置属性定义其他的依赖对象。IoC容器在创建Bean时会自动注入这个Bean的依赖项,这个过程Bean主动通过类的构造器和setter方法设置依赖项过程是相反的。(意思就是容器主动自动注入Bean的方式,Bean主动这个类构造器和setter方法设置依赖这个方式,二者过程是相反的)

有了DI(控制反转,就是除了创建对象,连对象里面依赖的关系都由容器来完成。),使用了·DI还有一个好处就是bean之间没有强耦合关系,对象不需要主动获取其依赖项,交给容器,让DI来完成这些。DI依赖注入方式,构造器依赖注入和setter注入。

构造器依赖注入:

构造器依赖注入,由IoC容器调用带有很多参数构造器来完成,每个参数表是一个依赖项。和调用带有特定参数的静态工厂来构造Bean是一样的。

构造器注入:

javascript 复制代码
/**
 * @author lx
 * 构造器依赖注入
 */
public class SimpleConstructorBased {
    /**
     * 依赖的两个属性
     */
    private String property1;
    private String property2;

    /**
     * 测试构造器依赖注入
     */
    public SimpleConstructorBased(String property1, String property2) {
        this.property1 = property1;
        this.property2 = property2;
        System.out.println("构造器依赖注入");
    }

    @Override
    public String toString() {
        return "SimpleConstructorBased{" +
                "property1='" + property1 + '\'' +
                ", property2='" + property2 + '\'' +
                '}';
    }
}

要想使用构造器依赖注入方式,需要依赖< bean />标签的子标签< constructor-arg >,一个< constructor-arg >标签表示一个属性。

这里我们新建一个DI.xml配置文件,用于测试依赖注入。在文件中配置我们的bean:

javascript 复制代码
<!--构造函数属性注入-->
<bean id="simpleConstructorBased" class="com.spring.core.SimpleConstructorBased">
    <!--一个constructor-arg表示一个属性-->
    <constructor-arg value="v1"/>
    <constructor-arg value="v2"/>
</bean>

构造参数注入的解析:

配置的< constructor-arg >标签表示一个参数,容器会对该标签进行解析,以保证能够匹配到一个合适的构造函数。解析的方式有多种,最直接的就是通过< constructor-arg >标签的value属性指定该标签对应的参数的值,容器会自动解析参数个数和参数值的类型并选择对应的构造器,上面的案例就是根据参数值value直接解析。(construct-arg 构造器参数)

指定参数名

对于引用类型的参数,他有明确不同的类型,容器可能正常解析,对于基本类型和String类型,容器有时候不能正常解析。特别是对于多个构造器并且具有相同个数参数的情况。

javascript 复制代码
/**
 * @author lx
 * 构造器依赖注入
 */
public class SimpleConstructorBased2 {
    /**
     * 依赖的两个属性
     */
    private int property1;
    private String property2;
    private boolean property3;

    /**
     * 测试构造器依赖注入1
     */
    public SimpleConstructorBased2(int property1, String property2) {
        this.property1 = property1;
        this.property2 = property2;
        System.out.println("构造器依赖注入1");
    }

    /**
     * 测试构造器依赖注入2
     */
    public SimpleConstructorBased2(int property1, boolean property3) {
        this.property1 = property1;
        this.property3 = property3;
        System.out.println("构造器依赖注入2");
    }

    @Override
    public String toString() {
        return "SimpleConstructorBased2{" +
                "property1=" + property1 +
                ", property2='" + property2 + '\'' +
                ", property3=" + property3 +
                '}';
    }
}

配置文件

javascript 复制代码
<bean id="simpleConstructorBased2"
 class="com.spring.core.SimpleConstructorBased2">
    <constructor-arg value="1"/>
    <constructor-arg value="true"/>
</bean>

运行测试

javascript 复制代码
/**
 * 构造器依赖注入
 */
@Test
public void simpleConstructorBased2() {
    ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("DI.xml");
    System.out.println(Arrays.toString(ac.getBeanDefinitionNames()));
    System.out.println(ac.getBean("simpleConstructorBased2", SimpleConstructorBased2.class));
}

输出结果

javascript 复制代码
构造器依赖注入1
[simpleConstructorBased2]
SimpleConstructorBased2{property1=1, property2='true', property3=false}

这就是由于Spring不能分辨基本类型和String造成的不能确定到底使用哪一个构造器的情况,输出的还是·第一个构造器。这种情况需要用< constructor-arg >标签的name属性来指定参数名字。

指定参数顺序

虽然可以指定参数名和参数类型,但是在某些情况下仍然有问题,比如当存在两个构造器形参列表类型一致,但是参数顺序不一致,这样的情况下,Spring仍然不能确定到底使用哪一个构造器。

我们可以使用< constructor-arg >标签的index属性来指定参数出现的索引位置,index从0开始,0就表示第一个参数,1就表示第二个参数......以此类推。注意:你可以不把顺序写完整,Spring会自动寻找能通过你写的顺序识别的构造器,但是你不能把顺序写错了,那样找不到构造器就会报错。

对配置文件进行改造,指定参数顺序,这里我们发现只需要指定int类型的参数顺序1或者2,Spring就能分辨出两个构造器:

javascript 复制代码
<bean id="simpleConstructorBasedx" 
class="com.spring.core.SimpleConstructorBasedx">
    <!--一个constructor-arg表示一个属性-->
    <constructor-arg name="property1" value="xx" type="java.lang.String"/>
    <constructor-arg name="property3" value="1" type="int" index="1"/>
    <constructor-arg name="property2" value="yy" type="java.lang.String"/>
</bean>

setter依赖注入

setter依赖注入是由IoC容器调用参数setter方法完成,setter方法是在调用构造器以实例化Bean之后完成的。

工厂方法依赖注入

(1)ApplicationContext容器被实例化之后,它包含了所有bean的配置元数据。这些配置元数据可以通过XML、Java代码或注解来指定。

(Spring 中的 Bean 配置元数据是指用于描述如何创建和配置一个 Bean 的信息集合。这些元数据包含了关于 Bean 的各种属性和行为的信息,例如名称、作用域、依赖关系以及生命周期回调方法等。)

(2)对于每个bean,其依赖项以属性的set方法、构造函数参数或静态工厂方法的参数的形式表示。当实际创建 bean 时,这些依赖项将提供给 bean。

(Spring通过配置元数据(如XML、注解或Java配置)来描述Bean的依赖关系,并在实际创建Bean时,将这些依赖项(可能是其他Bean或基本类型的值)通过指定的方式注入到目标Bean中。这里只是如歌提供给Bean对应的依赖项的不同方式。)

(3)每个需要注入的依赖项要设置的实际注入的value值,或对容器中另一个 bean 的ref引用(下面会讲),最开始统一为一个字符串格式

(这句话描述的是Spring在解析配置元数据(如XML或注解)时,所有依赖项(无论是基本类型的value还是其他Bean的ref)最初都是以字符串形式表示的,后续由Spring内部的组件(如PropertyEditor或TypeConverter)将其转换为实际需要的类型。所以你不指定类型,会出现前面返回一个不对的构造器的情况。)

(4)最后,属性值会从字符串的描述转换为实际属性类型。通过value设置的值, Spring可以自动将以字符串格式提供的值转换为所有内置的类型,如int、long、String、boolean等。当然我们也可以自定义转换方式,比如字符串转换为Date时间类型!

依赖注入问题:循环依赖

循环依赖就是A依赖B,B依赖A,这种尤其是构造器注入会出现这种情况,鸡生蛋蛋生鸡。IoC容器将在运行时检测到此循环引用,并抛出BeanCurrentlyCreationException。

这种情况也不是无解,可以用setter方法来处理,就是A和B都创建好了,我在set进行依赖注入。

依赖注入问题:构造器和setter注入的选择

强制依赖,我们可以用构造器方式。换一种方式也就是setter方法,就是用@required的注解来标注这个必须要依赖的参数。

相关推荐
Sword-豪4 分钟前
全平台开源电子书阅读器推荐,支持多端同步+AI朗读!支持epub/mobi/azw3/pdf常见电子书格式!
笔记·电脑
汇能感知1 小时前
关于光谱相机的灵敏度
经验分享·笔记·科技
jian110582 小时前
java spring -framework -mvc
java·spring·mvc
wktomo3 小时前
GO语言学习(七)
开发语言·学习·golang
悲伤小伞3 小时前
C++_数据结构_哈希表(hash)实现
数据结构·c++·笔记·算法·哈希算法·散列表
贺函不是涵3 小时前
【沉浸式求职学习day46】【华为5.7暑期机试题目讲解】
学习·算法·华为
红衣小蛇妖4 小时前
Python基础学习-Day30
开发语言·python·学习
*TQK*4 小时前
高等数学笔记——向量代数与空间解析几何1
笔记·学习·高等数学
hopetomorrow4 小时前
学习路之uniapp --- 视频直播开发(在现有nginx服务器上搭建RTMP媒流体服务器)
学习·nginx·uni-app
love530love5 小时前
【笔记】PyCharm 中创建Poetry解释器
运维·人工智能·windows·笔记·python·pycharm·conda