资讯详情

Spring工厂模式全解析:从BeanFactory到策略工厂实践

📅 2026/10/18 8:32:57 | 华诺云谱 👁 阅读
Spring工厂模式全解析:从BeanFactory到策略工厂实践
1. 从BeanFactory开始——Spring工厂模式的原始底座1.1 为什么说BeanFactory才是工厂模式的原型接触Spring的开发者大概率都听过这句话——“Spring的核心就是一个Bean工厂”。这句话不是比喻而是字面意义上的描述。Spring最底层、最基础的功能就是创建并管理对象而这个创建与管理工作正是通过工厂模式实现的。BeanFactory就是这个工厂的标准接口所有的对象在Spring容器中都以Bean的形式存在开发者只管声明依赖关系具体什么时候new、怎么new、怎么注入全部由容器这个“超级工厂”负责。如果你去看BeanFactory接口的源码会发现它的方法列表非常克制getBean、containsBean、isSingleton、getType这些核心动作其实就一个——“根据名称或类型获取一个对象”。这个设计本质上就是工厂方法模式中“用一个工厂方法代替new操作”的典型体现。调用方不关心Bean是怎么来的、来自XML还是注解、是单例还是原型只需要拿到一个可用的对象即可。1.2 BeanFactory与工厂模式的对应关系工厂模式有三种常见形态简单工厂、工厂方法、抽象工厂。Spring并不仅仅套用了其中某一种而是把三种形态都融进了自身的设计里。准确来说BeanFactory本身更像一个“抽象工厂”——它定义了创建和管理对象的总接口而具体的容器实现比如XmlBeanFactory、AnnotationConfigApplicationContext则承担了工厂方法模式中“具体工厂”的角色。每一个Bean的定义信息BeanDefinition就像是产品的配方容器根据配方生产出对应的对象实例。举个例子当你在ApplicationContext中调用getBean(userService)时背后发生的事情远远不是“new一个UserService”这么简单。容器会先获取UserService对应的BeanDefinition解析构造函数判断是否需要处理循环依赖检查AOP切面是否需要生成代理对象再考虑BeanPostProcessor是否要对实例进行增强。这一整套流程本质上就是一个高度定制化的工厂生产线远比简单的new复杂得多也强大得多。2. Spring中工厂模式的几种常见落地形态2.1 BeanFactory与ApplicationContext的差别很多初学者分不清BeanFactory和ApplicationContext这其实很正常因为两者的关系就是“接口与实现增强”。BeanFactory是Spring容器的最底层规范ApplicationContext在BeanFactory的基础上增加了国际化、事件发布、资源加载、自动扫描等能力。从工厂模式的角度看两者都属于工厂只是ApplicationContext是一个强化版工厂。我在实际开发中几乎不会直接使用BeanFactory因为它太底层了连BeanPostProcessor的自动注册都需要手动处理。但理解BeanFactory依然是必要的因为它是理解Spring容器生命周期的起点。ApplicationContext在启动时默认会实例化所有单例Bean而BeanFactory则是在第一次调用getBean时才创建对象。这个差异在调试启动慢、排查初始化顺序问题时非常关键。2.2 FactoryBean让工厂里面套工厂如果说BeanFactory是整个容器的总工厂那么FactoryBean就是允许你在容器内部“自定义某个Bean的工厂”。FactoryBean是Spring提供的一个接口专门用于那些不能简单通过构造函数创建的Bean。典型的例子就是MyBatis的MapperFactoryBean——每个Mapper接口都是动态代理对象根本不是普通的类实例Spring没法直接用反射new出来于是让FactoryBean来负责生成代理。你向容器请求“userMapper”这个Bean时容器的逻辑是发现这个Bean定义对应的是一个FactoryBean实现于是调用该实现类的getObject()方法把返回的对象交给你。这里有一个很容易踩坑的地方。如果你用getBean(userMapper)去获取得到的是FactoryBean创建的产品对象但如果你写的是getBean(userMapper)得到的才是FactoryBean本身。这个“”前缀是Spring约定俗成的特殊标记新手经常会在这里被绕晕。2.3 Configuration和Bean注解下的工厂思维回到日常开发最常见的工厂模式实践其实是Bean注解。每一个被Bean标注的方法本质上就是一个工厂方法。方法名是Bean的名称方法返回的对象就是产品。比如你配置一个RestTemplateConfiguration public class RestConfig { Bean public RestTemplate restTemplate() { return new RestTemplateBuilder() .setConnectTimeout(Duration.ofSeconds(3)) .build(); } }这个方法就是典型的简单工厂写法。Spring容器在启动时会调用restTemplate()方法把返回值纳入容器管理。如果是通过启动类的ComponentScan扫描到的配置类Spring还会为这个配置类生成一个CGLIB代理确保Bean方法在同一个容器内只执行一次。这里就有个容易忽略的细节既然Bean方法会被代理那就不要在配置类内部直接调用另一个Bean方法对应的“方法”本体而应该通过依赖注入去拿Bean。否则你可能会发现代理逻辑没有生效对象被多创建了一次。3. 静态工厂、实例工厂与FactoryBean的实战选择3.1 静态工厂方法的配置与局限静态工厂方法是指通过某个类的静态方法来创建对象这是Java老项目中比较常见的做法。Spring完全支持这种模式只需要在XML或注解中指定工厂方法即可。XLM配置时代是这样的bean idcalendar classjava.util.Calendar factory-methodgetInstance/换成注解后你要做的就是自己去写Bean方法在方法内部调用静态工厂方法。比如创建UUID、创建ObjectMapper等工具类对象时这种写法依然很实用。静态工厂方法的优点是用起来简单、清晰缺点是无法扩展——你不能对静态工厂的返回值做后续处理。所以后来Spring更推荐直接用Bean方法把创建逻辑写在方法内部灵活性高得多。3.2 实例工厂方法工厂对象作为Bean实例工厂方法的意思是工厂本身也是一个Bean通过工厂对象的实例方法来创建其他Bean。听起来有点绕我给你一个生活化的类比静态工厂就像学校门口的自动打印机你投硬币它出文件不用管打印机本身实例工厂就像找一家打印店让店员帮你打印你不但指定打印数量还能让店员帮你装订、裁剪。在Spring中实现实例工厂的形式很简单Component public class CarFactory { public Car createBmw() { Car car new Car(); car.setBrand(BMW); car.setType(sedan); return car; } } Configuration public class CarConfig { Autowired private CarFactory carFactory; Bean public Car bmwCar() { return carFactory.createBmw(); } }这种写法的好处是你可以在创建对象的同一个方法里加入复杂的业务逻辑工厂对象自己也可以被注入其他依赖。它比静态工厂方法更灵活比直接new更符合“依赖倒置”原则。3.3 三种方案怎么选静态工厂方法、实例工厂方法、FactoryBean这三者看起来都能创建对象但侧重点完全不同静态工厂方法用于创建JDK自带的、有现成静态方法返回实例的工具类比如Calendar、UUID、ObjectMapper。实例工厂方法适用于创建过程依赖其他Spring Bean、或者需要按不同条件返回不同子类型的场景。FactoryBean适用于创建动态代理、生成字节码对象、或者Bean的创建过程不能被Spring常规扫描机制处理的场景比如MyBatis的Mapper、Feign的Client。我的经验是日常业务代码里用Bean就够了根本不需要考虑FactoryBean只有写框架级代码、或者集成第三方组件时才需要动FactoryBean。4. 生产中更常用的模式——策略工厂的搭建实践4.1 场景设定支付渠道选择的痛点假设你正在做一个聚合支付模块项目里有支付宝、微信、银行卡三种支付方式每种支付方式有不同的参数组装、签名逻辑和下单接口。如果在一个Service里写if else判断渠道后续每加一个渠道就要改动这个Service的代码这明显违反了开闭原则。这时候就可以用工厂模式来改造定义统一的支付接口每种支付渠道实现这个接口再用一个工厂根据渠道编号返回对应的实现类。public interface PaymentChannel { boolean supports(String channel); PayResult pay(PayRequest request); } Component public class AlipayChannel implements PaymentChannel { Override public boolean supports(String channel) { return alipay.equals(channel); } Override public PayResult pay(PayRequest request) { // 支付宝相关逻辑 return new PayResult(success, 支付宝下单成功); } } Component public class WechatChannel implements PaymentChannel { Override public boolean supports(String channel) { return wechat.equals(channel); } Override public PayResult pay(PayRequest request) { // 微信相关逻辑 return new PayResult(success, 微信下单成功); } }4.2 工厂类的两种实现方式第一种是注入所有实现类到List中遍历查找支持当前渠道的BeanComponent public class PaymentFactory { private final ListPaymentChannel channels; public PaymentFactory(ListPaymentChannel channels) { this.channels channels; } public PaymentChannel getChannel(String channel) { return channels.stream() .filter(c - c.supports(channel)) .findFirst() .orElseThrow(() - new IllegalArgumentException(不支持的支付渠道: channel)); } }使用的时候是这样的Service public class PaymentService { private final PaymentFactory paymentFactory; public PaymentService(PaymentFactory paymentFactory) { this.paymentFactory paymentFactory; } public PayResult pay(String channel, PayRequest request) { PaymentChannel paymentChannel paymentFactory.getChannel(channel); return paymentChannel.pay(request); } }第二种是用Map的方式key是渠道编号value是Bean实例Component public class PaymentFactory { private final MapString, PaymentChannel channelMap; public PaymentFactory(MapString, PaymentChannel channelMap) { this.channelMap channelMap; } public PaymentChannel getChannel(String channel) { PaymentChannel paymentChannel channelMap.get(channel); if (paymentChannel null) { throw new IllegalArgumentException(不支持的支付渠道: channel); } return paymentChannel; } }两种写法的区别在于定位Bean的方式不同。第一种依赖supports()方法去匹配每次匹配都是遍历集合效率上略低于Map的直接get。第二种直接用Map.get定位速度更快但对Bean的命名和配置有要求——Spring会把Map的key自动设置为Bean的名称所以你的Component注解里最好显式指定名称比如Component(alipay)。4.3 扩展工厂时最舒服的一点这种策略工厂最大的优势在于扩展性。新接入一个云闪付渠道时只需要新建一个CloudPayChannel实现PaymentChannel自动注册到容器工厂不用改一行代码调用方也不用改。这就是工厂模式与Spring容器结合后产生的化学反应——容器的自动装配机制帮我们完成了“注册”的动作工厂本身只需要负责“选择”。我在项目里常用的一个做法是把渠道编号定义成枚举然后在实现类的supports方法里返回枚举的字符串值避免魔法值散落各处。5. 工厂模式与Spring生命周期的结合点5.1 BeanPostProcessor工厂产出前的最后一道工序Spring容器的强大之处不在于“创建对象”而在于“在创建对象的过程中提供横切能力”。BeanPostProcessor就是这种横切能力的核心参与方。它在Bean实例创建完成后、初始化方法执行前后回调你可以在这时对Bean做代理、包装、属性修改等操作。从工厂模式的角度理解BeanPostProcessor就相当于“产品出厂前的质检员和改装师”。工厂生产出原始对象质检员按需增加特性。AOP的实现正是基于这一点——Spring在BeanPostProcessor的回调中判断这个Bean是否需要生成代理对象如果需要就返回代理对象不需要则返回原始对象。如果你自己实现了一个BeanPostProcessor一定不要忽略返回值的细节。BeanPostProcessor的postProcessAfterInitialization方法是有返回值的你返回的如果是null后续的处理器就会被跳过这个坑会导致神秘的空指针问题。5.2 SmartFactoryBean和延迟创建的设计取舍Spring还提供了一个SmartFactoryBean接口给FactoryBean增加了isPrototype和isEagerInit等控制能力。isEagerInit返回false时这个FactoryBean关联的Bean不会被容器启动时强行创建而是等到第一次getBean时才实例化。这在你的Bean创建成本较高、且不总是需要被容器启动逻辑触碰的场景下非常有用。我做过一个集成某个报表引擎的模块其核心对象的创建需要加载大量模板文件启动时初始化要花掉一两秒。用SmartFactoryBean把isEagerInit置为false后应用可以快速启动报表对象在用户访问时才构建。这是一种很典型的“延迟初始化”与工厂模式结合的设计。5.3 ObjectProvider工厂模式的“按需获取”变体Spring从4.3版本开始引入了ObjectProvider它的用法非常符合工厂模式的“按需获取”思想。当你注入一个ObjectProvider 时你并不是直接拿到T对象而是拿到一个“对象提供器”你可以在需要的时机调用它的getObject()方法得到真正的Bean。这个能力在处理可选依赖时特别顺手。比如某个功能强依赖Redis但这个模块又要在没有Redis时降级运行Service public class SomeService { private final ObjectProviderRedisTemplateString, String redisProvider; public SomeService(ObjectProviderRedisTemplateString, String redisProvider) { this.redisProvider redisProvider; } public void doSomething() { RedisTemplateString, String redisTemplate redisProvider.getIfAvailable(); if (redisTemplate ! null) { // 使用Redis执行逻辑 } else { // 降级处理 } } }ObjectProvider本身也是Spring容器这个“大工厂”暴露给开发者的一种受控访问入口它比直接注入Bean延迟了“拿到对象”的时机给了更灵活的控制空间。6. 常见问题与排查经验6.1 在Bean方法里直接new对象导致AOP失效这个问题很典型。假设你有一个被Transactional标注的Service你在某个配置类里用Bean方法创建它Bean public UserService userService() { return new UserService(); }看起来没问题但你会发现事务失效。原因在于容器中注册的是你自己new出来的UserService原始对象而不是经过AOP代理的增强对象。Spring的AOP代理依赖BeanPostProcessor在Bean创建后对返回对象做包装如果你的Bean方法里自己new等于绕过了代理的挂载。解决方式很简单UserService本身标注好Component、Service等注解通过组件扫描让容器自己管理如果必须显式用Bean创建那就注入它依赖的对象让容器辅助完成装配而不是自己在方法里new。6.2 getBean(xxx)拿到了FactoryBean而不是产品对象我们前面提过“”前缀的问题。如果某个Bean是通过FactoryBean方式创建的你又用相同名称去getBean默认拿到的其实是FactoryBean.getObject()的返回值。想拿FactoryBean本身要用“”前缀。如果你在一个项目的启动日志里看到两种类型的输出比如“userMapper”和“userMapper”不要慌这是正常的。Spring的BeanDefinition已经保留了这两者的注册信息。6.3 Bean方法会被调用多次这里分情况。如果Bean定义在普通的非配置类里或者配置类没有被Spring代理那么每次调用Bean方法都会执行方法体产生新对象。如果配置类被CGLIB代理了Spring会拦截Bean方法调用并优先从容器中获取已有的Bean。所以如果你想在一个类中直接调用另一个Bean方法来获取同一个Bean实例注意你的配置类是否真正被代理。比如声明了Configuration的配置类会被代理方法内调用同一个类中其他Bean方法时能拿到同一个实例。而在加了Configuration(proxyBeanMethods false)的轻量模式下就没有这种保证每次调用都会创建新实例。从工厂模式角度去理解这件事就更清晰了proxyBeanMethods true时Spring为配置类创建了一个“代理工厂”所有Bean方法都走代理工厂分发proxyBeanMethods false时配置类只是普通类Bean方法就是普通的对象创建方法。6.4 策略工厂中Bean名称冲突导致Map注入异常Spring将Bean注入Map时键值默认是Bean名称。如果你的两个实现类不小心声明了相同的Component名称启动就会报BeanDefinitionOverrideException。这个问题在多人协作时经常发生解决方法是规范命名或者干脆在实现类里显式指定名称与枚举值保持一致。7. 从工厂到领域模型的扩展手法7.1 工厂模式与构造器模式配合使用如果你的对象创建参数较多建议不要在工厂方法里堆太多参数。可以先在工厂方法内调用Builder或者构造器方法生成对象再把工厂方法的职责限定在“决策”而不是“构建细节”上。比如Component public class OrderFactory { public Order createOrder(OrderType type, Long userId, BigDecimal amount) { return Order.builder() .orderNo(generateOrderNo(type)) .userId(userId) .amount(amount) .status(OrderStatus.INIT) .build(); } }工厂负责“怎么选择正确的产品”构造器负责“产品内部的完整性”两者分工明确。这种写法的好处是测试友好——你可以单独测试工厂的决策逻辑不需要真的去构造一个完整的复杂订单。7.2 抽象工厂在跨模块协作中的体现当你需要创建一组相互关联的产品对象时抽象工厂就有用武之地了。比如你做一个报表系统需要同时创建“PDF格式报表”和“Excel格式报表”它们各自有对应的导出器和元数据解析器。这时可以定义ReportFactory接口为每个格式提供一套完整的工厂实现。不过说句实在话在Spring项目里抽象工厂用得并不多——因为Spring容器本身就是抽象工厂的典范实现。你声明接口容器帮你选择实现开发者真正手工写抽象工厂的场景非常少。更多时候我们把工厂收紧成单层策略工厂就够了强行抽象反而增加理解成本。7.3 用工厂模式隐藏第三方组件的复杂度还有一种常见实践用工厂模式封装对第三方SDK的接入。比如你得接入一个短信供应商的SDKSDK要求先初始化Client、设置签名、预热连接池然后才能发送短信。如果在业务代码里暴露这些初始化流程谁调用谁都头疼。用一个短息工厂Component public class SmsClientFactory { private SmsClient smsClient; PostConstruct public void init() { SmsConfig config new SmsConfig(); config.setEndpoint(https://sms.example.com); config.setAccessKey(xxx); config.setSecretKey(xxx); this.smsClient new SmsClient(config); } public SmsClient getClient() { return smsClient; } }这样外部调用方只需要依赖SmsClientFactory完全不用关心SDK的内部初始化细节。工厂模式在这里起到了“封装复杂创建过程”的作用也让后续替换SDK供应商时只需要改动工厂内部实现业务层零感知。8. 我的几点心得与踩坑总结说实话工厂模式是我在Spring项目里最常用、也最容易“用过头”的设计模式。自己刚工作那会儿恨不得把所有Service都套上一层工厂结果项目里全是绕来绕去的间接层代码阅读成本急剧上升。后来慢慢摸出两个判断标准如果你只是把一个类的创建过程简单包一层而创建逻辑根本不会变化、也没有多种实现可选那这个工厂大概率是多余的。Spring容器本身已经在管理Bean你再去套工厂属于重复建设。如果你确实有多个实现类需要根据运行时条件选择或者创建过程需要依赖容器中的其他Bean、需要做复杂的初始化组装那么工厂模式才值得用。另一个让我印象深刻的点是工厂模式与Spring的结合点大多数场景不在“创建对象”本身而在于“选择哪个对象”。Spring已经帮我们做好了创建你只需要管好选择策略——这也是我在做支付、消息、文件存储这类多实现功能时的首选方案。最后分享一个小技巧。当你用策略工厂时一定要把工厂接口的入参设计成“稳定的上下文对象”而不是一堆散参数。比如之前那个支付渠道的选择传入的是一个PayRequest对象而不是三个字符串参数。这样做的好处是将来接口增加参数时不需要改动工厂和所有实现类的签名只改PayRequest本身即可。这个原则在工厂模式里尤其重要却经常被忽略。希望这些内容对你理解Spring中的工厂模式有帮助。如果后续遇到具体问题比如FactoryBean不生效、策略工厂拿不到实现类、Bean方法被重复调用建议先回头看看类路径上有没有未扫描的包、Bean名称是否冲突、配置类有没有被CGLIB代理大部分问题都出在这几个点上。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