您的位置:首页 > 编程语言 > Java开发

深入理解Spring4框架(四)——依赖

2017-10-31 23:06 323 查看
     一个典型的企业应用不止包含一个对象,即使是一个简单的应用,也是由几个对象合作来完成用户需要使用的功能。本节将介绍定义的Bean是如何在真实系统中配合来完成既定功能的。

   依赖注入就是在对象构建完成后,为其定义依赖的过程。容器会在创建Bean的时候将这些依赖注入进去。这个过程跟我们通常编码不同,平常我们都需要在代码中去new一个对象,而这里把这个操作交给容器了,由Bean自己来控制依赖的实例化,与通常的方法是相反的,称之为控制反转(IoC)。

    使用依赖注入的话,代码会变得更加清晰,也将对象和依赖进行了解耦。

1 基于构造器的依赖注入

    基于构造器的依赖注入是由容器来调用一个带有多个参数的构造器,和调用静态工厂方法来构建Bean类似。下面的例子表示一个仅通过构造器进行依赖注入的类。

[java] view
plain copy

public class SimpleMovieLister {  

    private MovieFinder movieFinder;  

    public SimpleMovieLister(MovieFinder movieFinder) {  

        this.movieFinder = movieFinder;  

    }  

}  

2 构造器参数解析

    构造器使用参数类型来进行参数匹配。如果没有参数冲突,那么在Bean定义中的参数顺序即可确定一个构造器。看下面一个例子:

[java] view
plain copy

public class Foo {  

    public Foo(Bar bar, Baz baz) {  

        // ...  

    }  

}  

    没有冲突的参数,假设Bar和Baz没有继承关系。因此下面的配置可以正常运行,而不需要在<constructor-arg/>中指定index或type。

[xml] view
plain copy

<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</value>,若不显示地为其指定类型,那么Spring是不能确定这个value是什么类型的。

[java] view
plain copy

public class ExampleBean {  

    private int years;  

    private String ultimateAnswer;  

    public ExampleBean(int years, String ultimateAnswer) {  

        this.years = years;  

        this.ultimateAnswer = ultimateAnswer;  

    }  

}  

    在之前的情况中,容器可以通过指定的type属性来使用类型匹配,例如:

[xml] view
plain copy

<bean id="exampleBean" class="examples.ExampleBean">  

    <constructor-arg type="int" value="7500000"/>  

    <constructor-arg type="java.lang.String" value="42"/>  

</bean>  

    使用index属性来显示指定构造器参数的顺序:

[xml] view
plain copy

<bean id="exampleBean" class="examples.ExampleBean">  

    <constructor-arg index="0" value="7500000"/>  

    <constructor-arg index="1" value="42"/>  

</bean>  

    当遇到多个简单类型的参数时,可以为其指定index来解决冲突。

    也可以使用构造器参数名字来区分:

[xml] view
plain copy

<bean id="exampleBean" class="examples.ExampleBean">  

    <constructor-arg name="years" value="7500000"/>  

    <constructor-arg name="ultimateAnswer" value="42"/>  

</bean>  

3 基于Setter的依赖注入

    基于Setter的依赖注入,是在Bean完成实例化之后,容器调用Bean的setter方法来完成的。

    下面的类表明一个类只能使用纯粹的setter来注入依赖:

[java] view
plain copy

public class SimpleMovieLister {  

    private MovieFinder movieFinder;  

    public void setMovieFinder(MovieFinder movieFinder) {  

        this.movieFinder = movieFinder;  

    }  

}  

    ApplicationContext支持对其管理的类使用基于构造器的注入和基于Setter的注入。它也支持在一些依赖使用基于构造器注入之后,再使用基于Setter的注入。

通常,我们使用基于构造器的注入来注入一些必选的依赖,而使用基于Setter的注入来注入一些可选的依赖。

4 依赖解析过程

    容器会如下所示执行依赖解析:

(1)ApplicationContext创建之后,会对配置元数据进行初始化。

(2)当Bean被创建时,它的依赖就会被注入。

(3)每个属性或构造器参数实际上就是一个待设置的值,或者是容器中另一个Bean的引用。

(4)每个属性或构造器参数会被转换为实际的类型。默认情况下,Spring可以将String类型的值转换为int, long, boolean等基本类型。

    Spring在容器创建时,会验证每个Bean的配置。然而,Bean属性是不会被设置的,直到Bean实际上已被创建。那些是单例作用域和被设置为预实例化(默认)的Bean,将会在容器创建时被同时创建。否则,Bean会在需要的时候才会被创建。一个Bean的创建潜在地导致了一系列Bean的创建。

    如果两个Bean相互依赖,且均采用基于构造器的注入时,会报BeanCurrentlyInCreationException异常。可以将其改为基于Setter的注入就可以解决这种循环依赖的问题。

5 使用depends-on

