Author 罗先生
本文是IOC系列的第三篇,往期回顾:

FactoryBean是什么?

  • Spring提供的一种特殊Bean接口
  • 实现了FacotryBean这个接口的Bean,Spring当中会存在两个对象,一个是实现的FactoryBean对象,还有一个就是你实现的getObject()方法中返回的对象。

来编个码:

@Component
public class MyFactoryBean implements FactoryBean {
   public void selectOrder() {
      System.out.println("MyFactoryBean selectOrder");
   }
   @Override
   public Object getObject() throws Exception {
      return new OrderDaoImpl();
   }
   @Override
   public Class<?> getObjectType() {
      return OrderDaoImpl.class;
   }
}

测试代码一(获取MyFactoryBean 对象):

public class FactoryBeanTest {
   public static void main(String[] args) {
      AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Appconfig.class);
      MyFactoryBean myFactoryBean = (MyFactoryBean)annotationConfigApplicationContext.getBean("myFactoryBean");
      System.out.println(myFactoryBean);
   }
}
//运行结果:
Exception in thread "main" java.lang.ClassCastException: com.luban.ioctest.OrderDaoImpl cannot be cast to com.luban.ioctest.factoryBean.MyFactoryBean
	at com.luban.test.FactoryBeanTest.main(FactoryBeanTest.java:15)

发现报错了???这里一定要觉得奇怪。。 不然IOC的基础就不太行了(当然前提是不知道FactoryBean)。因为@Component生成的BeanName应该就是类名首字母小写。
答案在于开篇就说了FactoryBean特殊,除了会生成两个Bean以外,获取自己的时候也特殊,需要加&,后面再跟beanName,才可以get出来

测试代码二(获取MyFactoryBean 对象 - 正确版):

public class FactoryBeanTest {
   public static void main(String[] args) {
      AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Appconfig.class);
      MyFactoryBean myFactoryBean = (MyFactoryBean)annotationConfigApplicationContext.getBean("&myFactoryBean");
      System.out.println(myFactoryBean);
   }
}
//运行结果
com.luban.ioctest.factoryBean.MyFactoryBean@3c5a99da

没报错了,证实了上面说的话

测试代码三(获取MyFactoryBean 往Spring容器里注册的对象):

public class FactoryBeanTest {
   public static void main(String[] args) {
      AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Appconfig.class);
      OrderDaoImpl orderDao = (OrderDaoImpl)annotationConfigApplicationContext.getBean("orderDaoImpl");
      System.out.println(orderDao);
   }
}

这里如果强转成MyFactoryBean对象会报错,不信的话可以尝试一下

FactoryBean内部存储

  • getObject()得到的对象,bename存的是当前类指定的名字(类名首字母小写),对象则是return的对象
  • FactoryBean对象的beanName其实还是当前类指定的名字(类名首字母小写)

为什么取FactoryBean还要加&

上面说了Spring在容器(DefaultListableBeanFactory)内部的beanName还是myFactoryBean(上面自定义的FactoryBean的名字),但是取的时候加了&,那么怎么取的呢,可以查看doGetBean源码,第一行

final String beanName = transformedBeanName(name);
public static String transformedBeanName(String name) {
   Assert.notNull(name, "'name' must not be null");
   String beanName = name;
   while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
      beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
   }
   return beanName;

}

答案就在于这个方法org.springframework.beans.factory.support.AbstractBeanFactory#transformedBeanName
源码也比较简单,如果以&开头,那么代表需要找的是FactoryBean,然后把前缀&去掉,这样就能找到真实的bean了

FactoryBean在什么会用到?

当我们一个类依赖关系十分复杂的时候,而我们想对外提供一种简单的关系,让外部可以很便捷的把某个类配置出来的话就可以选择FactoryBean,经典的场景是SqlSessionFactoryBean,可见SpringIOC-FactoryBean