Spring核心技术IoC容器(四)

前面两篇文章描述了IoC容器中依赖的概念,包括依赖注入以及注入细节配置。本文将继续描述玩全部的依赖信息。

使用 depends-on

如果一个Bean是另一个Bean的依赖的话,通常来说这个Bean也就是另一个Bean的属性之一。多数情况下,开发者可以在配置XML元数据的时候使用<ref/>标签。然而,有时Bean之间的依赖关系不是直接关联的。比如:需要调用类的静态实例化器来出发,类似数据库驱动注册。depends-on属性会使明确的强迫依赖的Bean在引用之前就会初始化。下面的例子使用depends-on属性来让表示单例Bean上的依赖的。

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

如果想要依赖多个Bean,可以提供多个名字作为depends-on的值,以逗号,空格,或者分号分割,如下:

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

Bean中的depends-on属性可以同时指定一个初始化时间的依赖以及一个相应的销毁时依赖(单例Bean情况)。独立的定义了depends-on属性的Bean会优先销毁,优于depends-on的Bean来销毁,这样depends-on可以控制销毁的顺序。

延迟初始化的Bean

默认情况下,ApplicationContext会在实例化的过程中创建和配置所有的单例Bean。总的来说,这个预初始化是很不错的。因为在环境上的一些配置错误能够及时的发现,而不是系统运行了很久之后才发现。如果这个行为不是迫切需要的,开发者可以通过将Bean标记为延迟加载就能阻止这个预初始化。延迟初始化的Bean会通知IoC不要让Bean预初始化而是在被引用的时候才会实例化。

在XML中,可以通过<bean/>元素的lazy-init属性来控制这个行为。如下:

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

当将Bean配置为上面的XML的时候,ApplicationContext之中的lazyBean是不会随着ApplicationContext的启动而进入到预初始化状态的,而那些非延迟加载的Bean是处于预初始化的状态的。

然而,如果一个延迟加载的类是作为一个单例非延迟加载的Bean的依赖而存在的话,ApplicationContxt仍然会在ApplicationContext启动的时候加载,因为作为单例Bean的依赖,会随着单例Bean的实例化而实例化。

开发者可以通过使用<beans/>default-lazy-init属性来再容器层次控制Bean是否延迟初始化,比如:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

自动装配关联

Spring容器可以自动装配关联的Bean之间的依赖关系。开发者可以令Spring通过ApplicationContext来自动解析这些关联。自动的装载有很多的有点:

  • 自动装载能够明显的减少指定的属性或者是构造参数。
  • 自动装载可以扩展开发者的对象。比如说,如果开发者需要加一个依赖,依赖就能够不需要开发者特别关心更改配置就能够自动满足。这样,自动装载在开发过程中是极度有用的,不用明确的选择装载的依赖会使系统更加的稳定。

当使用基于XML的元数据配置的时候,开发者可以指定自动装配的方式。通过配置<bean/>元素的autowire属性就可以了。自动装载有如下四种方式,开发者可以指定每个Bean的方式,这样Bean就知道如何加载自己的依赖。

模式 解释
no (默认)不装载。Bean的引用必须通过ref元素来指定。对于比较大项目的部署,不建议修改默认的配置,因为特指会加剧控制。在某种程度上来说,默认的形式也说明了系统的结构。
byName 通过名字来装配。Spring会查找所有的Bean知道名字和属性相同的一个Bean来进行装载。比如说,如果Bean配置为根据名字来自动装配,它包含了一个属性名字为master(也就是包含一个setMaster(..)方法),Spring就会查找名字为master的Bean,然后用之装载
byType 如果需要自动装配的属性的类型在容器之中存在的话,就会自动装配。如果容器之中存在不止一个类型匹配的话,就会抛出一个重大的异常,说明开发者最好不要使用byType来自动装配那个Bean。如果没有匹配的Bean存在的话,不会抛出异常,只是属性不会配置。
构造函数 类似于byType的注入,但是应用的构造函数的参数。如果没有一个Bean的类型和构造函数参数的类型一致,那么仍然会抛出一个重大的异常

