Spring IoC基于XML的DI详细配置全解两万字

Posted 刘Java

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring IoC基于XML的DI详细配置全解两万字相关的知识,希望对你有一定的参考价值。

详细介绍了Spring IoC基于XML的DI详细配置全解。

前面的文章Spring IOC容器的概念以及基于XML的IoC装配中,我们学习了IoC容器的概念以及依赖注入的两种方式,现在我们来看看一些更详细的配置。本文基于Spring5.2.8,案例基于上一篇文章的案例。

1 value字面量

对于基本类型、String、包装类类型的属性,我们可以直接使用value属性的字符串值来描述具体的值,这样可读性也更强。在最后注入的时候Spring的转换服务会将这些值从 String 转换为属性或参数的实际类型。

并且Spring支持使用< value >标签表示具体的字面量值:

<bean id="simpleSetterBased" class="com.spring.core.SimpleSetterBased">
    <constructor-arg name="property1">
        <value>xxx</value>
    </constructor-arg>
    <constructor-arg name="property2">
        <value>yyy</value>
    </constructor-arg>
    
    <!--setter方法 name表示属性名 value 表示属性值-->
    <property name="property3">
        <value>123</value>
    </property>
    <property name="property4">
        <value>false</value>
    </property>
</bean>

1.1 Properties快捷转换

Spring容器支持通过PropertyEditor直接解析value中的特定格式的字符串字面量值,并转换为一个Properties集合。后面我们也会学习集合的注入方式,但是这是一个非常好用的快捷方式!

我们来测试一下,首先有一个PropertiesDI类,内部有一个Properties(Hashtable是Properties的父类)属性:

/**
 * @author lx
 */
public class PropertiesDI {

    private Hashtable properties;

    /**
     * setter
     */
    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    @Override
    public String toString() {
        return "PropertiesDI{" +
                "properties=" + properties +
                '}';
    }
}

配置如下:

<!--properties-->
<bean class="com.spring.core.PropertiesDI" id="propertiesDI">
    <property name="properties">
        <!--直接写配置即可,自动转换为Properties-->
        <value>
            ! 注释
            # 注释
            # “#”“!”开头的一行被算作注释不会解析。
            # key和value可以使用 “=”、“:”、“ ”等符号分割,详见properties说明


            key=value
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
            ccc:ddd
            aaa bbb
            eee    fff
        </value>
    </property>
</bean>

测试:

@Test
public void properties() {
    ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("DI.xml");
    System.out.println(Arrays.toString(ac.getBeanDefinitionNames()));
    System.out.println(ac.getBean("propertiesDI", PropertiesDI.class));
}

结果如下,成功注入:

