Spring的bean建立详解

       IoC容器,又名控制反转,全称为Inverse of Control,其是Spring最为核心的一个组件,其余的组件如AOP,Spring事务等都是直接或间接的依赖于IoC容器的。本文主要讲解IoC容器所管理的bean的几种建立方式,而且详细讲解了xml配置中相关参数的配置。java

       在IoC容器中,bean的获取主要经过BeanFactoryApplicationContext获取,这里ApplicationContext其实是继承自BeanFactory的,二者的区别在于BeanFactory对bean的初始化主要是延迟初始化的方式,而ApplicationContext对bean的初始化是在容器启动时即将全部bean初始化完毕。以下是BeanFactory的主要接口:spring

Object getBean(String name) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
boolean containsBean(String name);
String[] getAliases(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

       能够看到,BeanFactory中主要提供的是一些查询bean的方法,而bean的建立和管理其实是由BeanDefinitionRegistry来进行的。BeanDefinitionRegistry会为其管理的每一个bean都建立一个BeanDefinition实例,该实例中主要包含当前bean的名称,类型,是否抽象类,构造函数参数等信息。BeanDefinition有两个主要的实现类RootBeanDefinitionChildBeanDefinition,这里RootBeanDefinition主要用于建立而且注册一个bean到BeanDefinitionRegistry中,ChildBeanDefinition则主要用于预处理具备parent/child的bean定义。以下图为IoC容器管理bean的主要类结构图,这里DefaultListableBeanFactoryBeanFactoryBeanDefinitionRegistry的一个默认实现类:数据库

       IoC容器建立bean主要有三种方式:硬编码,元数据和配置文件。这里硬编码方式也即显示的使用上面的类图关系将bean以及它们之间的依赖关系注册到IoC容器中;元数据方式即便用Java注解和spring自动扫描的功能配置bean;配置文件的方式主要有两种:xml和properties文件,这里主要讲解使用更普遍的xml文件的方式。app

       这了以零售超市的例子来说解bean的建立,SuperMarket表示零售超市,其有DrinkProvider合FruitProvider两个供应商,而且这两个供应商分别有两个实现类Milk和Apple。以下是各个类的结构:ide

public class SuperMarket {
  private DrinkProvider drink;
  private FruitProvider fruit;
  
  public SuperMarket() {}

  public SuperMarket(DrinkProvider drink, FruitProvider fruit) {
    this.drink = drink;
    this.fruit = fruit;
  }

  public void setDrink(DrinkProvider drink) {
    this.drink = drink;
  }

  public void setFruit(FruitProvider fruit) {
    this.fruit = fruit;
  }
  
  @Override
  public String toString() {
    return "drink: " + drink + ", fruit: " + fruit;
  }
}
public interface DrinkProvider {}
public class Milk implements DrinkProvider {
  @Override
  public String toString() {
    return "this is milk";
  }
}
public interface FruitProvider {}
public class Apple implements FruitProvider {
  @Override
  public String toString() {
    return "this is an apple";
  }
}

1. 硬编码

       根据上面对IoC容器对bean进行管理的几个类的讲解,这里硬编码的方式实际上很好实现,以下是bean建立的代码:函数

public class BeanApp {
  public static void main(String[] args) {
    DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
    BeanFactory beanFactory = bindViaCode(beanRegistry);
    SuperMarket superMarket = beanFactory.getBean(SuperMarket.class);
    System.out.println(superMarket);
  }

  private static BeanFactory bindViaCode(BeanDefinitionRegistry beanRegistry) {
    AbstractBeanDefinition fruit = new RootBeanDefinition(Apple.class);
    AbstractBeanDefinition drink = new RootBeanDefinition(Milk.class);
    AbstractBeanDefinition superMarket = new RootBeanDefinition(SuperMarket.class);

    beanRegistry.registerBeanDefinition("fruit", fruit);
    beanRegistry.registerBeanDefinition("drink", drink);
    beanRegistry.registerBeanDefinition("superMarket", superMarket);

    // 使用构造方法对属性进行设值
    ConstructorArgumentValues argumentValues = new ConstructorArgumentValues();
    argumentValues.addIndexedArgumentValue(0, drink);
    argumentValues.addIndexedArgumentValue(1, fruit);
    superMarket.setConstructorArgumentValues(argumentValues);

    // 使用setter方法对属性进行设值
    MutablePropertyValues propertyValues = new MutablePropertyValues();
    propertyValues.addPropertyValue("fruit", fruit);
    propertyValues.addPropertyValue("drink", drink);
    superMarket.setPropertyValues(propertyValues);
    
    return (BeanFactory) beanRegistry;
  }
}

以下是输出结果:测试

drink: this is milk. , fruit: this is an apple.

       在示例中,咱们首先声明了一个DefaultListableBeanFactory实例,须要注意,DefaultListableBeanFactory既实现了BeanFactory接口,也实现了BeanDefinitionRegistry接口,于是这里将该实例传入bindViaCode()方法做为bean注册器使用。在bindViaCode()方法中,咱们首先为每一个须要建立的bean建立一个BeanDefinition对其进行管理,而后将每一个BeanDefinition注册到BeanDefinitionRegistry中。注册完以后,咱们使用ConstructorArgumentValues类来指定建立的三个bean之间的相互依赖关系(这里咱们也提供了使用setter方法对属性进行设值的代码)。从最后的输出咱们能够看出,SuperMarket,Milk和Apple三个类都成功建立了。ui

2. 元数据

       元数据的方式也即注解方式,Spring IoC主要提供了两个注解用于bean的建立和属性的注入,即@Component@Autowired。这里@Component用在类声明上,用于告知Spring,其须要为当前类建立一个实例,实例名为当前类名首字母小写的形式。@Autowired则用在属性上,Spring检测到该注解以后就会在IoC容器中查找是否有与该属性相匹配的类或子类实例,有的话就注入到当前属性中,不然就会报错。以下是使用元数据方式建立的bean的示例,示例的类结构中部分代码与前述类结构一致,这里对其进行了省略:this

@Component
public class SuperMarket {
  @Autowired
  private DrinkProvider drink;
  
  @Autowired
  private FruitProvider fruit;
  
  // getter和setter,以及toString()等方法
}
@Component
public class Milk implements DrinkProvider {
}
@Component
public class Apple implements FruitProvider {
}

       能够看到,这里建立了分别建立了Milk,Apple和SuperMarket的实例,而且将Milk和Apple实例经过@Autowired注入到SuperMarket实例中了。这里须要注意的是,对于IoC容器而言,单纯使用了上述注解还不能让其自动建立这些bean,还须要经过配置文件用来指明须要对哪些包下的类进行扫描,以检测相关的注解,并注册相应的实例。以下是xml文件的配置方式:编码

<context:component-scan base-package="com.market"/>

以下是测试驱动类的代码:

public class BeanApp {
  public static void main(String[] args) {
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("com/market/application.xml");
    SuperMarket superMarket = beanFactory.getBean(SuperMarket.class);
    System.out.println(superMarket);
  }
}

结果输出以下:

drink: this is milk, fruit: this is an apple

3. 配置文件

       xml配置文件是bean实例化使用最为普遍的一种方式,其主要包括两种形式的bean建立:构造方法和属性注入。这里咱们会对着两种方式进行详细讲解,而且还会讲解如何注入List,Set,Map等类型属性值的方式,另外,咱们也会讲解具备初始化顺序的bean的初始化和具备父子类关系的bean的初始化等方式。

1. 构造方法注入

       构造方法注入主要使用constructor-arg标签,具体使用方式有如下几种类型

  • 引用类型
<bean id="drink" class="com.market.Milk"/>
<bean id="fruit" class="com.market.Apple"/>
<bean id="superMarket" class="com.market.SuperMarket">
  <constructor-arg ref="drink"/>
  <constructor-arg ref="fruit"/>
</bean>

       这里首先建立Milk和Apple类的对象,而后在建立SuperMarket对象时,向其构造函数传入了先前建立的Milk和Apple对象。这里ref节点用于表示当前参数是引用的其余的bean。

  • 数值类型
public class SequenceFile { 
  private int dependency1;
  private String dependency2;

  public SequenceFile(int dependency1) {
    this.dependency1 = dependency1;
  }
}
<bean id="sequenceFile" class="com.market.SequenceFile">
  <constructor-arg value="123"/>
</bean>

       这里使用constructor-arg的value节点来为只有一个参数的构造函数指定值。因为SequenceFile只有一个构造函数,于是这里IoC容器知道应该使用该构造函数,而且会进行强制类型转换以使参数值符合参数类型。

  • 指定参数类型
public class SequenceFile {
  private int dependency1;
  private String dependency2;

  public SequenceFile(String dependency2) {
    this.dependency2 = dependency2;
  }
  
  public SequenceFile(int dependency1) {
    this.dependency1 = dependency1;
  }
}
<bean id="sequenceFile" class="com.market.SequenceFile">
  <constructor-arg value="123" type="int"/>
</bean>

       这里有两个只有一个参数的构造函数,此时若是配置文件仍是按照上一示例中的配置,那么IoC容器是不知道应该使用哪一个构造函数的,于是其会默认使用第一个构造函数,也就是dependency2会被注入123。这里若是使用type节点指定了参数类型为int,那么IoC容器就会找只有一个参数,而且参数类型为int类型的构造函数进行bean的实例化,这里也就是dependency1会被初始化为123。

  • 指定参数顺序
public class SequenceFile {
  private int dependency1;
  private String dependency2;

  public SequenceFile(int dependency1, String dependency2) {
    this.dependency1 = dependency1;
    this.dependency2 = dependency2;
  }
}
<bean id="sequenceFile" class="com.market.SequenceFile">
  <constructor-arg value="abc" index="1"/>
  <constructor-arg value="123" index="0"/>
</bean>

       这里SequenceFile有一个包含两个参数的构造函数,在声明bean指定参数的时候,若是不指定当前注入的参数对应于构造函数的第几个参数,那么IoC容器就会按照声明的顺序为构造函数的参数注值,这每每是有问题的。示例中咱们使用index节点为当前的参数值指定了对应的构造函数的参数位,注意构造函数的参数索引是从0开始的。

2. 属性注入

       属性注入也就是使用setter方法注入,注入的参数名与setter方法后缀部分是一致的,而与实际参数名无关。setter方法注入在类的声明上主要有两个地方须要注意:①若是配置文件没有显示使用显示的声明构造函数,那么类中必定要声明默认的构造函数;②类中必定要包含有要注入属性的setter方法。以下是一个setter方法进行数值注入的示例:

public class SequenceFile {
  private int dependency;

  public void setDependency(int dependency) {
    this.dependency = dependency;
  }
}
<bean id="sequenceFile" class="com.market.SequenceFile">
  <property name="dependency" value="123"/>
</bean>

setter方法也能够进行引用注入,以下所示:

<bean id="fruit" class="com.market.Apple"/>
<bean id="drink" class="com.market.Milk"/>
<bean id="superMarket" class="com.market.SuperMarket">
  <property name="drink" ref="drink"/>
  <property name="fruit" ref="fruit"/>
</bean>

       这里属性注入的使用方式和构造函数中参数的注入方式在配置文件的配置上基本是一致的,这里就再也不赘述其具体的使用。

3. List,Set,Map和Properties

       对于集合参数的注入,不管是构造函数仍是属性注入,其使用方式是一致的,只须要在相应的参数声明节点下使用集合标签便可。这里集合类型与标签对应方式以下:

集合类型 xml标签
List
Set
Map
Properties

以下是一个声明集合参数的示例:

public class MockDemoObject {
  private List<Integer> param1;
  private String[] param2;
  private Set<String> param3;
  private Map<Integer, String> param4;
  private Properties properties;
  private Object param5;

  public void setParam1(List<Integer> param1) {
    this.param1 = param1;
  }

  public void setParam2(String[] param2) {
    this.param2 = param2;
  }

  public void setParam3(Set<String> param3) {
    this.param3 = param3;
  }

  public void setParam4(Map<Integer, String> param4) {
    this.param4 = param4;
  }

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

  public void setParam5(Object param5) {
    this.param5 = param5;
  }
}
<bean id="mdBean" class="com.market.MockDemoObject">
  <property name="param1">
    <list>
      <value>1</value>
      <value>2</value>
      <value>3</value>
    </list>
  </property>

  <property name="param2">
    <list>
      <value>string1</value>
      <value>string2</value>
      <value>string3</value>
    </list>
  </property>

  <property name="param3">
    <set>
      <value>abc</value>
      <value>def</value>
      <value>hij</value>
    </set>
  </property>

  <property name="param4">
    <map>
      <entry key="1" value="string1"/>
      <entry key="2" value="string2"/>
      <entry key="3" value="string3"/>
    </map>
  </property>

  <property name="properties">
    <props>
      <prop key="author">zhangxufeng</prop>
      <prop key="age">26</prop>
    </props>
  </property>

  <property name="param5">
    <null/>
  </property>
</bean>

       这里须要说明的是,若是集合的元素是引用类型,那么只须要在对应的元素声明处使用ref节点指向另外声明的bean。

4. depends-on依赖

       这里depends-on依赖指的是在某些bean进行实例化时,必须保证另一个bean已经实例化完成,而且这两个bean不必定具备属性依赖关系。depends-on实际使用状况好比进行dao的bean实例化时,须要先将管理数据库链接池的bean进行初始化。以下是一个depends-on依赖的示例:

public class ServiceInstance {}
public class SystemConfigurationSetup {
  static {
    System.out.println("static initialization! ");
  }
}
public class SystemConfigurationSetup2 {
  static {
    System.out.println("static initialization 2 ! ");
  }
}

配置文件:

<bean id="scSetup1" class="com.market.SystemConfigurationSetup"/>
<bean id="scSetup2" class="com.market.SystemConfigurationSetup2"/>
<bean id="serviceInstance" class="com.market.ServiceInstance" depends-on="scSetup1,scSetup2"/>

       能够看到,这里在ServiceInstance的bean标签中使用的depends-on,具备多个依赖的使用逗号隔开,IoC容器在进行该bean的初始化以前会保证scSetup1和scSetup2都初始化完毕。

5. autowire自动注入

       autowire自动注入指的是在声明一个bean的时候不显示的为其声明构造函数或者是属性名的参数,而是使用autowire节点,让IoC容器经过构造函数和属性名自动识别当前bean所依赖的bean,从而注入进来。autowire有两个值可选byType和byName,分别表示根据构造函数参数和属性的类型进行自动注入,或者是根据属性名进行自动注入。以下所示为autowire注入的一个示例:

public class Foo {
  private Bar emphasisAttribute;

  public void setEmphasisAttribute(Bar emphasisAttribute) {
    this.emphasisAttribute = emphasisAttribute;
  }
}
public class Bar {}
<bean id="fooBean" class="com.market.Foo" autowire="byName"/>
<bean id="emphasisAttribute" class="com.market.Bar"/>

       示例中,Foo实例依赖于Bar实例,在配置文件中建立Foo实例的处并无指定其属性值,而是使用了autowire="byName",而Bar实例的名称则和Foo的setter方法后的名称一致。这里也可使用byType类型的自动注入,此时Bar实例的名称则能够为任意名称:

<bean id="fooBean" class="com.market.Foo" autowire="byType"/>
<bean id="anyName" class="com.market.Bar"/>

6. 继承

  • bean的类之间具备继承关系

       对于具备继承关系的bean,因为父类的属性,子类也会有,于是若是直接配置,那么两个bean的配置将会有很大一部分趋于类似。这里可使用parent属性用来将父类已经注入的bean继承给子类bean,子类bean能够只更改其中实现与父类有区别的bean。以下示例中,SpecialSuperMarket继承自SuperMarket类,而SpecialApple则继承自Apple。在实例化SpecialSuperMarket实例的时候其和SuperMarket实例有部分相同的属性,而另外一部分是有区别的。以下是SpecialSuperMarket和SpecialApple的声明,其他的类与前面的类声明一致:

public class SpecialSuperMarket extends SuperMarket {}
public class SpecialApple extends Apple {}
<bean id="drink" class="com.market.Milk"/>
<bean id="fruit" class="com.market.Apple"/>
<bean id="superMarket" class="com.market.SuperMarket">
  <property name="fruit" ref="fruit"/>
  <property name="drink" ref="drink"/>
</bean>

<bean id="specFruit" class="com.market.SpecialApple"/>
<bean id="specSuperMarket" parent="superMarket" class="com.market.SpecialSuperMarket">
  <property name="fruit" ref="specFruit"/>
</bean>

       从配置文件能够看出来,父类bean只须要按照正常方式声明便可,子类的bean只须要使用parent节点指定其继承的父类bean,而且指明子类与父类有差别的属性bean。

  • 提取公共bean并进行继承

        对于两个或多个bean,若是其大部分属性bean都是类似的,只有少部分不一致,那么就能够将公共的bean提取出来做为父bean,而后每一个bean继承自这个bean,子bean能够重写本身与父bean不一致的属性。这里须要注意的是,提取出来的父bean并非一个真正的bean,其也没有对应的Java类对应。

       以下例所示,假设另外有一个零售商店Outlet与SuperMarket同样,其DrinkProvider也为Milk,但其FruitProvider不同,是Pear,这里就能够将Outlet示例与SuperMarket实例的声明中的相同部分Milk提取出来,而FruitProvider则各自本身提供(SuperMarket代码与前面一致,这里省略):

public class Outlet {
  private DrinkProvider drink;
  private FruitProvider fruit;

  public void setDrink(DrinkProvider drink) {
    this.drink = drink;
  }

  public void setFruit(FruitProvider fruit) {
    this.fruit = fruit;
  }
}
public class Pear implements FruitProvider {}
<bean id="drink" class="chapter2.eg1.Milk"/>
<bean id="superSales" abstract="true">
  <property name="drink" ref="drink"/>
</bean>

<bean id="marketFruit" class="chapter2.eg1.Apple"/>
<bean id="superMarket" parent="superSales" class="chapter2.eg1.SuperMarket">
  <property name="fruit" ref="marketFruit"/>
</bean>

<bean id="outletFruit" parent="superSales" class="chapter2.eg4.Pear"/>
<bean id="outlet" class="chapter2.eg4.Outlet">
  <property name="fruit" ref="outletFruit"/>
</bean>

       从配置文件中能够看出来,这里将SuperMarket和Outlet中drink属性的注入提取出来,从而造成一个父bean,即superSales,而SuperMarket和Outlet的bean只须要继承父bean,而且注入各自特有的bean便可。这里须要注意,因为父bean是没有对应的class与之对应的,于是其没有class节点,而且父bean须要设置为abstract类型的。

4. 结语

        本文首先对IoC容器管理bean的方式进行了讲解,而后分别介绍了如何使用硬编码,元数据和配置文件的方式进行bean的配置,而且这里着重讲解了如何使用配置文件对bean进行配置。

相关文章
相关标签/搜索