通过 byType 或者 构造函数 的自动装配方式,开发者可以装在数组和强类型集合。在如此的例子之中,所有容器之中的匹配指定类型的Bean会自动装配到Bean上来完成依赖注入。开发者可以自动装配key为String的强类型的Map。自动装配的Map值会包含所有的Bean实例值来匹配指定的类型,Map的key会包含关联的Bean的名字。

自动装配的限制和劣势

自动装备如果在整个的项目的开发过程中使用,会工作的很好。但是如果不是全局使用,而只是用之来自动装配几个Bean的话,会很容迷惑开发者。

下面是一些自动装配的劣势和限制

  • 精确的property以及constructor-arg参数配置,会覆盖掉自动装配的配置。开发不能够自动装配所谓的简单属性,比如Primitive类型或者字符串。
  • 自动装配并有精确装配准确。尽管如上面的表所描述,Spring会尽量小心来避免不必要的错误装配,但是Spring管理的对象关系仍然不如文档描述的那么精确。
  • 装配的信息对开发者可见性不好,因为由Spring容器管理。
  • 容器中的可能会存在很多的Bean匹配Setter方法或者构造参数。比如说数组,集合或者Map等。然而依赖却希望仅仅一个匹配的值,含糊的信息是无法解析的。如果没有独一无二的Bean,那么就会抛出异常。

在后面的场景,开发者有如下的选择

  • 放弃自动装配有利于精确装配
  • 可以通过配置autowire-candidate属性为false来阻止自动装配
  • 通过配置<bean/>元素的primary属性为true来指定一个bean为主要的候选Bean
  • 实现更多的基于注解的细粒度的装配配置。

排除一个Bean,使之不自动装配

