02、IoCContainer-2 依赖

依赖

  1. 依赖注入(Dependency Injection)

    依赖注入主要有两种形式:

    基于构造器的依赖注入

    在改方式下,容器调用构造器,并向构造器传入参数,每个参数都是一个依赖。向静态工厂方法传递参数与向构造器传参一样。

    下面这个类只能通过构造器注入进行依赖注入

    public class SimpleMovieLister {
    
        // the SimpleMovieLister has a dependency on a MovieFinder
        private MovieFinder movieFinder;
    
        // a constructor so that the Spring container can inject a MovieFinder
        public SimpleMovieLister(MovieFinder movieFinder) {
            this.movieFinder = movieFinder;
        }
    
        // business logic that actually uses the injected MovieFinder is omitted...
     }
    
    

    构造器参数解析

    构造器参数解析匹配发生在使用参数类型。如果在bean的定义中不存在潜在的歧义,那么bean定义中参数的顺序与构造bean时向构造器提供的参数顺序对应。

    package x.y;
    public class ThingOne {
    
        public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
            // ...
        }
    }
    

    假设ThingTowThingThree没有继承关系,不存在潜在的歧义,那么下面的配置将会正常工作。我们不需要在<onstructor-arg>中指定参数的顺序或者类型

    <beans>
        <bean id="beanOne" class="x.y.ThingOne">
            <constructor-arg ref="beanTwo"/>
            <constructor-arg ref="beanThree"/>
        </bean>
    
        <bean id="beanTwo" class="x.y.ThingTwo"/>
    
        <bean id="beanThree" class="x.y.ThingThree"/>
    </beans>
    

    当一个bean被引用,这个bean的类型就是已知的,参数匹配能够正常工作。但是当引用一个基本类型时,如<value>true</value>,Spring就不能确定这个参数的类型了,此时就需要在配置中指定参数类型。

    package examples;
    
    public class ExampleBean {
    
         // Number of years to calculate the Ultimate Answer
         private int years;
    
         // The Answer to Life, the Universe, and Everything
         private String ultimateAnswer;
    
         public ExampleBean(int years, String ultimateAnswer) {
             this.years = years;
             this.ultimateAnswer = ultimateAnswer;
         }
    }
    

    在上面的情形中,如果我们使用type属性明确指定了参数的类型,那么容器就可以匹配基本类型参数,如下:

    <bean id="exampleBean" class="examples.ExampleBean">
       <constructor-arg type="int" value="7500000"/>
       <constructor-arg type="java.lang.String" value="42"/>
    </bean>
    

    也可以使用index属性,明确指定参数的顺序:

    <bean id="exampleBean" class="examples.ExampleBean">
        <constructor-arg index="0" value="7500000"/>
        <constructor-arg index="1" value="42"/>
    </bean>
    

    当有两个属性类型相同时,使用index解决歧义就很必要了。

    也可以使用name属性指定构造器参数的姓名:

    <bean id="exampleBean" class="examples.ExampleBean">
        <constructor-arg name="years" value="7500000"/>
        <constructor-arg name="ultimateAnswer" value="42"/>
    </bean>
    

    要使上面的配置有效,需要使用debug模式编译,否则应该在构造器上使用@ConstructorProperties进行注解:

    package examples;
    
    public class ExampleBean {
    
        // Fields omitted
    
        @ConstructorProperties({"years", "ultimateAnswer"})
        public ExampleBean(int years, String ultimateAnswer) {
            this.years = years;
            this.ultimateAnswer = ultimateAnswer;
         }
    }
    

    基于修改器的依赖注入

    修改器依赖注入是容器在调用无参构造函数或者无参工厂方法构建bena之后调用bean的修改器方法注入依赖。

    下面这个类只能通过修改器注入进行依赖注入:

    package x.y;
    public class SimpleMovieLister {
    
        // the SimpleMovieLister has a dependency on the MovieFinder
        private MovieFinder movieFinder;
    
        // a setter method so that the Spring container can inject a MovieFinder
        public void setMovieFinder(MovieFinder movieFinder) {
           this.movieFinder = movieFinder;
        }
    
        // business logic that actually uses the injected MovieFinder is omitted...
    }
    
    <bean id="movieFinder" class="x.y.MovieFinder"/>
    <bean id="simpleMovieLister" class="x.y.SimpleMovieLister">
      <property name="movieFinder" ref="movieFinder"/>
    </bean>
    

    基于构造器的依赖注入适合用于注入哪些必要的依赖,基于修改器的依赖注入适合那些非必要依赖的注入。对修改器进行@Required注解,将会使这个依赖称为必要依赖。

  2. 依赖解决流程

    容器完成依赖解析的流程如下:

    • ApplicationContext被创建并根据配置元数据进行初始化。
    • 当一个bean被创建后,它的依赖便以属性、构造器参数或者是静态工厂方法参数的形式提供出来。
    • 每一个属性或者是参数都有一个定义了的值或者是对容器中其他bean的引用
    • 为属性和参数配置的值都将被转换为这些属性和参数对应的类型。默认的,Spring可以将字符串类型的值转换为任意的基础类型。

    Bean默认是单例预实例化的,在容器被创建之后,Bean就会被创建。这可以避免在应用运行时抛出一些初始化Bean的异常,让我们在容器初始化时就发现配置上出现的问题。

    循环依赖

    如果我们主要使用构造器注入,那么可能会造成循环依赖。一个解决的办法就是,将其中一个依赖的注入方式改为修改器注入,或者全部使用修改器注入。虽然不提倡使用修改器注入,但这的确可以解决循环依赖的问题。

    如果A依赖B,那么容器会在实例化B,并完成B的依赖注入之后,再讲B注入A。

  3. 依赖和配置详情

    • 直接赋值(value属性):

    <property>或者<constructor-arg>标签的value属性可以为bean的属性或者构造器参数赋值String类型的值。Spring的转换服务会将这些值转换为与bean属性或构造器参数对应的类型。如:

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <!-- results in a setDriverClassName(String) call -->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
        <property name="username" value="root"/>
        <property name="password" value="masterkaoli"/>
    </bean>
    

    使用p命名空间是的xml配置更加的简洁:

     <beans xmlns="http://www.springframework.org/schema/beans"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:p="http://www.springframework.org/schema/p"
         xsi:schemaLocation="http://www.springframework.org/schema/beans
         https://www.springframework.org/schema/beans/spring-beans.xsd">
    
         <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
             destroy-method="close"
             p:driverClassName="com.mysql.jdbc.Driver"
             p:url="jdbc:mysql://localhost:3306/mydb"
             p:username="root"
             p:password="masterkaoli"/>
    </beans>
    

    我们也可以定义一个java.util.Properties实例:

    <bean id="mappings"
       class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    
       <!-- typed as a java.util.Properties -->
       <property name="properties">
           <value>
               jdbc.driver.className=com.mysql.jdbc.Driver
               jdbc.url=jdbc:mysql://localhost:3306/mydb
           </value>
       </property>
    </bean>
    

    通过使用JavaBeans的PropertyEditor机制,Spring容器将<value>标签内的文字转换为java.util.Properties实例。

    • <idref>元素
    <bean id="theTargetBean" class="..."/>
    
    <bean id="theClientBean" class="...">
        <property name="targetName">
            <idref bean="theTargetBean"/>
        </property>
    </bean>
    

    上面的配置同下面的配置作用相同:

    <bean id="theTargetBean" class="..." />
    
    <bean id="client" class="...">
        <property name="targetName" value="theTargetBean"/>
    </bean>
    

    但是第一种配置要优于第二种配置,因为第一种配置在应用部署时,容器会检验refid的引用是否存在;第二种配置则不会检查,错误的拼写只有在clientbean被初始化时,才会发现这个引用根本不存在而抛出错误。

    • <ref>元素

    <ref>元素是<property><constructor-arg>元素中的终极元素。

    通过<ref>中的bean属性来设置bean的依赖,这个依赖可以是同一个容器中的bean,也可以是父容器中的bean,所以来的bean也不一定是在同一个xml文档中定义的。bean属性的值可以是所依赖bean的id的值,或者所依赖bean的一个name值。

    <ref bean="someBean"/>
    

    通过<ref>元素的parent元素来设置bean的依赖,该依赖bean必须在当前容器的一个父容器中,parent属性的值可以是所依赖bean的id的值,或者所依赖bean的一个name值。
    使用parent属性的主要用途是为了用某个与父容器中的bean同名的代理来包装父容器中的bean。

    <!-- in the parent context -->
    <bean id="accountService" class="com.something.SimpleAccountService">
        <!-- insert dependencies as required as here -->
    </bean>
    
    <!-- in the child (descendant) context -->
    <!-- bean name is the same as the parent bean -->
    <bean id="accountService" 
        class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target">
            <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
        </property>
        <!-- insert other configuration and dependencies as required here -->
    </bean>
    
    • 内部bean

    <property>标签或者<constructor-arg>标签的内部使用<bean>标签来定义一个内部bean:

    <bean id="outer" class="...">
        <!-- instead of using a reference to a target bean, simply define the target bean inline -->
        <property name="target">
            <bean class="com.example.Person"> <!-- this is the inner bean -->
                <property name="name" value="Fiona Apple"/>
                <property name="age" value="25"/>
            </bean>
        </property>
    </bean>
    

    内部bean不需要定义id,即使定义了,容器也会忽略。同时,容器也会忽略内部bean的scope标志,因为内部bean是匿名的且总是同它的外部bean一起被创建。内部bean无法被独立访问,也无法注入到除他的外部bean以外的其它bean中。

    • 集合

    <list><set><map><props>元素用来设置Java的ListSetMapProperties类型的属性和参数。

    <bean id="moreComplexObject" class="example.ComplexObject">
        <!-- results in a setAdminEmails(java.util.Properties) call -->
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@example.org</prop>
                <prop key="support">support@example.org</prop>
                <prop key="development">development@example.org</prop>
            </props>
        </property>
        <!-- results in a setSomeList(java.util.List) call -->
        <property name="someList">
            <list>
                <value>a list element followed by a reference</value>
                <ref bean="myDataSource" />
            </list>
        </property>
        <!-- results in a setSomeMap(java.util.Map) call -->
        <property name="someMap">
            <map>
                <entry key="an entry" value="just some string"/>
                <entry key ="a ref" value-ref="myDataSource"/>
            </map>
        </property>
        <!-- results in a setSomeSet(java.util.Set) call -->
        <property name="someSet">
            <set>
                <value>just some string</value>
                <ref bean="myDataSource" />
            </set>
        </property>
    </bean>
    

    map的keyvalue的值,以及set的value值可以是:bean | ref | idref | list | set | map | props | value | null

    • 集合合并

    Spring容器支持子集合继承父集合,并覆盖父集合中的一些值。

    <beans>
      <!-- abstract=“true”,容器不会实例化这个bean,该bean只会作为子bean的模板 -->
       <bean id="parent" abstract="true" class="example.ComplexObject">
           <property name="adminEmails">
               <props>
                   <prop key="administrator">administrator@example.com</prop>
                   <prop key="support">support@example.com</prop>
               </props>
           </property>
       </bean>
       <!-- 继承父bean的类型 -->
       <bean id="child" parent="parent">
           <property name="adminEmails">
               <!-- the merge is specified on the child collection definition -->
               <props merge="true">
                  <!-- 比父bean多的值 -->
                   <prop key="sales">sales@example.com</prop>
                   <!-- 覆盖父bean support的值 -->
                   <prop key="support">support@example.co.uk</prop>
               </props>
           </property>
       </bean>
    <beans>
    

    对于<list/>,值是有序的,父bean的值在子bean之前;对于<set/>、<map>和<props>而言,值是无序的。

  • 强类型集合

    如果我们在定义类的集合属性时,擦除了类型,那么在配置时,可以为这些集合配置任意类型的元素。如果我们确定了类型,得益于Spring的类型转换机制,容器可以将String类型的值转换为对应的类型。

  1. NULL和空值

     <bean class="ExampleBean">
         <property name="email" value=""/>
     </bean>
    

    是相当于:

    exampleBean.setEmail("");
    
     <bean class="ExampleBean">
         <property name="email">
             <null/>
         </property>
     </bean>
    

    相当于:

     exampleBean.setEmail(null);
    
  2. p命名空间

    p命名空间 使得我们使用<bean>的属性而非<property>元素来配置bean的依赖。

    普通的XML配置结构在XML Schema Document中有定义,但是p命名空间只在Spring Core中被定义。

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:p="http://www.springframework.org/schema/p"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!-- 两个bean的作用相同 -->
        <bean name="classic" class="com.example.ExampleBean">
            <property name="email" value="someone@somewhere.com"/>
        </bean>
    
        <bean name="p-namespace" class="com.example.ExampleBean"
            p:email="someone@somewhere.com"/>
         <!-- 使用p命名空间定义一个依赖 -->
        <bean name="p-modern" class="com.example.OtherExampleBean"
          p:detail-ref="classic"/>
    </beans>
    

    在第三个bean中,使用p:detail-ref来创建一个从p-modernclassic的引用。其中,detail是变量名,-ref表示这不是一个直接赋值,而是一个对其它bean的引用。

  3. c命名空间

    同p命名空间一样,使用c命名空间使得我们使用<bean>的属性而非<constructor-arg>元素来通过构造器注入依赖。

    <beans xmlns="http://www.springframework.org/schema/beans"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:c="http://www.springframework.org/schema/c"
         xsi:schemaLocation="http://www.springframework.org/schema/beans
             https://www.springframework.org/schema/beans/spring-beans.xsd">
    
         <bean id="beanTwo" class="x.y.ThingTwo"/>
         <bean id="beanThree" class="x.y.ThingThree"/>
    
         <!-- traditional declaration with optional argument names -->
         <bean id="beanOne" class="x.y.ThingOne">
             <constructor-arg name="thingTwo" ref="beanTwo"/>
             <constructor-arg name="thingThree" ref="beanThree"/>
             <constructor-arg name="email" value="something@somewhere.com"/>
         </bean>
    
         <!-- c-namespace declaration with argument names -->
         <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
             c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
    
     </beans>
    

    c命名空间的使用规则于p命名空间相同。

    一种少见的情况:当我们无法知道构造器入参的名称是,我们可以使用构造参数的序号:

    <!-- c-namespace index declaration -->
    <bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
        c:_2="something@somewhere.com"/>
    

    由于XML的语法:XML的属性名不能以数字开始,所以上述以序列符号需要一_开头。这种命名结构在<constructor-arg>元素中也可以使用,
    但是并不推荐使用,因为这个元素有index属性,同时,<constructor-arg>元素的顺序也可以作为序号。

  4. 混合元素名

    当我们在设置bean属性时,我们可以使用混合元素名/嵌套元素名

    <bean id="something" class="things.ThingOne">
         <property name="fred.bob.sammy" value="123" />
     </bean>
    

    上面的配置中,somethingbean有一个fred属性,fred属性有一个sammy属性,并且我们为sammy赋值为123。为了让上面的配置生效,需要在bean被构造后保证fredbob属性不为空,所以这里必须保证定义things.ThingOne时,在构造器中对fred进行了初始化,bob属性同理。或者在bean定义中,为fred设置引用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值