其他分享
首页 > 其他分享> > 别再BeanFactory和FactoryBean傻傻分不清楚

别再BeanFactory和FactoryBean傻傻分不清楚

作者:互联网

别再BeanFactory和FactoryBean傻傻分不清楚

写在前面

Spring系列,我打算按照如下类图的方式来一点点的深入学习,所以我们从BeanFactory和FactoryBean开始下手。
别再BeanFactory和FactoryBean傻傻分不清楚

前言

众所周知,在Spring中有两个特别容易搞混的概念,那就是BeanFactory和FactoryBean两兄弟。上篇文章我们聊了BeanFactory,它的主语是Factory,定语是Bean,也就是说它是管理Bean的工厂。我们需要使用某个Bean时只需要通过它就能获得。而对于FactoryBean,它的主语是Bean,定语是Factory,所以它本质上是一个Bean,与其他被注册到Spring容器中的Bean一样,只不过,该Bean比较特殊,它本身就是生产Bean的工厂(这就好比女人是一个人,但是女人比较特殊,她可以生产人)。

场景引入

工作中,我们经常强调"面向接口编程",但你想:虽然我们可以通过接口来避免和实现类耦合,但总归要有一种方式可以将接口和实现类进行关联才行。如果实现类是我们自己开发就比较简单:通过依赖注入的方式,让容器来解除接口和实现类的耦合性。但有时候实现类并不是由我们开发,有一种情况是实现类是在运行时根据某些信息自动生成,生成后还要交给容器管理(比如mybatis的mapper接口的代理类),那这个情况怎么办呢?一般我们采用工厂方法模式(Factory-Method)来解决。如下代码:

public interface AirConditioner {
  // 空调接口
}

public class Car {
  private AirConditioner conditioner;
  public Car() {
    this.conditioner = AirConditionerFactory.getInstance();
    // 或者如下方式
    // this.conditioner = new AirConditionFactory.getInstance();
  }
  // getter setter 方法略
}

public class AirConditionerFactory {
  public static AirConditioner getInstance(){
    // 动态生成AirConditioner的代理类实现
  }
  // 或者如下方式
  //public AirConditioner getInstance(){
    // 动态生成AirConditioner的代理类实现
  //}
}

Spring中的解决方案

针对上面这样使用工厂方法模式实例化对象的方式,在Spring中IOC容器给我们提供了对应的支持。我们只需要将工厂返回的接口的具体实现类注入给依赖该接口的对象就可以了。具体方式一共有以下三种,我们一一来了解。

通过静态工厂方法实现

public class AirConditionerFactory {
  public static AirConditioner getInstance(){
    // 动态生成AirConditioner的代理类实现
  }
}

在配置文件中,我们进行如下配置:

<bean id="airConditioner" class="xxx.AirConditionerFactory" factory-method="getInstance"/>
<bean id="car" class="xxx.Car">
    <property name="conditioner" ref="airConditioner"/>
</bean>

对于airConditioner这个bean来说,class是我们自己创建的静态方法工厂,factory-method是工厂的静态方法名。在运行时,容器调用该静态方法工厂类的静态方法,获取AirConditioner的实例,而后为car这个bean注入该实例。而不是注入静态工厂方法类(AirConditionerFactory)的实例。

静态工厂方法类的类型和静态方法返回的类型并没有必然的联系,更不需要一定相同。

另外,我们生成具体实现时,有可能依赖一些参数,那怎么实现参数传递呢?具体方式如下:

public class AirConditionerFactory {
  public static AirConditioner getInstance(String clazz){
    // 动态生成AirConditioner的代理类实现
  }
}

此时,配置方式就变成下面这样了:

<bean id="airConditioner" class="xxx.AirConditionerFactory" factory-method="getInstance">
  <constructor-arg>xxx.AirConditioner</constructor-arg>
</bean>
<bean id="car" class="xxx.Car">
    <property name="conditioner" ref="airConditioner"/>
</bean>

这里我们需要说明的是:配置文件中通过constructor-arg传入的是静态方法的参数,而不是静态工厂类的构造函数的参数。

通过非静态工厂方法实现

我们通过静态工厂方法可以实现将静态方法的调用结果注册到容器中,那我们肯定也可以基于非静态工厂来实现。代码如下:

public class AirConditionerFactory {
  public AirConditioner getInstance(){
    // 动态生成AirConditioner的代理类实现
  }
}

因为工厂方法不是静态的,所以我们只有依赖该工厂的实例才能得到具体的实现类,配置方式如下:

<bean id="airConditionerFactory" class="xxx.AirConditionerFactory" />
<bean id="airConditioner" factory-bean="airConditionerFactory" factory-method="getInstance"/>
<bean id="car" class="xxx.Car">
    <property name="conditioner" ref="airConditioner"/>
</bean>

可以看到,这种方式下AirConditionerFactory是作为正常的bean被注册到容器里的。而airConditioner在定义时,factory-bean属性用于指定工厂方法的实例,factory-method属性用于指定工厂方法名。

非静态工厂方法需要传递参数时,用法和静态工厂类似,都是用constructor-arg传入。

通过FactoryBean实现

像本文的情景,或者某些对象实例化过程过于复杂,我们就可以通过实现org.springframework.beans.factory.FactoryBean接口来实现达到我们的目标。虽然上面两种方式也都可以实现相同的效果,但总归感觉不是那么的优雅。FactoryBean才是正确的选择,该接口的源码如下:

public interface FactoryBean {
  Object getObject() throws Exception;
  Class getObjectType();
  boolean isSingleton();
}

其中,getObject()方法返回的是该FactoryBean生产的对象实例。getObjectType返回时的是getObject()方法返回的实例的类型,如果无法预先知道类型,则返回null即可。isSingleton()方法用来标识getObject()方法返回的对象是否要以singleton的方式存在于容器中。

这种方式的具体实现方式如下:

public class AirConditionerFactory implements FactoryBean {
  public Object getObject() {
    // 动态生成AirConditioner的代理类实现
  }
  public Class getObjectType() {
    return AirConditioner.class;
  }
  public boolean isSingleton() {
    return true;
  }
}

至于配置方式,就更简单了,如下:

<bean id="airConditioner" class="xxx.AirConditionerFactory"/>
<bean id="car" class="xxx.Car">
    <property name="conditioner" ref="airConditioner"/>
</bean>

单纯从配置文件来看,跟使用普通的bean没有任何区别,但是通过定义的airConditioner作为id去容器里获取bean,得到的却是FactoryBean生产的对象,而不是FactoryBean实例。

那如果我们就是想要获取该FactoryBean对象实例怎么办呢?别急,我们只需要在该bean定义的id前面加上"&"前缀再去容器里获取就可以获取到FactoryBean本身的实例了。(这是不是有点C语言中的取地址的味道,其实Spring官方称它为解引用)

写在最后

其实,在Spring容器的内部,许多地方都是用了FactoryBean,我们在这里列举出一些常见的FactoryBean的实现:

它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean开始支持泛型,即接口声明改为FactoryBean的形式。而且FactoryBean在AOP和事务中都发挥着不替代的作用。这个我们后面会慢慢接触到~
别再BeanFactory和FactoryBean傻傻分不清楚

阿豪说
学到了?请我喝杯咖啡吧~

标签:AirConditioner,实例,BeanFactory,实现,工厂,FactoryBean,分不清楚,public
来源: https://blog.51cto.com/15047484/2560194