在每个Bean的基础之上,开发者可以阻止Bean来自动装配。在基于XML的配置中,可以配置<bean/>元素的autowire-candidate属性为false来做到这一点。容器在读取到这个配置后,会让这个Bean对于自动装配的结构中不可见(包括注解形式的配置比如@Autowired

开发者可以通过模式匹配而不是Bean的名字来限制自动装配的候选者。最上层的<beans/>元素会在default-autowire-candidates属性中来配置多种模式。比如,限制自动装配候选者的名字以Repository结尾,可以配置*Repository。如果需要配置多种模式,只需要用逗号分隔开即可。当然Bean中如果配置了autowire-candidate的话,这个信息拥有更高的优先级。

上面的这些技术在配置那些不需要自动装配的Bean是很有效的。当然这并不是说这类Bean本身无法自动装配其他的Bean,而是说这些Bean不在作为自动装配依赖的候选了。

方法注入

在大多数的应用场景下,大多数的Bean都是单例的。当这个单例的Bean需要和另一个单例的或者非单例的Bean联合使用的时候,开发者只需要配置依赖的Bean为这个Bean的属性即可。但是有时会因为不同的Bean生命周期的不同而产生问题。假设单例的Bean A在每个方法调用中使用了非单例的Bean B。容器只会创建Bean A一次,而只有一个机会来配置属性。那么容器就无法给Bean A每次都提供一个新的Bean B的实例。

一个解决方案就是放弃一些IoC。开发者可以通过实现ApplicationContextAware接口令Bean A可以看到ApplicationContext,从而通过调用getBean("B")来在Bean A 需要新的实例的时候来获取到新的B实例。参考下面的例子:

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

上面的代码并不是让人十分满意,因为业务的代码已经与Spring框架耦合在了一起。Spring提供了一个稍微高级的点特性方法注入的方式,可以用来处理这种问题。

查找方法注入

查找方法注入就是容器一种覆盖容器管理Bean的方法,来返回查找的另一个容器中的Bean的能力。查找方法通常就包含前面场景提到的Bean。Spring框架通过使用CGLIB库生成的字节码来动态生成子类来覆盖父类的方法实现方法注入。

  • 为了让这个动态的子类方案正常,那么Spring容器所继承的父类不能是final的,而覆盖的方法也不能是final的。
  • 针对这个类的单元测试因为存在抽象方法,所以必须实现子类来测试
  • 组件扫描的所需的具体方法也需要具体类。
  • 一个关键的限制在于查找方法与工厂方法是不能协同工作的,尤其是不能和配置类之中的@Bean的方法,因为容器不在负责创建实例,而是创建一个运行时的子类。
  • 最后,被注入的到方法的对象不能被序列化。

看到前面的代码片段中的CommandManager类,我们发现发现Spring容器会动态的覆盖createCommand()方法。CommandManager类不在拥有任何的Spring依赖,如下:

package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

在包含需要注入的方法的客户端类当中,注入的方法需要有如下的函数签名

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

如果方法为抽象,那么动态生成的子类会实现这个方法。否则,动态生成的子类会覆盖类中的定义的原方法。例如:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="command"/>
</bean>

上面的commandManager调用自己的方法createCommand()当它需要一个command bean的新实例的时候。开发者一定要谨慎配置command Bean的为prototype类型的Bean。如果所需的Bean为单例的,那么这个方法注入返回的将都是同一个实例。

任意的方法替换

从前面的描述中,我们知道查找方法是有能力来覆盖任何由容器管理的Bean的方法的。开发者最好跳过这一部分,除非一定需要使用这个功能。

通过配置基于XML的配置元数据,开发者可以使用replaced-method元素来替换一个存在的方法的实现。考虑如下情况:

public class MyValueCalculator {

    public String computeValue(String input) {
        // some real code...
    }

    // some other methods...

}

一个实现了org.springframework.beans.factory.support.MethodReplacer接口的类会提供一个新方法的定义。

/**
 * meant to be used to override the existing computeValue(String)
 * implementation in MyValueCalculator
 */
public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ...
        return ...;
    }
}

如果需要覆盖Bean的方法需要配置XML如下:

<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

开发者可以使用更多的<replaced-method>中的<arg=type/>元素来指定需要覆盖的方法。当需要覆盖的方法存在重载方法时,指定参数才是必须的。为了方便起见,字符串的类型是会匹配如下类型,完全等同于java.lang.String

java.lang.String
String
Str

因为通常来说参数的个数已经足够区别不同的方法了,这种快捷的写法可以省去很多的代码。



至此,已经完全描述了Spring核心技术中的依赖注入的基本信息,下一篇博文将会介绍Bean的scope。

时间: 2024-10-11 05:56:57

Spring核心技术IoC容器(四)的相关文章

Spring核心技术IoC容器(六)

前文已经描述了Bean的作用域,本文将描述Bean的一些生命周期作用,配置还有Bean的继承. 定制Bean 生命周期回调 开发者通过实现Spring的InitializeingBean和DisposableBean接口,就可以让容器来管理Bean的生命周期.容器会调用afterPropertiesSet()前和destroy()后才会允许Bean在初始化和销毁Bean的时候执行一些操作. JSR-250的@PostConstruct和@PreDestroy注解就是现代Spring应用生命周期回

Spring核心技术IoC容器(五)

前文概述了Spring的容器,Bean,以及依赖的一些信息,本文将描述一下Bean的作用域 Bean的作用域 当开发者定义Bean的时候,同时也会定义了具体如何创建Bean实例的步骤.这些步骤是很重要的,因为只有通过这些配置,开发者才能创建实例对象. 开发者不仅可以控制多种多样的依赖到Bean之中,也可以配置Bean的作用域.这种方法是非常强大而且弹性也非常好,开发者可以通过配置来指定对象的作用域,而不用在Java类层次上来配置.Bean可以配置多种作用域.Spring框架支持5中作用域,有三种

