是翻译过来的,自己重新组织了下:原文在这里,需要科学上网。
在 Spring 中,有两个接口:BeanFactory
和 FactoryBean
因为名字相近很容易被混淆。那他们之间有什么区别呢?
先说结论:
- BeanFactory: 是 IOC 容器,并且提供方法支持外部程序对这些 bean 的访问,在程序启动时 根据传入的参数产生各种类型的 bean,并添加到 IOC容器(实现
BeanFactory
接口的类) 的singletonObject
属性中。 - FactoryBean: 首先是个 bean,也存放在 BeanFactory 中。它具有工厂方法的功能,在程序运行中 产生指定(一种)类型的 bean,并添加到了 IOC容器中的
factoryBeanObjectCache
属性中。
所以,这两种方式创建的 bean 都是被 spring 容器管理的。
1. BeanFactory
beanFactory
是 Spring IOC 容器的核心接口,它定义了 Spring 管理 bean 的通用方法,比如:getbean()
、containsBean()
。Spring 容器都是他的一种实现,比如:
- DefaultListableBeanFactory。
- XmlBeanFactory
- ApplicationContext
这些实现类从不同的维度对 beanFactory 进行了扩展。
注:文章后面锁所说的 ”容器“ 都指的是 ”IOC容器“。
1.1 beanFactory 源码
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
public interface BeanFactory {
// factoryBean 的转义标识符。(具体用法后面有介绍)
String FACTORY_BEAN_PREFIX = "&";
// 根据 name 从容器中拿对应的 bean。
Object getBean(String name) throws BeansException;
// 根据 name 和 type 从容器中拿对应的 bean,要对 bean 的类型做校验。
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
// 在容器中能否找到与 name 匹配的 bean 或者 beanDefinition。
boolean containsBean(String name);
// 判断 name 对对应的 bean 是不是 单例。
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
// 判断 name 对应的 bean 与指定的类型是否匹配。
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
//根据 name 获取对应的 bean 的类型。
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
// 根据 name 获取对应 bean 的 别名。
String[] getAliases(String name);
}
1.2 使用场景
- 通过 名字或类型从容器中获取 bean。
- 判断容器中是否包含指定的 bean。
- 判断 bean 是不是单例。
2. FactoryBean
首先 FactoryBean
是一个 bean,但它又不仅仅是个 bean。它是一个可以 创建 或 修饰 其他对象的”工厂 bean“,这跟设计模式中的工厂模式或者装饰设模式很相似,它可以创建除自身以外的其他对象。(后面有个例子,看完就明白这里说的什么意思了)。
2.1 FactoryBean 源码
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
//从 factory 中获取 bean。
@Nullable
T getObject() throws Exception;
// 从 beanFactory 中获取类型。
@Nullable
Class<?> getObjectType();
//是单例?
default boolean isSingleton() {
return true;
}
}
从上面的接口可以看出,FactoryBean 有工厂的味道。也就是说,如果一个 A类实现了 FactoryBean 接口,那么 类A 就变成了 A工厂。根据 A 的名称获得的实际上是工厂调用 getObject()
返回的对象, 而不是 A工厂自己。如果你想获得 A工厂自己的实例,你需要添加 &
前缀。
2.2 用个 demo 解释下
到这里再不出 demo 就写不下去了。这是一个简单的 FactoryBean 的使用栗子。
package com.example.demo.domian;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;
@Component
public class MyFactoryBean implements FactoryBean {
// 保存一句话,用来区分不同的对象。
private String message;
// 无参构造器。
public MyFactoryBean() {
// 意思是:当前对象是 MyFactoryBean 的对象。
this.message = "object of myFactoryBeanSelf";
}
// 有参构造器。
public MyFactoryBean(String message) {
this.message = message;
}
// 获取 message。
public String getMessage() {
return this.message;
}
@Override
/**
* 这个方法在执行时创建了新的 MyFactoryBean 类型的对象。
* 这里继续沿用了 MyFactoryBean 类型,但是可以是别的类型
* 比如:Person、Car、等等。
*/
public Object getObject() throws Exception {
// 意思是:当前对象是 MyFactoryBean 的 getObject() 创建的。
return new MyFactoryBean("object from getObject() of MyFactoryBean");
}
@Override
public Class<?> getObjectType() {
return MyFactoryBean.class
}
}
测试内容很简单,MyFactoryBean 被 @Component 注解了,当启动SpringBoot程序的时候,会为 MyFactoryBean 构建 bean 并且保存到容器中。我们要测试的就是用 类: MyFactoryBean 的 名字: myFactoryBean 去容器中拿 bean,看拿到的结果是什么?
package com.example.demo.domian;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = com.example.demo.domian.MyFactoryBean.class)
class MyFactoryBeanTest {
@Autowired
private ApplicationContext context;
@Test
public void test() {
// 第一次用 myFactoryBean 去拿。
MyFactoryBean myBean1 = (MyFactoryBean) context.getBean("myFactoryBean");
System.out.println("myBean1 = " + myBean1.getMessage());
// 第二次用 &myFactoryBean 去拿。
MyFactoryBean myBean2 = (MyFactoryBean) context.getBean("&myFactoryBean");
System.out.println("myBean2 = " + myBean2.getMessage());、
// 判断两次拿到的对象是不是一样的?
System.out.println("myBean1.equals(myBean2) = " + myBean1.equals(myBean2));
}
}
结果:
myBean1 = object from getObject() of MyFactoryBean
myBean2 = object of myFactoryBeanSelf
myBean1.equals(myBean2) = false
结果很明显:
第一次使用 myFactoryBean
去容器中拿,实际上是 容器中 MyFactorybean 的bean 调用了 getObject()
方法,并将结果返回。
第二次使用 &myFactoryBean
去容器中拿,才是真正拿到了 MyFactorybean 的 bean。
而 &
就是用于区分到底拿谁的的前缀。
两次拿出来的对象当然是不一样的。
2.3 使用场景
说了这么说,为什么需要 FactoryBean ,它的特殊功能是什么呢?
Spring 中 FactoryBean 最大的应用场景是用在 AOP 中。我们都知道,AOP 实际上是 Spring 在运行是创建出来的代理对象,这个对象是在运行时才被创建的,而不是在启动时定义的,这与工厂方法模式是一致的。更生动地说,AOP 代理对象通过 java 反射机制在运行时创建代理对象,并根据业务需求将相应的方法编织到代理对象的目标方法中。Spring 中的 ProxyFactoryBean
就是干这事的。
因此,FactoryBean 提供了更灵活的实例化 bean 的方法。通过 FactoryBean 我们可以创建更复杂的 bean。