美文网首页spring4.x
spring IOC容器bean依赖注入

spring IOC容器bean依赖注入

作者: 不迷失 | 来源:发表于2016-05-14 17:14 被阅读632次

    依赖
    企业应用绝不会只有1个简单对象(或者说Spring bean)。哪怕是最简单的应用,也会包含许多对象协同工作。下一章节讲述,如何为真正的应用定义大量的、独立的bean,并让这些对象一起合作。
    依赖注入是一个有对象定义依赖的手法,也就是,如何与其他对象合作,通过构造参数、工厂方法参数、或是在对象实例化之后设置对象属性,实例化既可以构造也可以是使用工厂方法。容器在它创建bean之后注入依赖。这个过程从根本上发生了反转,因此又名控制反转(Ioc),因为Spring bean自己控制依赖类的实例化或者定位 ,Spring bean中就有依赖类的定义,容器使用依赖类构造器创建依赖类实例。
    DI机制使代码简洁,对象提供它们的依赖,解耦更高效。对象无需自己查找依赖。同样的,类更容易测试,尤其当依赖接口或者抽象类时,测试允许在单元测试中使用stub或者mock(模拟技术)实现。
    DI有2种主要方式,构造注入,容器调用构造函数并传参数,每个参数都是依赖。调用静态工厂方法并传参数方式构造bean和构造注入差不多,这里是指构造注入处理参数和静态工厂方法处理参数像类似。下例中展示了一个只能使用构造注入的类。注意,此类无任何特别之处,并未依赖容器指定的接口、基类、注解,就是一个POJO。

    public class SimpleMovieLister {
    
        // the SimpleMovieLister 依赖 a MovieFinder
        private MovieFinder movieFinder;
    
        //Spring容器能注入MovieFinder的构造函数
        public SimpleMovieLister(MovieFinder movieFinder) {
            this.movieFinder = movieFinder;
        }
    
        // 实际如何使用MovieFinder的业务逻辑省略了
    
    }
    
    构造参数解决方案,会匹配所使用的参数类型。如果在bean的定义中,构造参数不存在歧义,那么,在bean定义中定义的构造参数的次序,在bean实例化时,就是提供给适合的构造参数的次序。看这个类:
    
    package x.y;
    
    public class Foo {
    
        public Foo(Bar bar, Baz baz) {
            // ...
        }
    
    }
    不存在歧义,假设Bar和Baz类没有集成关系,那么下面的配置是合法的,而且,不需要在<constructor-arg/>元素里指定构造参数的明确的indexes索引或者类型。
    
    <beans>
        <bean id="foo" class="x.y.Foo">
            <constructor-arg ref="bar"/>
            <constructor-arg ref="baz"/>
        </bean>
    
        <bean id="bar" class="x.y.Bar"/>
    
        <bean id="baz" class="x.y.Baz"/>
    </beans>
    若需要引用另一个bean,类型已知,构造函数就可以匹配参数类型(像上面的示例)。使用简单类型时, 想<value>true</true>,Srping不能决定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>
    当构造函数有2个相同类型的参数,指定次序可以解决此种情况。注意index是从0开始
    
        <bean id="exampleBean" class="examples.ExampleBean">
            <constructor-arg name="years" value="7500000"/>
            <constructor-arg name="ultimateAnswer" value="42"/>
        </bean>
    

    setter注入

    Setter注入是容器调用bean上的setter方法,bean是使用无参构造函数返回的实例,或者无参静态工厂方法返回的实例。 下面样例中展示了只能使用Setter注入的类。这个类是传统java类,就是个POJO,不依赖容器指定的接口、基类、注解。

    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...
    }
    

    ApplicationContext对它所管理的bean支持构造注入和setter注入。也支持先构造注入再setter注入。定义依赖,会转换成某种形式的BeanDefinition类,BeanDefinition类与PropertyEditor实例配合,即可将属性从一种格式转换成其他格式。然而,大多数程序员不会直接使用这些类(也就是编程式),更多的是使用XML、注解(也就是@Component@Controller等等),或者@Configuration注解的类中的方法上使用 @Bean。这些配置数据,都会在容器内部转换成BeanDefinition,用于加载整个Spring Ioc 容器。

    xml配置中的直接赋值(原始类型、String等等)
    <property />元素的value属性为对象域属性或者构造参数设置了一个可读的字串。Spring的会将其转换为实际的与属性或者参数的数据类型。

    <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>
    

    也可以这样配java.unit.Properties实例:

    <bean id="mappings" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <!-- 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>
    

    引用其他bean(协作类)
    ref元素是<constructor-arg/>元素和<property/>元素内决定性元素。用它设置bean的属性以引用另一个容器管理的bean。引用的bean就是要设置属性的bean的依赖,在设置属性值之前它就要被初始化。(如果协作类是单例bean,它会在容器初始化时首先完成初始化)。差不多所有的bean都会引用其他对象。指定id/name的对象的作用域和依赖校验通过bean,local ,parent属性来配置。 指定引用bean通常使用<ref/>标签,它允许引用本容器或者父容器中任意的bean,无需配置在同一个xml文件中 。<ref/>标签中bean的属性值,使用的被引用bean的id或者name。

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
    
        <bean id="CustomerBean" class="com.example.common.Customer">
            <property name="person" ref="PersonBean" />
        </bean>
    
        <bean id="PersonBean" class="com.example.common.Person">
            <property name="name" value="MrChen" />
            <property name="address" value="address1" />
            <property name="age" value="28" />
        </bean>
    
    </beans>
    

    集合

    <list/>,<set/>,<map/>,<props/>元素,用来设置Java Collection属性和参数,分别对应List,Set,Map,Properties

    <bean id="moreComplexObject" class="example.ComplexObject">
        <!--调用setAdminEmails(java.util.Properties) -->
        <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>
        <!-- 调用setSomeList(java.util.List) -->
        <property name="someList">
            <list>
                <value>a list element followed by a reference</value>
                <ref bean="myDataSource" />
            </list>
        </property>
        <!-- 代用setSomeMap(java.util.Map) -->
        <property name="someMap">
            <map>
                <entry key="an entry" value="just some string"/>
                <entry key ="a ref" value-ref="myDataSource"/>
            </map>
        </property>
        <!-- 调用 setSomeSet(java.util.Set) -->
        <property name="someSet">
            <set>
                <value>just some string</value>
                <ref bean="myDataSource" />
            </set>
        </property>
    </bean>
    

    构造注入对比setter注入
    何时使用构造注入,何时使用setter注入,经验法则是:强制依赖用构造,可选依赖用Setter。注意,在settter方法上使用@Required注解即可另属性强制依赖。
    Spring 团队建议,构造注入的实例是不可变的,不为null的。此外,构造注入组件要将完全初始化后的实例返回给客户端代码。还有,大量参数的构造函数是非常烂的,它意味着该类有大量的职责,得重构。
    setter注入主要用于可选依赖,类内部可以指定默认依赖。否则类内所有使用依赖的地方,都得进行非空校验。setter注入的有个好处就是,类可以重配置或者再注入。因此,使用JMX MBeans
    进行管理的场景中,就非常适合setter注入。
    使用何种依赖注入方式,对于某些类,非常有意义。有时协同第三方类处理,没有源码,由你来决定使用何种方式。比如,第三方类未暴露任何setter方法,那么构造注入也许就是唯一的可行的注入方式了。

    依赖处理过程
    容器解析bean依赖如下:
    ApplicationContext创建后用配置元数据中描述的所有bean进行初始化。配置元数据格式可以是XML、Java Code,或者注解。
    每个bean的依赖,都会以下列形式表达:属性、构造参数,静态工厂方法的参数。当bean真正的创建时,这些依赖会被提供给bean。
    每个属性或者构造函数或者以value值形式在bean处直接设置,或者引用容器中其他bean。
    每一个属性或者构造参数都是一个值,该值将会从指定的格式转换为属性、构造参数的真正类型。Spring默认会将一个String类value转换成内建类型,比如int,long,String,boolean等等。
    Spring容器在创建bean之前会验证bean的配置。在bean创建之前,bean的属性不会赋值。当容器创建之后,会创建被设置为预先初始化的sington-scope单例作用域bean,非单例作用域bean,只有在请求时才会创建。

    微信公众号: java技术
    技术交流群: 245130488

    相关文章

      网友评论

        本文标题:spring IOC容器bean依赖注入

        本文链接:https://www.haomeiwen.com/subject/zdamrttx.html