Spring核心技术IoC容器(八)

本文针对自动装载的一些注解进行描述. 基于注解的容器配置 @Required注解 @Required注解需要应用到Bean的属性的setter方法上面,如下面的例子: public class SimpleMovieLister { private MovieFinder movieFinder; @Required public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } //

Spring 核心技术 IoC容器(一)

IoC 容器 IoC容器和Bean简介 这章包括了Spring框架对于IoC规则的实现.Ioc也同DI(依赖注入).而对象是通过构造函数,工厂方法,或者一些Set方法来定义对象之间的依赖的.容器在创建这些Bean对象的时候同时就会注入这些依赖.这个过程是根本上的反转了,不再由Bean本身来控制实例化和定位依赖,而是通过服务定位来控制这个过程,也是IoC(控制反转)的由来. org.springframework.beans和org.springframework.context包是Spring框

Spring核心技术IoC容器(七)

本文将讨论如何关于在Spring生命周期中扩展Spring中的Bean功能. 容器的扩展 通常来说,开发者不需要通过继承ApplicationContext来实现自己的子类扩展功能.但是Spring IoC容器确实可以通过实现接口来增加一些功能.下面将描述一下这些接口. 通过BeanPostProcessor定义Bean BeanPostProcessor接口定义了一些回调方法,开发者可以通过实现来自己的实例化逻辑,依赖解析逻辑等等.如果开发者只是想在Spring容器完成了实例化,配置以及初始化

Spring 核心技术IoC容器(二)

本文将继续前文,描述Spring IoC中的依赖处理. 依赖 一般的企业应用也不会只有一个对象(或者是Spring Bean).甚至最简单的应用都要有一些对象来协同工作来让终端用户看到一个完整的应用.下一部分将解释开发者从单独的定义Bean,到让这些对象在一个应用中协同工作. 依赖注入 依赖注入是一个让对象只通过构造参数,工厂方法的参数或者配置的属性来定义他们的依赖.这些依赖也是对象所需要协同工作的对象.容器在之后会在创建Bean的时候注入这些依赖.整个过程完全反转了Bean自己控制实例化或者,

Spring 核心技术IoC容器 (三)

本文将继续前文,针对依赖注入的细节进行描述 依赖注入细节 如前文所述,开发者可以通过定义Bean的依赖的来引用其他的Bean或者是一些值.Spring基于XML的配置元数据支持一些子元素<property/>以及<constructor-arg/>来达到这一目的. 内在值类型(Java Primitives类型,字符串等) 元素<property/>有value属性来以易读的形式配置一个属性或者构造参数.Spring的遍历就是用来讲这些字符串的值转换成指定的类型. &l

Spring之IOC容器加载初始化的方式

引言 我们知道IOC容器时Spring的核心,可是如果我们要依赖IOC容器对我们的Bean进行管理,那么我们就需要告诉IOC容易他需要管理哪些Bean而且这些Bean有什么要求,这些工作就是通过通过配置文件告诉Spring 的IOC容器.在我们的完成这些配置文件以后,如果IOC容易要实现对这些Bean的管理,除了资源的定位还有一个很重要的一步就是完成IOC加载初始化,也就是配置文件的载入过程.完成IOC容器加载初始化的方式只要有三种,第一种就是通过File文件加载,第二种是通过Classpath

Spring的IOC容器—依赖注入

前面一篇博客大致讲了一下Spring的IOC容器的原理,IOC即控制反转主要是依靠依赖注入的方式来实现的.依赖注入是指所依赖的对象不是由自己new出来的,而是用别的方式像打针似的注入进来. 其实说白了不管是控制反转还是依赖注入都说明了Spring采用动态.灵活的方式来管理各种对象. Spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO之间依赖关系的管理.有以下几种注入方式: 1. Setter 注入 因为对于javaBean来说,我们可以通过setter和getter方法