当一个Bean需要在其依赖的所有Bean都初始化结束之后才能执行初始化,那么就可以使用depends-on属性来指定依赖。

[xml] view
plain copy

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">  

    <property name="manager" ref="manager" />  

</bean>  

<bean id="manager" class="ManagerBean" />  

<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />  

6 懒加载Bean

    默认情况下,ApplicationContext的实现会及时创建和配置所有的单例Bean,这是ApplicationContext初始化流程的一部分。一般而言,这种预初始化正是我们所期待的,因为可以立即发现配置或环境错误。如果不想预初始化,那么可以为Bean定义添加懒加载属性来阻止预初始化。一个懒加载Bean会告知容器,只有在它第一次被请求时才会创建一个Bean实例,而不是启动的时候。

    在XML中,懒加载通过lazy-init属性来控制的。

[xml] view
plain copy

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>  

<bean name="not.lazy" class="com.foo.AnotherBean"/>  

    然而,当一个单例非懒加载Bean依赖一个懒加载Bean时,ApplicationContext会在启动时创建这个懒加载Bean,因为它必须满足非懒加载Bean的依赖。

7 自动注入

    Spring容器可以为Bean自动注入依赖。

(1)自动注入可以极大减少对属性或构造器参数的需要。

(2)自动注入不需要在依赖改变时修改配置文件。

    当使用基于XML的配置元数据时,可以使用autowire属性来指定一个Bean的自动注入模式。自动注入功能由四种模式:

(1)no:这是默认的,不会自动注入。在大型部署中不建议修改默认配置,因为显示地指定依赖会更加清晰和便于控制。

(2)byName:通过属性名称来自动注入。Spring会查找和属性名称相同的Bean来自动注入。

(3)byType:如果恰好有一个Bean与指定的类型相同,那么自动注入这个Bean。如果这个类型的Bean超过一个,会抛出错误异常,也就意味着不能使用byType来自动注入。

(4)constructor:跟byType类似,但是适用于构造器参数。如果容器中不存在指定构造器参数类型的Bean,或者多余一个的,那么会抛出错误异常。

自动注入的限制和缺点

    当在一个工程中均采用自动注入时,自动注入会表现得很好。若不是经常使用,而只用自动注入来注入一个或两个Bean,那么这样会使得开发者感觉疑惑。

    考虑以下限制和缺点:

(1)在property和constructor-arg设置的显示依赖总是会覆盖自动注入。

(2)自动注入没有显示指定那么准确。也许,你不太容易找到Bean所依赖的真正实现Bean。

(3)不太容易通过工具来生成文档。

(4)指定类型也许会匹配到多个Bean。

    在Spring的XML配置文件中,将<bean>元素的autowire-candidate属性设置为false,容器将不会自动注入这个bean。

8 方法注入

    在很多应用场景中,大多数Bean都是单例的。当一个单例Bean依赖另一个单例Bean;或者当一个非单例Bean需要依赖另一个非单例Bean,只需要将其中一个Bean定义为另一个Bean的属性。当Bean的生命周期不同时,问题就来了。假如单例Bean A需要使用一个非单例Bean B,容器只会创建单例Bean A一次,仅有一次机会来设置属性。容器不能在每次调用Bean
A时提供一个新的Bean B实例。

    一个解决办法就是,将Bean A实现ApplicationContextAware接口,每次A需要B的时候,通过getBean(“B”)来获取一个新的Bean B。

[java] view
plain copy

public class CommandManager implements ApplicationContextAware {  

    private ApplicationContext applicationContext;  

    public Object process(Map commandState) {  

        // 获取一个新的Command实例  

        Command command = createCommand();  

        // 在Command实例上设置状态  

        command.setState(commandState);  

        return command.execute();  

    }  

    protected Command createCommand() {  

        // 注意SpringAPI依赖  

        return this.applicationContext.getBean("command", Command.class);  

    }  

    public void setApplicationContext(  

            ApplicationContext applicationContext) throws BeansException {  

        this.applicationContext = applicationContext;  

    }  

}  

    还可以换一种写法,如下:

[java] view
plain copy

public abstract class CommandManager {  

    public Object process(Object commandState) {  

        Command command = createCommand();  

        command.setState(commandState);  

        return command.execute();  

    }  

    protected abstract Command createCommand();  

}  

    上面的写法简洁了很多,但是createCommand()是一个抽象方法,具体的实现类去哪儿了?

[xml] view
plain copy

<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">  

</bean>  

<bean id="commandManager" class="fiona.apple.CommandManager">  

    <lookup-method name="createCommand" bean="command"/>  

</bean>  

    这里,<lookup-method />标签用于指定createCommand()的实现类,这样,CommandManager就解耦了Spring框架代码和业务代码。Spring框架使用CGLIB库来动态生成CommandManager的子类,这个子类覆盖了createCommand()这个抽象方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: