工厂模式才是SpringCloud微服务的第一课:从原理到源码实战

📅 发布时间:2026/10/10 12:59:05
工厂模式才是SpringCloud微服务的第一课:从原理到源码实战
1. 为什么说工厂模式是SpringCloud微服务的第一课先抛个结论你如果只懂语法不懂设计模式写个几百行的单机Demo没问题。但一旦进入SpringCloud微服务这种大型工程体系工厂模式就是躲不开的一道坎。SpringCloud里大量的组件创建、接口加载、动态代理、配置刷新本质上都是“工厂”思维在驱动。你搞懂了工厂模式再去看Feign、看Gateway、看LoadBalancer的源码会顺手很多搞不懂面试被问两次也就露馅了。这篇我结合Java进阶路线和SpringCloud微服务框架把工厂模式的三种形态——简单工厂、工厂方法、抽象工厂——全部拆开讲清楚再深入到SpringCloud源码里去验证这些设计到底用在了什么地方最后用一套订单支付场景的实战重构告诉你工厂模式到底怎么落地。适合正在啃Java基础到SpringBoot过渡的朋友也适合准备SpringCloud面试的开发工程师。1.1 工厂模式到底是解决什么问题的工厂模式属于创建型模式一句话概括把对象的创建和使用分离开来。正常你写代码要一个支付服务对象直接new PaymentService()就完事了。这有什么问题问题在于“创建逻辑”侵入到了业务代码中。如果支付服务依赖数据库、依赖Redis、需要初始化连接池你把这一大坨创建逻辑写在业务方法里业务代码就脏了。更麻烦的是将来要换数据库、加缓存、升级实现类你得在业务代码里到处改new。工厂模式做的事情很简单把“怎么造对象”这个责任单独拎出来交给专门的类去负责。业务侧只说我需要一个什么类型的对象工厂负责现场生产。你不需要关心工厂内部是调构造方法、读配置、做代理还是从Spring容器里取你只拿到结果。这个“结果”就是解耦的产物。在SpringCloud里这种需求被无限放大——几十个微服务、上百个接口调用、各类组件实例如果全靠手工new谁也管理不过来。1.2 SpringCloud里其实到处都是工厂的影子很多人学SpringCloud的时候会有一种错觉设计模式是课本章节里的事跟微服务框架没有直接关系。我当年也有这个错觉后来看源码才发现SpringCloud本身就是设计模式的大型实践基地。光是工厂模式就有这些典型位置ApplicationContext和BeanFactory整个Spring容器就是一个万能工厂靠它管理所有Bean的生命周期。FeignClientFactoryBeanOpenFeign里用来生成远程接口代理对象的核心类。GatewayFilterFactory与PredicateFactorySpringCloud Gateway的过滤器和断言全部通过工厂创建。RefreshScopeSpringCloud Config配置刷新底层就是一个特殊工厂用来重建Bean实例。LoadBalancer相关的ILoadBalancer创建逻辑负载均衡器实例也是工厂生成出来的。所以你看着这些名字它们都带Factory后缀这不是凑巧是设计上的有意为之。这篇文章我会把其中几个典型实现展开讲透你会发现设计模式不是纸上谈兵。2. 三种工厂模式全拆解从简单工厂到抽象工厂教科书上通常把工厂模式拆成三类简单工厂、工厂方法、抽象工厂。很多初学者会混上手写的时候更是分不清。我的建议是你先不要纠结名字按“职责范围”来理解简单工厂是“一个工厂管所有创建”工厂方法是“一类产品配一个工厂”抽象工厂是“一个工厂管一整套产品族”。下面逐个展开。2.1 简单工厂最直观的创建入口简单工厂是工厂模式里最简单的一种形态它用一个工厂类、一个静态方法根据传入的参数返回不同的对象。网上有人说它不算真正的设计模式我认同一半它确实太“朴素”了但它在Spring里依然有大量变体应用比如各种Builder、各种Client。你先把这个看懂后面看工厂方法就容易了。public class PaymentFactory { public static PaymentService create(String channel) { if (alipay.equals(channel)) { return new AlipayService(); } else if (wechat.equals(channel)) { return new WechatService(); } else if (unionpay.equals(channel)) { return new UnionPayService(); } throw new IllegalArgumentException(未支持的支付渠道: channel); } }这个模式的好处是创建逻辑集中了业务侧少了一堆new。坏处也很明显新加一个渠道就要改create()方法违背“开闭原则”。如果渠道多到几十个这个工厂就是一个大杂烩比没有还难维护。所以它适合对象种类少、变化频率低的场景。2.2 工厂方法把创建能力下沉到子类工厂方法模式进一步解决了“扩展难”的问题。思路很简单定义一个工厂接口每个对象都有自己的专属工厂。你不再往一个大工厂里塞分支判断而是告诉工厂接口“我要哪家的”由对应的工厂子类去创建。public interface PaymentFactory { PaymentService create(); } public class AlipayFactory implements PaymentFactory { Override public PaymentService create() { return new AlipayService(); } } public class WechatFactory implements PaymentFactory { Override public PaymentService create() { return new WechatService(); } }使用的时候你可以通过Map或者Spring容器把工厂实现类管理起来调度方拿着channel找到对应的工厂再调用create()。这样新增支付渠道只需要增加一个PaymentFactory实现类不用再改原有代码。这就是设计模式的意义牺牲一点代码量换将来扩展时少改代码。2.3 抽象工厂产品族的工厂抽象工厂比工厂方法再复杂一个层次。工厂方法强调的是“一个产品一条产线”抽象工厂强调的是“一个产品族一条产线”。什么叫产品族你还是拿支付来举例光有“支付服务”不够你还需要“退款服务”、“对账服务”、“风控服务”。支付宝有支付宝的一套微信有微信的一套。你希望的是当我选择一个“支付宝产品族”时能同时给我创建支付、退款、对账这三个配套对象而且这三个对象彼此之间逻辑是协调的。public interface PayProductFactory { PaymentService createPaymentService(); RefundService createRefundService(); QueryService createQueryService(); } public class AlipayProductFactory implements PayProductFactory { Override public PaymentService createPaymentService() { return new AlipayPayService(); } Override public RefundService createRefundService() { return new AlipayRefundService(); } Override public QueryService createQueryService() { return new AlipayQueryService(); } }抽象工厂的核心价值在于“保证配套产品的组合关系不会乱”。如果支付宝的退款接口和微信的支付接口混在一起就是典型的线上事故。抽象工厂把这一族对象绑定在一个工厂里外部一旦选定工厂就不会拿到“错位组合”的对象。2.4 三种工厂模式怎么选用表格直接对比你扫一眼就知道差别在哪里对比维度简单工厂工厂方法抽象工厂粒度一个工厂管所有对象一个接口对应一个工厂一个工厂管一整套产品族对扩展的支持加类型要改工厂内部加类型只需新增工厂子类加产品族需新增整套工厂复杂度低中高适用场景类型少、分支简单类型多、独立扩展需要保证一组产品配套使用代码直观性最直观需配合容器或Map调度需要引入“族”的概念实际开发中我的建议是能用Spring容器管理的直接用容器容器就是最好的工厂容器不够用的再考虑手工实现工厂。框架层面的很多内置类就是“简单工厂工厂方法抽象工厂”的复合体。你没必要把自己框死在一种模式里组合使用才是常态。3. 翻SpringCloud源码看工厂模式的实际应用理论讲完下面来点硬核的。说白了你面试的时候能说出“工厂模式有哪三种”只是及格线能讲出“SpringCloud里的FeignClientFactoryBean是怎么用工厂模式生成代理对象的”才是加分项。这章节我们一头扎进源码和应用中看看工厂模式在SpringCloud里到底怎么活过来的。3.1 BeanFactory整个Spring容器就是最大的工厂Spring容器里有个顶层接口叫BeanFactory普通Bean的获取、注入、生命周期管理全靠它。我们写代码时天天用的Autowired就是一个“从工厂取货”的动作。很多人习惯把这层关系忽略掉但你要理解ApplicationContext本质上就是一个增强版的BeanFactory它把对象创建、配置加载、依赖注入全部封装成一个大型工厂体系。你可以自己注册一个BeanPostProcessor在Bean初始化的前后做额外处理。也可以配置各种Scope控制Bean是“单例”还是“原型”。这就是工厂模式的好处——创建逻辑和业务逻辑完全解耦。你在业务代码里根本不关心AlipayService是从反射来的、还是从配置中心来的还是从缓存里重建的反正用Autowired拿到的就是一个可用的对象。这里多说一个高频面试题BeanFactory和FactoryBean的区别。BeanFactory是容器负责管理所有BeanFactoryBean是一个接口当你需要“由工厂方式生产某个Bean”时就让定义的这个类实现FactoryBean它本身注册为Bean的时候容器会调用getObject()返回真正的目标对象。FeignClientFactoryBean就是这个机制的最佳代表。3.2 FeignClientFactoryBean远程调用接口是怎么造出来的先回忆一下你用OpenFeign的习惯定义一个接口写上FeignClient(name order-service)然后直接Autowired注入这个接口在业务代码里像本地方法一样调用远程接口。你有没有想过接口明明没有实现类这个“对象”是哪里冒出来的答案就是FeignClientFactoryBean。这个类实现了Spring的FactoryBean接口SpringCloud在启动时会扫描所有FeignClient标注的接口然后为它们创建一个FeignClientFactoryBean。等到你要注入某个Feign接口时容器调用getObject()触发内部的FeignBuilders生成一个JDK动态代理对象。代理对象内部定义好了如何拼接URL、如何序列化请求、如何处理响应然后把远程HTTP调用包装成一次本地接口调用。这个过程就是典型的工厂模式应用工厂FeignClientFactoryBean负责代理对象的创建业务侧完全不知道代理的存在。这也解释了为什么Feign接口不能手动new因为你new出来的只是一个普通接口壳子里面的代理逻辑根本没触发。3.3 GatewayFilterFilter网关层的工厂化设计微服务网关SpringCloud Gateway的设计也把工厂模式玩得很明白。网关的核心工作流是“断言是否匹配路由然后走一连串过滤器”。而Route构建时就大量使用了工厂模式RoutePredicateFactory负责创建各种断言GatewayFilterFactory负责创建各种过滤器。你打开GatewayAutoConfiguration看注册的Bean会发现一大排xxxGatewayFilterFactory、xxxRoutePredicateFactory。想自定义过滤器也很简单继承了AbstractGatewayFilterFactory在apply()方法里返回一个GatewayFilter然后注册成Bean就可以在配置文件中通过对应的名称直接引用了。这就是工厂模式的“框架化应用”——框架不关心你具体要什么过滤器只按照工厂产出的标准接口来干活。3.4 负载均衡与配置刷新里的工厂逻辑负载均衡方面SpringCloud LoadBalancer里的LoadBalancerClientFactory同样承担了“按需创建负载均衡器”的职责。它根据服务名、服务实例、负载均衡策略通过工厂机制生成对应的ReactorLoadBalancer。日常使用LoadBalancedRestTemplate时请求会被LoadBalancerInterceptor拦截然后从工厂中取出负载均衡器去选择服务实例。你如果自己实现过自定义负载均衡算法应该也实现了对应的ReactorLoadBalancer和ServiceInstanceListSupplier这些都是靠工厂接进去的。再讲一个RefreshScope。这个注解的底层是Spring Cloud Config实现配置动态刷新的核心它的原理就是工厂模式的一个高级变体。当你加上RefreshScope这个Bean就被放入一个特殊的ScopeRefreshScope由ScopedProxyFactoryBean生成代理。配置中心的/actuator/refresh接口一旦触发就会清除这个Scope里的缓存下一次Bean被调用时工厂会重新创建整个Bean把最新的配置注进去。所以你会觉得“配置一刷新代码里的值就变了”实际上是工厂把旧对象丢弃、造了个新对象。4. 实操实战用工厂模式重构订单支付渠道光看原理不过瘾我直接用一个真实业务场景带你走一遍工厂模式的落地流程。场景是电商系统的订单支付要对接支付宝、微信支付、银联三种渠道未来还可能要加新的渠道。这个案例我实际在项目里用过改造前后的代码差距很直观你可以直接照着抄。4.1 需求场景与重构前的写法需求本身很简单用户在前端选择支付渠道后端根据渠道类型调用对应的支付服务发起支付。最初项目里是下面这种写法逻辑也很直白渠道传alipay就走支付宝wechat就走微信没了。这种代码放到10个渠道以内还能忍一旦超过10个if-else就变成一个巨大的仓库你每加一个渠道就要改这段核心订单逻辑。public PayResult pay(Long orderId, String channel) { if (alipay.equals(channel)) { AlipayService alipay new AlipayService(); return alipay.pay(orderId); } else if (wechat.equals(channel)) { WechatService wechat new WechatService(); return wechat.pay(orderId); } else if (unionpay.equals(channel)) { UnionPayService unionpay new UnionPayService(); return unionpay.pay(orderId); } else { throw new RuntimeException(不支持的渠道: channel); } }这段代码的硬伤有三个第一new对象的细节写死在订单业务中耦合严重第二增加渠道时必须修改主流程谁改谁害怕第三如果每个渠道的创建都需要复杂的初始化参数这个方法就会变成天大的泥潭。4.2 用工厂加策略模式重构重构的思路很简单把“选择渠道逻辑”和“各渠道具体实现”拆开。先定义一个统一的支付服务接口public interface PaymentService { String channelCode(); PayResult pay(Long orderId); }每个渠道实现这个接口Component public class AlipayService implements PaymentService { Override public String channelCode() { return alipay; } Override public PayResult pay(Long orderId) { // 支付宝支付的完整逻辑 return PayResult.success(支付宝支付成功); } } Component public class WechatService implements PaymentService { Override public String channelCode() { return wechat; } Override public PayResult pay(Long orderId) { // 微信支付的完整逻辑 return PayResult.success(微信支付成功); } }再搞一个工厂类但这里的重点是利用Spring容器来自动收集所有PaymentService的Bean按照channelCode()建一个索引。这样以后新加渠道Spring容器会自动把它收进工厂完全不需要改工厂代码Component public class PaymentFactory { private final MapString, PaymentService serviceMap; public PaymentFactory(ListPaymentService services) { this.serviceMap services.stream() .collect(Collectors.toMap(PaymentService::channelCode, Function.identity())); } public PaymentService getService(String channelCode) { PaymentService service serviceMap.get(channelCode); if (service null) { throw new RuntimeException(未支持的支付渠道: channelCode); } return service; } }改造后的订单支付逻辑就变得很清爽RestController public class OrderPayController { private final PaymentFactory paymentFactory; public OrderPayController(PaymentFactory paymentFactory) { this.paymentFactory paymentFactory; } PostMapping(/pay) public PayResult pay(RequestParam Long orderId, RequestParam String channel) { PaymentService paymentService paymentFactory.getService(channel); return paymentService.pay(orderId); } }你看订单主链路完全不知道“支付宝”或“微信”的存在它只面向一个PaymentService接口。新增渠道时只需要新增一个Component实现类主流程一行不改。这就是典型的“针对接口编程不针对实现编程”。4.3 进一步升级工厂与配置中心的联动如果你觉得上面这套已经够用了我再给你加一个SpringCloud的进阶玩法。在微服务项目中渠道列表不是固定的运营后台可能需要动态启停某个支付渠道。此时你可以把“启用的渠道”放到配置中心里然后用ConditionalOnProperty或者自定义的Conditional注解来控制Component是否被实例化。举个例子支付宝渠道开关配置payment.channel.alipay.enabledtrue那么AlipayService上的条件就满足Spring容器会创建它并把它注入PaymentFactory如果配置改成falseBean不创建工厂里自然没有支付宝渠道前端选择支付宝时直接提示“渠道下线”。这就是工厂模式和SpringCloud Config的配合你再也不用为了下线一个渠道改代码、发版本了。4.4 重构前后对比我用表格给你总结一下重构的收益对比项重构前if-else new重构后工厂 Spring容器 策略新增渠道改订单主流程风险大新增一个实现类主流程不动渠道启停需要改代码或加if开关配中心控制实时生效业务耦合度高订单逻辑知道所有渠道低订单逻辑只面向接口代码可测试性弱测试要覆盖分支强每个渠道可以单独写测试与SpringCloud集成弱天然被容器管理支持配置刷新两组代码对比下来你就能感受到设计模式带来的工程价值。它不是炫技它是在帮你将来少改几次代码少掉几次头发。5. 工厂模式避坑指南与面试题解析最后这个部分我把自己在实际项目中踩过的坑和总结出的面试经验分享出来。设计模式这玩意儿写起来很容易用得恰到好处很难。有些坑我在带团队时见得太多了专门列出来提醒你。5.1 三个常见的设计坑第一个坑是“为模式而模式”。有些项目里一个接口只有两个实现类也硬要套工厂方法搞得很隆重结果代码量翻了一倍。模式的前提是“变化的维度足够多”如果你的对象创建逻辑两行就能写完、未来三五年都不会扩展那直接new反而最合理。第二个坑是“工厂里写业务逻辑”。我见过有的同事把工厂写得无比庞大不仅做对象创建还顺带处理参数校验、日志记录、状态机流转。这就违背了“单一职责原则”工厂的正确职责只有一个创建并返回对象。其他任何额外的逻辑都应该挪到对应的策略类里。第三个坑和SpringCloud直接相关手动new的对象是无法被Spring容器管理的。你在工厂里如果直接new AlipayService()那么这个对象内部如果依赖了Value配置、RedisTemplate、别的Spring Bean就会得到空指针。正确做法是让工厂接收Spring容器或ListT接口自动收集或者使用SpringContextUtils.getBean()来获取而不是手工new。这个坑在微服务项目里最凶因为你的服务依赖特别多手动new几乎必炸。5.2 结合SpringCloud的经典面试题这里的面试题不是让你背答案而是帮你理解出题人的意图。先说一个最经典的“Spring中的BeanFactory和FactoryBean有什么区别”我个人被问到过也问过别人。BeanFactory是整个容器的核心负责所有Bean的管理FactoryBean是Bean的一种特殊形态容器会拿它生产出来的对象去注册。Feign接口能被注入就是靠FeignClientFactoryBean你只要把这一点联系起来面试官就知道你是真懂容器和Feign的原理。第二个面试题“Feign客户端是如何通过工厂模式创建的”你要讲清楚EnableFeignClients扫描接口注册FeignClientFactoryBean触发getObject()之后经FeignBuilders生成JDK动态代理代理内部封装HTTP请求过程。整个过程体现了工厂模式的“隐藏创建细节”和“统一创建入口”两大价值。第三个面试题“抽象工厂和工厂方法在实际开发中如何选择”我的回答套路是如果产品之间没有“组合配套”的概念各产品独立变化就用工厂方法如果产品之间存在固定的搭配关系尤其是一个体系内的产品必须一起工作就用抽象工厂。再举一个SpringCloud里的例子网关的PredicateFactory和FilterFactory虽然各管一摊但它们在构建一条路由时是协同配置的网关配置里的Predicates和Filters就是产品族配合的体现。5.3 实测排查实录与经验心得最后分享一个我印象深刻的排查经历。之前接手一个支付服务循环依赖问题频发OrderService依赖PaymentFactoryPaymentFactory的构造函数又自动收集了所有PaymentService而某个PaymentService实现里又依赖了OrderService。启动时Spring容器报“Cycle dependency”。我当时的处理方案是调整PaymentFactory的获取时机先让它缓存在ApplicationContext里用的时候再根据名称动态取Bean而不是构造函数阶段全量收集。这个思路也体现在SpringCloud很多源码里它们在处理循环依赖时往往不用强依赖注入而是走ObjectProvider延迟解析。踩过几次坑之后我个人的经验是工厂模式在SpringCloud里的正确打法是“容器为核心接口为标准注解为配置”。尽量让Spring容器来做对象的收集和注入工厂类负责合理的调度和路由。你手动写一个很大的工厂没有意义因为容器本身就是一个比你聪明得多的工厂。设计模式这个东西单独看每一本书都很抽象放到项目里你会越来越觉得它是“解决工程问题的语言”。我在实际项目中之后接定制化开发、对接外部渠道、做插件类功能时几乎都默认走这一套接口定义能力边界Spring容器收集实现工厂统一调度。建议你拿自己手头的项目也试一试先把代码重构一遍再回去看SpringCloud源码你会发现之前看不懂的那些Factory、那些Builder开始变得亲切了。