工厂方法模式在Spring中的体现:BeanFactory源码与FactoryBean的区别
工厂方法模式在Spring中的体现:BeanFactory源码与FactoryBean的区别
适读人群:中高级Java开发者 | 阅读时长:约22分钟 | 模式类型:创建型
开篇故事
刚学Spring那会儿,我被 BeanFactory 和 FactoryBean 这两个名字搞得晕头转向。面试官问:"你说说 BeanFactory 和 FactoryBean 的区别",我支支吾吾半天,说不到点子上。
后来有一个具体场景让我彻底理解了这两者的差异。那是2020年,我们要在Spring项目里集成一个老旧的第三方SDK,这个SDK的客户端对象创建极其复杂——需要先读配置文件,再初始化安全上下文,再建立TCP长连接,整个过程涉及七八个步骤,不是简单的 new 一下就能搞定的。
如果直接在 Spring 的 XML 或 Java Config 里配置这个 Bean,代码会非常臃肿,而且这些初始化逻辑跟 Spring 框架本身的逻辑混在一起,非常难以维护。
我的同事建议用 FactoryBean 来封装这个复杂对象的创建过程。这一做法让我眼前一亮——原来工厂方法模式在 Spring 里有这么优雅的应用!从那以后,我对工厂方法模式的理解上了一个台阶。今天把这些经验系统整理出来。
一、模式动机:为什么需要工厂方法模式
简单工厂(Static Factory Method)有一个致命缺点:当需要增加新产品时,必须修改工厂类的代码,违反了开闭原则(OCP)。
工厂方法模式(Factory Method Pattern)通过引入抽象工厂角色解决了这个问题:定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。
在工程实践中,工厂方法模式的核心价值体现在三个场景:
场景一:创建逻辑复杂,不适合直接暴露
比如线程池的创建,涉及队列类型选择、拒绝策略配置、线程名称前缀等大量参数,通过工厂方法封装,调用方只需要描述"我需要什么样的线程池",而不需要关心怎么创建。
场景二:创建逻辑需要多态
比如根据配置项决定创建哪种消息队列客户端(Kafka Client vs RocketMQ Client),工厂方法的子类实现天然支持这种多态创建。
场景三:框架设计时创建的对象类型未知
Spring 作为框架不可能预知用户需要创建什么样的对象,通过 FactoryBean 接口,用户可以把任意复杂的对象创建逻辑"插入"到 Spring 容器中,这正是工厂方法模式的精髓。
二、模式结构
对应到 Spring 中:
Creator=BeanFactory/FactoryBean接口ConcreteCreator=DefaultListableBeanFactory/ 用户自定义的FactoryBean实现Product= 各种 Spring Bean
三、Spring 源码中的工厂方法实现分析
3.1 BeanFactory:Spring 的核心工厂接口
BeanFactory 是 Spring IoC 容器的根接口,它定义了容器的基本行为,本质上就是工厂方法模式中的抽象工厂角色。
// BeanFactory 的核心接口方法
public interface BeanFactory {
// 工厂方法:根据名称获取Bean(核心方法)
Object getBean(String name) throws BeansException;
// 工厂方法的重载:根据名称和类型获取Bean
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
// 工厂方法的重载:根据类型获取Bean
<T> T getBean(Class<T> requiredType) throws BeansException;
// 检查是否包含某个Bean定义
boolean containsBean(String name);
// 判断是否是单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
// 判断是否是原型
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
}BeanFactory 的继承体系非常丰富:
BeanFactory
├── ListableBeanFactory // 可枚举Bean的工厂
├── HierarchicalBeanFactory // 支持父子容器层级的工厂
│ └── ConfigurableBeanFactory
│ └── AbstractBeanFactory(模板方法实现)
│ └── AbstractAutowireCapableBeanFactory
│ └── DefaultListableBeanFactory(最终实现)
└── AutowireCapableBeanFactory // 支持自动装配的工厂AbstractBeanFactory.getBean() 是真正的核心工厂方法,它调用了 doGetBean():
protected <T> T doGetBean(String name, Class<T> requiredType,
Object[] args, boolean typeCheckOnly) {
String beanName = transformedBeanName(name);
Object beanInstance;
// 1. 先从缓存中获取(三级缓存机制)
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 如果是FactoryBean,调用其getObject()方法
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
// 2. 缓存中没有,需要创建
// 获取Bean定义
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 3. 根据作用域决定创建策略
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
return createBean(beanName, mbd, args); // 工厂方法
});
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
Object prototypeInstance = createBean(beanName, mbd, args);
beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
// 其他作用域...
}
}
return adaptBeanInstance(name, beanInstance, requiredType);
}3.2 FactoryBean:用户扩展点的工厂方法
FactoryBean 是 Spring 提供给用户的工厂方法扩展点,允许用户自定义 Bean 的创建逻辑:
public interface FactoryBean<T> {
// 工厂方法:返回实际要注入容器的对象
@Nullable
T getObject() throws Exception;
// 告知Spring工厂方法返回的对象类型
@Nullable
Class<?> getObjectType();
// 工厂创建的对象是否是单例
default boolean isSingleton() {
return true;
}
}关键区别:
- 从容器中获取
FactoryBean自身:在名字前加&,即ctx.getBean("&myFactoryBean") - 从容器中获取
FactoryBean.getObject()返回的对象:直接用名字,即ctx.getBean("myFactoryBean")
这个 & 前缀的处理在 AbstractBeanFactory.transformedBeanName() 中:
protected String transformedBeanName(String name) {
return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}
// BeanFactoryUtils.transformedBeanName
public static String transformedBeanName(String name) {
Assert.notNull(name, "'name' must not be null");
if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) { // "&"
return name;
}
return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
do {
beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
}
while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
return beanName;
});
}3.3 Mybatis-Spring 中 FactoryBean 的典型应用
Mybatis 与 Spring 的整合中,SqlSessionFactoryBean 和 MapperFactoryBean 是 FactoryBean 的经典实现:
// SqlSessionFactoryBean 简化版
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>,
InitializingBean, ApplicationListener<ContextRefreshedEvent> {
private Resource configLocation;
private DataSource dataSource;
private SqlSessionFactory sqlSessionFactory;
@Override
public void afterPropertiesSet() throws Exception {
// 在属性设置完成后构建SqlSessionFactory
this.sqlSessionFactory = buildSqlSessionFactory();
}
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
@Override
public Class<? extends SqlSessionFactory> getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class
: this.sqlSessionFactory.getClass();
}
@Override
public boolean isSingleton() {
return true;
}
private SqlSessionFactory buildSqlSessionFactory() throws Exception {
// 复杂的SqlSessionFactory创建逻辑
SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
Configuration targetConfiguration = new Configuration();
// 设置数据源、插件、类型处理器等...
return factoryBuilder.build(targetConfiguration);
}
}四、生产级代码实现
4.1 实战场景:多云存储客户端工厂
/**
* 云存储客户端接口(Product角色)
*/
public interface CloudStorageClient {
/**
* 上传文件
*/
String upload(String bucket, String key, InputStream content, long size);
/**
* 下载文件
*/
InputStream download(String bucket, String key);
/**
* 删除文件
*/
boolean delete(String bucket, String key);
/**
* 生成预签名访问URL
*/
String generatePresignedUrl(String bucket, String key, Duration expiration);
}
/**
* 阿里云OSS实现
*/
@Slf4j
public class AliyunOssClient implements CloudStorageClient {
private final OSS ossClient;
private final String endpoint;
public AliyunOssClient(String endpoint, String accessKeyId, String accessKeySecret) {
this.endpoint = endpoint;
this.ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
log.info("Aliyun OSS client initialized, endpoint: {}", endpoint);
}
@Override
public String upload(String bucket, String key, InputStream content, long size) {
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(size);
PutObjectResult result = ossClient.putObject(bucket, key, content, metadata);
return "https://" + bucket + "." + endpoint + "/" + key;
}
@Override
public InputStream download(String bucket, String key) {
OSSObject object = ossClient.getObject(bucket, key);
return object.getObjectContent();
}
@Override
public boolean delete(String bucket, String key) {
ossClient.deleteObject(bucket, key);
return true;
}
@Override
public String generatePresignedUrl(String bucket, String key, Duration expiration) {
Date expirationDate = new Date(System.currentTimeMillis() + expiration.toMillis());
URL url = ossClient.generatePresignedUrl(bucket, key, expirationDate);
return url.toString();
}
}
/**
* 腾讯云COS实现
*/
@Slf4j
public class TencentCosClient implements CloudStorageClient {
private final COSClient cosClient;
public TencentCosClient(String secretId, String secretKey, String region) {
COSCredentials credentials = new BasicCOSCredentials(secretId, secretKey);
ClientConfig clientConfig = new ClientConfig(new Region(region));
this.cosClient = new COSClient(credentials, clientConfig);
log.info("Tencent COS client initialized, region: {}", region);
}
@Override
public String upload(String bucket, String key, InputStream content, long size) {
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(size);
cosClient.putObject(bucket, key, content, metadata);
return "https://" + bucket + ".cos." + "ap-beijing" + ".myqcloud.com/" + key;
}
@Override
public InputStream download(String bucket, String key) {
COSObject object = cosClient.getObject(bucket, key);
return object.getObjectContent();
}
@Override
public boolean delete(String bucket, String key) {
cosClient.deleteObject(bucket, key);
return true;
}
@Override
public String generatePresignedUrl(String bucket, String key, Duration expiration) {
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucket, key, HttpMethodName.GET);
request.setExpiration(new Date(System.currentTimeMillis() + expiration.toMillis()));
URL url = cosClient.generatePresignedUrl(request);
return url.toString();
}
}4.2 抽象工厂实现
/**
* 云存储客户端工厂接口(Creator角色)
* 工厂方法模式:定义创建接口,子类决定创建哪种客户端
*/
public abstract class CloudStorageClientFactory {
/**
* 工厂方法(核心):子类重写此方法以创建具体产品
*/
public abstract CloudStorageClient createClient(StorageProperties properties);
/**
* 模板方法:包含通用的创建前后处理逻辑
*/
public final CloudStorageClient buildClient(StorageProperties properties) {
validateProperties(properties);
CloudStorageClient client = createClient(properties);
log.info("Cloud storage client created: {}, provider: {}",
client.getClass().getSimpleName(), properties.getProvider());
return client;
}
protected void validateProperties(StorageProperties properties) {
if (StringUtils.isEmpty(properties.getAccessKey())) {
throw new IllegalArgumentException("Storage accessKey cannot be empty");
}
if (StringUtils.isEmpty(properties.getSecretKey())) {
throw new IllegalArgumentException("Storage secretKey cannot be empty");
}
}
}
/**
* 阿里云OSS工厂(ConcreteCreator角色)
*/
@Component
public class AliyunOssClientFactory extends CloudStorageClientFactory {
@Override
public CloudStorageClient createClient(StorageProperties properties) {
return new AliyunOssClient(
properties.getEndpoint(),
properties.getAccessKey(),
properties.getSecretKey()
);
}
}
/**
* 腾讯云COS工厂(ConcreteCreator角色)
*/
@Component
public class TencentCosClientFactory extends CloudStorageClientFactory {
@Override
public CloudStorageClient createClient(StorageProperties properties) {
return new TencentCosClient(
properties.getAccessKey(),
properties.getSecretKey(),
properties.getRegion()
);
}
}4.3 将工厂方法与 FactoryBean 结合
/**
* CloudStorageClient 的 FactoryBean 实现
* 将复杂的客户端初始化逻辑封装起来,交由Spring管理
*/
@Component
@Slf4j
public class CloudStorageClientFactoryBean implements FactoryBean<CloudStorageClient>,
InitializingBean {
@Autowired
private StorageProperties storageProperties;
@Autowired
private List<CloudStorageClientFactory> factories; // 注入所有工厂实现
private CloudStorageClient client;
@Override
public void afterPropertiesSet() throws Exception {
String provider = storageProperties.getProvider();
CloudStorageClientFactory factory = factories.stream()
.filter(f -> f.supports(provider))
.findFirst()
.orElseThrow(() -> new IllegalStateException(
"No factory found for storage provider: " + provider));
this.client = factory.buildClient(storageProperties);
log.info("CloudStorageClient initialized for provider: {}", provider);
}
@Override
public CloudStorageClient getObject() {
return this.client;
}
@Override
public Class<?> getObjectType() {
return CloudStorageClient.class;
}
@Override
public boolean isSingleton() {
return true; // 存储客户端复用,不需要每次都创建
}
}
/**
* 配置属性类
*/
@ConfigurationProperties(prefix = "storage")
@Data
public class StorageProperties {
private String provider; // aliyun / tencent / aws
private String endpoint;
private String accessKey;
private String secretKey;
private String region;
private String defaultBucket;
}
/**
* 在Service中使用:直接注入CloudStorageClient,无感知工厂细节
*/
@Service
@RequiredArgsConstructor
public class FileUploadService {
private final CloudStorageClient storageClient; // 由FactoryBean提供
@Value("${storage.default-bucket}")
private String defaultBucket;
public String uploadAvatar(MultipartFile file, Long userId) throws IOException {
String key = "avatar/" + userId + "/" + System.currentTimeMillis() +
getExtension(file.getOriginalFilename());
String url = storageClient.upload(
defaultBucket,
key,
file.getInputStream(),
file.getSize()
);
log.info("Avatar uploaded for user {}: {}", userId, url);
return url;
}
private String getExtension(String filename) {
if (filename == null) return ".jpg";
int dotIndex = filename.lastIndexOf('.');
return dotIndex > 0 ? filename.substring(dotIndex) : ".jpg";
}
}4.4 工厂注册表:动态工厂选择
/**
* 工厂注册表:通过注解驱动,自动扫描并注册工厂
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface StorageProvider {
String value(); // provider标识符
}@Component
@Slf4j
public class CloudStorageClientRegistry implements ApplicationContextAware {
private final Map<String, CloudStorageClientFactory> factoryRegistry = new HashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 扫描所有带@StorageProvider注解的工厂类
Map<String, Object> beansWithAnnotation = applicationContext
.getBeansWithAnnotation(StorageProvider.class);
beansWithAnnotation.forEach((beanName, bean) -> {
if (bean instanceof CloudStorageClientFactory factory) {
StorageProvider annotation = bean.getClass().getAnnotation(StorageProvider.class);
factoryRegistry.put(annotation.value(), factory);
log.info("Registered storage factory: {} -> {}",
annotation.value(), bean.getClass().getSimpleName());
}
});
}
public CloudStorageClient createClient(String provider, StorageProperties properties) {
CloudStorageClientFactory factory = factoryRegistry.get(provider);
if (factory == null) {
throw new UnsupportedOperationException("Unsupported storage provider: " + provider);
}
return factory.buildClient(properties);
}
}五、与相关模式的对比与选型
工厂方法 vs 简单工厂
简单工厂不是 GoF 23种设计模式之一,它通过一个静态方法根据参数创建不同对象:
// 简单工厂:违反OCP,每次新增类型都要改这里
public class SimpleStorageFactory {
public static CloudStorageClient create(String provider, StorageProperties props) {
return switch (provider) {
case "aliyun" -> new AliyunOssClient(props.getEndpoint(), props.getAccessKey(), props.getSecretKey());
case "tencent" -> new TencentCosClient(props.getAccessKey(), props.getSecretKey(), props.getRegion());
default -> throw new IllegalArgumentException("Unknown provider: " + provider);
};
}
}工厂方法通过子类化替代了 if-else,符合 OCP——新增类型只需新增工厂子类,不修改已有代码。
工厂方法 vs 抽象工厂
- 工厂方法:创建单一类型的对象,关注的是"如何创建"。
- 抽象工厂:创建一族相关对象,关注的是"创建哪一套"(下一篇会详细讲)。
BeanFactory vs FactoryBean(最易混淆的概念)
| 维度 | BeanFactory | FactoryBean |
|---|---|---|
| 角色 | Spring IoC 容器本身 | 注册在容器中的特殊 Bean |
| 作用 | 管理所有 Bean 的创建和生命周期 | 封装某个复杂 Bean 的创建逻辑 |
| 实现者 | Spring 框架 | 用户/第三方框架 |
| 数量 | 一个应用一个(或少数几个) | 可以有很多个 |
| 获取方式 | ApplicationContext 本身就是 | ctx.getBean("myBean") 获取产品,ctx.getBean("&myBean") 获取工厂自身 |
六、踩坑实录
坑一:FactoryBean 的 isSingleton() 返回 false 时的性能陷阱
有一次我们在做多租户系统,需要根据租户 ID 返回不同的数据库客户端,于是把 FactoryBean.isSingleton() 返回了 false,以为 Spring 每次都会调用 getObject() 重新创建。
结果发现性能极差,因为 Spring 对非单例的 FactoryBean,每次 getBean() 都会调用 getObject(),而我们的 getObject() 里有数据库连接初始化逻辑,每次请求都建连接,直接把数据库连接数打爆了。
正确做法:在 getObject() 内部自己维护按租户 ID 的客户端缓存,isSingleton() 仍然返回 true,单独处理多实例逻辑。
坑二:循环依赖中的 FactoryBean
FactoryBean 和普通 Bean 之间存在循环依赖时,Spring 的三级缓存机制处理起来会比较棘手。有一次我们的 MyFactoryBean 依赖了 ServiceA,而 ServiceA 又依赖了 MyFactoryBean.getObject() 返回的产品 Bean,形成循环。Spring 抛出了 BeanCurrentlyInCreationException,让我们排查了很久。
解决方案:通过 @Lazy 注解打破循环,或者重构代码,避免工厂 Bean 和其产品之间的循环依赖。
坑三:忘记实现 getObjectType() 导致 autowire 失败
FactoryBean.getObjectType() 如果返回 null,Spring 无法在自动装配(@Autowired)时匹配到这个 Bean,会抛出 NoSuchBeanDefinitionException。这个错误比较隐蔽,因为从 Bean 注册的角度看,Bean 是存在的,但自动装配找不到它。
一定要实现 getObjectType() 并返回正确的类型。 如果在调用 getObject() 之前无法确定类型,可以返回一个最接近的父类或接口。
坑四:Spring Boot 自动配置与 FactoryBean 的冲突
在 Spring Boot 项目中,如果你的 FactoryBean 创建的对象类型与某个自动配置类的条件注解匹配(比如 @ConditionalOnMissingBean),可能会出现冲突。比如你自定义了一个 DataSourceFactoryBean,而 Spring Boot 的 DataSourceAutoConfiguration 也会创建一个 DataSource,两者冲突导致启动失败。
解决方案:在自动配置类上加 @ConditionalOnMissingBean(DataSource.class)(其实已经有了),或者明确排除不需要的自动配置 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)。
七、总结
工厂方法模式在 Spring 中的体现深刻而全面:
BeanFactory 是容器层面的工厂:它管理整个 Bean 的生命周期,
getBean()就是工厂方法的体现,AbstractBeanFactory→DefaultListableBeanFactory的继承体系就是工厂方法模式的具体实现。FactoryBean 是 Bean 层面的工厂:它是 Spring 留给用户的扩展点,让用户可以把任意复杂的对象创建逻辑"插入"容器,是工厂方法模式在用户代码层面的应用。
两者的本质区别:
BeanFactory是框架的基础设施,FactoryBean是用户扩展点;前者"管理工厂",后者"是工厂"。实践建议:当对象的创建逻辑复杂(涉及外部资源、多步初始化),或者需要在运行时动态决定创建哪种实现时,
FactoryBean是非常优雅的解决方案。在 Spring Boot 时代,很多场景可以用@Configuration+@Bean方法替代,但对于需要条件创建、懒加载,或者需要与 Spring 生命周期深度集成的场景,FactoryBean依然是最佳选择。