[propertiesDI]
PropertiesDI{properties={jdbc.url=jdbc:mysql://localhost:3306/mydb, jdbc.driver.className=com.mysql.jdbc.Driver, eee=fff, key=value, aaa=bbb, ccc=ddd}}

2 引用其他bean

2.1 ref引用

在< constructor-arg >、< property >、< entry >标签中有一个ref属性,用于将bean的指定属性的值设置为对容器管理的另一个bean的引用。这就是引用类型属性依赖的设置方式。被引用的bean是要设置其属性的bean的依赖项,在设置该属性之前,需要对其进行初始化。ref属性的值需要与引用的目标bean的id或者name属性中的一个值相同。

当然还有一个< ref >标签,可以作为< constructor-arg >、< property >以及某些集合标签的子标签,通过< ref >标签的bean属性也可以来指定引用的目标bean。< ref >标签允许在同一容器或父容器中创建对任何bean的引用,而不管它是否在同一XML文件中。bean属性的值需要与引用的目标bean的id或者name属性中的一个值相同。

如下案例,首先有一个RefDI类,用于ref测试:

/**
 * @author lx
 */
public class RefDI {

    private HelloSpring helloSpring1;
    private HelloSpring helloSpring2;

    public RefDI(HelloSpring helloSpring1, HelloSpring helloSpring2) {
        this.helloSpring1 = helloSpring1;
        this.helloSpring2 = helloSpring2;
    }

    @Override
    public String toString() {
        return "RefDI{" +
                "helloSpring1=" + helloSpring1 +
                ", helloSpring2=" + helloSpring2 +
                '}';
    }
}

配置文件:

<!--ref-->
<!--定义一个Bean-->
<bean name="helloSpring3" class="com.spring.core.HelloSpring"/>

<bean class="com.spring.core.RefDI" id="refDI">
    <!--使用ref属性引用helloSpring3的bean-->
    <constructor-arg name="helloSpring1" ref="helloSpring3"/>

    <!--使用ref标签引用helloSpring3的bean-->
    <constructor-arg name="helloSpring2">
        <ref bean="helloSpring3"/>
    </constructor-arg>
</bean>

测试:

@Test
public void ref() {
    ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("DI.xml");
    System.out.println(Arrays.toString(ac.getBeanDefinitionNames()));
    System.out.println(ac.getBean("refDI", RefDI.class));
}

成功注入了其他bean:

初始化
[helloSpring3, refDI]
RefDI{helloSpring1=HelloSpring{hello='hello'}, helloSpring2=HelloSpring{hello='hello'}}

2.2 parent继承

< bean >、< ref >等标签中还有一个parent属性,这个属性用于指定目标bean将使用父bean的属性和配置,除了autowire、scope和lazy init属性,parent属性用于相同属性以及值的复用。parent属性的值与目标父bean的id属性或name属性中的一个值相同。

对于parent继承,这里又分几种情况:

  1. 如果父bean有class属性,而子bean没有class属性,那么子bean就是和父bean同一个class类型,相当于创建两个相同的对象。
  2. 如果父bean有class属性,而子bean也有class属性,那么允许它们是不同的类型,但是子bean必须含有父bean中定义的所有的注入方式。
  3. 如果父bean没有class属性,那么子bean必须定义class属性,这个父bean实际上类似于一个属性和值的模版,仅仅被值bean引用,实现配置复用,不能实例化,(这时父bean必须添加abstract="true"属性,表示父bean不会被创建,类似于于抽象类,否则启动容器会尝试父bean,但是由于父bean没有class而抛出异常:No bean class specified on bean definition)。这种情况下,子bean同样必须含有父bean中定义的所有的注入方式。
  4. 这里的父bean和子bean 以及“继承”,并不是Java中的继承关系,仅仅是复用了注入方式,精简了代码!
  5. 如果子bean和父bean中注入对相同依赖同时注入的值的话,那么可能会相互覆盖对方的值。这根据依赖注入的先后顺序:父bean的构造器注入->子bean的构造器注入->父bean的setter注入->子bean的setter注入,排序在后面的对相同依赖的注入值将会覆盖之前注入的值!

如下案例,首先有三个类:

/**
 * @author lx
 */
public class ParentOne {
    private String property1;

    public void setProperty1(String property1) {
        this.property1 = property1;
    }

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

一个有意思的地方是,虽然ParentTwo继承了ParentOne,但是并没有继承私有属性property1,不过由于继承了setProperty1方法,因此仍然能够正常工作,这就是前面说的“子bean必须含有父bean中定义的所有的注入方式”的含义,对于setter方法注入来说,你没这个属性没关系,只要有个同名方法,参数类型能够兼容(从String转为参数类型)就不会报错,与返回值无关(见ParentThree)!

3 idref引用校验值

< idref >标签通常可以作为< constructor-arg >、< property >以及某些集合标签的子标签,用于将容器中另一个 bean的id或者name的字符串值(并不是引用)传递给< constructor-arg >、< property >以及某些集合标签,同时使用idref容器在部署的时候还会验证这个名称的bean是否真实存在(被定义了),这是一种防止错误的方法。该标签目前用的比较少。

如下案例,首先有一个IdrefCheck类,用于校验bean是否被定义了:

public class IdrefCheck {

    private String targetName;

    public void setTargetName(String targetName) {
        this.targetName = targetName;
    }

    @Override
    public String toString() {
        return "IdrefDI{" +
                "targetName='" + targetName + '\\'' +
                '}';
    }
}

配置文件:

<!--idref-->
<!--定义一个Bean-->
<bean name="helloSpring3" class="com.spring.core.HelloSpring" />

<!--idrefCheck校验bean-->
<bean class="com.spring.core.IdrefCheck" name="idrefCheck">
    <!--实际上就等于<property name="targetName" value="helloSpring3">-->
    <!--但是多了bean校验的功能-->
    <property name="targetName">
        <idref bean="helloSpring3"/>
    </property>
</bean>

实际上< idref >的bean属性引用的值就是等于一个String类型的值,都是字符串,但是< idref >多了一个校验对应名称的bean是否存在的功能!

在idea中,如果idref的bean属性指定的bean名字不存在容器中,那么直接报红,如果运行,那么会抛出:Invalid bean name 'helloSpring3' in bean reference for bean property 'targetName'

@Test
public void idref() {
    ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("DI.xml");
    System.out.println(Arrays.toString(ac.getBeanDefinitionNames()));
    System.out.println(ac.getBean("idrefDI", IdrefCheck.class));
}

结果如下:

org.springframework.beans.factory.BeanCreationException: Error creating 
bean with name 'idrefDI' defined in class path resource [DI.xml]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean name 'helloSpring3' in bean reference for bean property 'targetName'

在很久之前(Spring2.0之前的版本中),< idref >标签的一个常用的作用是在用在ProxyFactoryBean中定义的AOP拦截器里。指定拦截器名称时使用< idref >元素可防止你拼写错误拦截器ID。但是目前用的比较少了!

4 内部bean

< bean >标签的内部可以使用< constructor-arg >、< property >以及某些集合标签,表示依赖注入。同样,在< constructor-arg >、< property >以及某些集合标签中也可以使用< bean >子标签,表示一个内部bean。原因很简单,如果我们注入的是一个对象,并且我们不想要通过ref引用其他已存在的bean,那么只有定义自己的内部的bean。

和“外部”bean的区别是,内部bean不需要定义id或者name属性,因为这个对象就相当于一个外部bean自己的对象。就算指定了,容器也不会使用这些值作为bean的名字,我们也不能通过IoC容器获取。容器在创建时也会忽略内部bean的scope作用域属性(后面会讲),因为内部 bean 始终是匿名的,并且始终使用外 bean 创建。无法独立访问内部bean,也无法将它们注入其他外部bean中。

如下案例,首先有一个InnerBean类,用于内部bean测试:

/**
 * 内部bean
 *
 * @author lx
 */
public class InnerBean {
    private InnerBeanInner innerBeanInner1;
    private InnerBeanInner innerBeanInner2;

    public void setInnerBeanInner1(InnerBeanInner innerBeanInner1) {
        this.innerBeanInner1 = innerBeanInner1;
    }

    public void setInnerBeanInner2(InnerBeanInner innerBeanInner2) {
        this.innerBeanInner2 = innerBeanInner2;
    }


    @Override
    public String toString() {
        return "InnerBean{" +
                "innerBeanInner1=" + innerBeanInner1 +
                ", innerBeanInner2=" + innerBeanInner2 +
                '}';
    }

    public static class InnerBeanInner {
        private String property1;
        private int property2;


        public void setProperty1(String property1) {
            this.property1 = property1;
        }

        public void setProperty2(int property2) {
            this.property2 = property2;
        }

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

配置文件:

<!--内部bean-->
<bean id="innerBean" class="com.spring.core.InnerBean">
    <property name="innerBeanInner1">
        <!--内部bean 不需要指定id或者name-->
        <bean class="com.spring.core.InnerBean.InnerBeanInner">
            <property name="property1" value="aaa"/>
            <property name="property2" value="111"/>
        </bean>
    </property>
    <property name="innerBeanInner2">
        <!--内部bean 指定id或者name也没用,不能通过容器获取到-->
        <bean id="innerBeanInner" class="com.spring.core.InnerBean.InnerBeanInner">
            <property name="property1" value="bbb"/>
            <property name="property2" value="222"/>
        </bean>
    </property>
</bean>

测试:

@Test
public void innerBean() {
    ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("DI.xml");
    System.out.println(Arrays.toString(ac.getBeanDefinitionNames()));
    InnerBean innerBean = ac.getBean("innerBean", InnerBean.class);
    System.out.println(innerBean);
}

结果:

[innerBean]
InnerBean{innerBeanInner1=InnerBeanInner{property1='aaa', property2=111}, innerBeanInner2=InnerBeanInner{property1='bbb', property2=222}}

5 集合注入

5.1 注入方式

Spring提供了详细的集合注入方式。< list >、< set >、< map >、< props >、< array >标签分别用来注入Java中的List、Set、Map、Properties、array类型的集合,主要是用于集合类型的依赖项的注入。因为集合的元素既可以是基本类型也可以是对象甚至集合,因此集合注入非常灵活。另外集合注入支持泛型转换,注入的时候会自动将value的字符串值转换为对应泛型类型!

如下案例,首先有一个CollectionDI类,用于集合注入测试:

/**
 * 集合注入
 *
 * @author lx
 */
public class CollectionDI {

    //集合属性注入

    private List list;
    private Set set;
    private Map map;
    private Properties properties;
    private Object[] array;

    public CollectionDI(List list, Set set, Map map, Properties properties, Object[] array) {
        this.list = list;
        this.set = set;
        this.map = map;
        this.properties = properties;
        this.array = array;
    }

    static class CollectionInner {
        private String property1;
        private int property2;


        public void setProperty1(String property1) {
            this.property1 = property1;
        }

        public void setProperty2(int property2) {
            this.property2 = property2;
        }

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

    @Override
    public String toString() {
        return "CollectionDI{" +
                "\\n" + "list=" + list +
                "\\n" + ", set=" + set +
                "\\n" + ", map=" + map +
                "\\n" + ", properties=" + properties +
                使用Spring框架入门二:基于注解+XML配置的IOC/DI的使用

Java--Spring之IoC控制反转;基于XML配置文件的DI

Spring 框架的概述以及Spring中基于XML的IOC配置

Java--Spring之IoC控制反转;基于注解的DI

阶段3 2.Spring_03.Spring的 IOC 和 DI_3 spring基于XML的IOC环境搭建和入门

框架 day36 Spring3 入门,DI依赖注入,装配bean基于xml/注解, 整合Junit4,配置约束自动提示