在Spring Boot项目里,当我们需要把某个外部资源客户端或者复杂对象交给容器管理,又不想让使用方关心具体的构造细节时,FactoryBean配合自定义@Enable注解是一种非常实用的做法。FactoryBean本身是一个工厂接口,它返回的并不是自身实例,而是getObject方法产出的目标对象。通过这种方式,我们可以把创建逻辑、参数校验、依赖装配全部封装在工厂内部。再结合@Import与@Enable注解,就能做到“哪里的配置类加了注解,哪里才注册对应Bean”,从而实现按需加载与模块解耦。

FactoryBean接口契约与基本用法
FactoryBean位于org.springframework.beans.factory包下,定义了三个核心方法:getObject返回实际对象,getObjectType指明对象类型,isSingleton标记是否单例。与普通的@Bean方法相比,FactoryBean更适合封装那些构造过程复杂、依赖环境变量、或者需要延迟初始化的对象。例如一个消息队列的Producer,它需要先读取配置、建立网络连接、再返回可发送实例,这种场景用FactoryBean就非常自然。
下面是一段简化版的FactoryBean实现,它根据配置前缀构建一个假想的客户端。注意在getObject中我们做了基础校验,如果必要参数缺失就抛出异常,避免把问题留到运行时。这种方式让Bean的创建失败可以尽早暴露,也方便单元测试时替换实现。
public class DemoClientFactoryBean implements FactoryBean<DemoClient> {
private String endpoint;
private String token;
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public void setToken(String token) {
this.token = token;
}
@Override
public DemoClient getObject() throws Exception {
if (endpoint == null || token == null) {
throw new IllegalArgumentException("endpoint与token必须配置");
}
return new DemoClient(endpoint, token);
}
@Override
public Class<?> getObjectType() {
return DemoClient.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
把上面的工厂注册到容器时,通常我们会用一个@Configuration类来声明它,并通过@Bean方法把属性注入进去。不过如果项目里有多个类似模块,每个都写一遍配置类会显得啰嗦。这时就可以引入@Enable注解,把“是否启用”的开关交给使用方。
自定义@Enable注解与@Import导入机制
Spring的@Import注解支持导入普通配置类、ImportSelector或者ImportBeanDefinitionRegistrar。对于整合FactoryBean来说,最简单的方式是直接导入一个配置类,该配置类内部声明FactoryBean。这样用户在启动类或任意配置类上写@EnableDemoClient,就等价于导入了对应的Bean定义。
下面我们定义注解与配置类。注解本身用@Import指向DemoClientConfiguration,并加上@Documented与@Retention保证元信息完整。这种写法的好处是:使用方完全不需要知道FactoryBean的存在,只看到一个语义清晰的开关。同时如果将来要改成条件化导入,只需把导入目标换成ImportSelector即可,对外的注解不变。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DemoClientConfiguration.class)
public @interface EnableDemoClient {
}
@Configuration
public class DemoClientConfiguration {
@Bean
public DemoClientFactoryBean demoClientFactoryBean(
@Value("${demo.endpoint:}") String endpoint,
@Value("${demo.token:}") String token) {
DemoClientFactoryBean factory = new DemoClientFactoryBean();
factory.setEndpoint(endpoint);
factory.setToken(token);
return factory;
}
}
如果希望更灵活,比如根据配置文件里的开关决定是否注册,可以实现ImportSelector读取Environment再返回类名数组。相比直接导入配置类,ImportSelector能在编译期之外做运行期决策,适合做starter式的自动装配。不过对于大多数内部模块整合,直接导入配置类已经足够清晰,也更容易被团队理解和维护。
在Spring Boot中整合与避免常见误区
在真实Spring Boot应用里,我们把@EnableDemoClient加在@SpringBootApplication主类或者某个业务配置类上即可。由于FactoryBean最终产出的是DemoClient,其他组件直接注入DemoClient就能拿到客户端,而不需要关心背后是工厂。如果确实需要拿到FactoryBean本身,可以在Bean名称前加&符号,例如applicationContext.getBean("&demoClientFactoryBean"),这是Spring约定的工厂前缀规则。
一个常见误区是以为FactoryBean会参与@ComponentScan并被自动发现。其实FactoryBean只是一个接口实现,如果你用@Component标在工厂类上,它确实会被扫到,但往往缺少必要的属性设置。更好的做法是通过显式的@Bean方法或@Enable导入来装配,这样参数来源、默认值、校验逻辑都集中在配置类里,不会出现“扫到了却没法用”的半吊子状态。另外当FactoryBean产出的是连接型资源时,记得实现DisposableBean或在配置类里指定销毁方法,防止应用关闭时连接泄漏。
最后要注意与Spring Boot配置绑定的配合。上面的例子用了@Value,如果参数较多,建议改成@ConfigurationProperties绑定到一个属性类,再传给FactoryBean。这样既能利用Relaxed Binding,也方便在application.yml里分组管理。整体来看,@Enable加FactoryBean的组合让模块既能独立封装创建逻辑,又对外保持极低的接入成本,是Spring Boot整合第三方组件时值得常用的一种模式。
SpringBootEnable注解FactoryBean修改时间:2026-08-15 23:40:35