Skip to content
Go back

策略模式——Spring如何用Map注入消除if-else

策略模式:如何用一行 @Autowired Map 消灭所有 if-else?

一句话结论(30s)

策略模式的本质是把「可替换的算法」从调用方抽离成独立策略,让新增策略零修改主流程。关键设计是 Spring 的 @Autowired Map<String, PayStrategy> 一行注入——容器自动收集所有策略实现并以 Bean 名为 key 组装成 Map。权衡:策略类数量会增多,但因为消除了 if-else 分支、完全符合开闭原则,值得。

核心原理(2min)

主流程:定义策略接口 → 每个算法实现一个 @Component 策略类并返回自己的 getType() 标识 → 容器注入 Map<String, PayStrategy> → 调用方 strategyMap.get(type) 路由后执行 pay()。关键机制在 Spring 依赖注入:当注入目标类型为 Map<String, T> 时,容器收集所有 T 类型 Bean、以 Bean 名称为 key 注入 Map,于是策略的「注册、发现、路由」全部交给容器,调用端只剩一行 get。源码落地:Spring 的 HandlerMethodArgumentResolverResource 按类型路由,以及支付/通知/优惠券等多渠道业务路由都采用此模式;它与状态模式结构相同,区别只在策略由调用方显式选择、状态由内部状态机自动切换。

底层深入(5-10min)

问题:支付方式选择的 if-else

public void pay(String type, BigDecimal amount) {
    if ("wechat".equals(type)) {
        // 微信支付逻辑
    } else if ("alipay".equals(type)) {
        // 支付宝逻辑
    } else if ("bankCard".equals(type)) {
        // 银行卡逻辑
    } else {
        throw new IllegalArgumentException("不支持");
    }
}

每次新增支付方式都要改这个方法 → 违背开闭原则 → 有一天 if-else 变成 20 个分支 → 圈复杂度爆炸。

💭 思考穿插:if-else 到底哪里有问题?分支少时它完全没问题,但它的成本随分支线性增长——每加一种支付方式都要改这段代码、都要重新测试这条路径,20 个分支的圈复杂度也让人难读难维护。策略模式的核心不是「去掉 if-else 这个语法」,而是「把可替换算法从调用方抽离」,让新增不再动主流程。

策略模式的标准实现

// 策略接口
public interface PayStrategy {
    void pay(BigDecimal amount);
    String getType();  // 返回策略标识
}

// 各策略实现
@Component
public class WechatPayStrategy implements PayStrategy {
    public String getType() { return "wechat"; }
    public void pay(BigDecimal amount) { /* 微信支付 */ }
}

@Component
public class AlipayStrategy implements PayStrategy {
    public String getType() { return "alipay"; }
    public void pay(BigDecimal amount) { /* 支付宝支付 */ }
}

Spring 的一行注入魔法

@Service
public class PayContext {
    @Autowired
    private Map<String, PayStrategy> strategyMap;

    public void pay(String type, BigDecimal amount) {
        PayStrategy strategy = strategyMap.get(type);
        if (strategy == null) throw new IllegalArgumentException("不支持");
        strategy.pay(amount);
    }
}

Spring 检测到注入目标类型是 Map<String, T> → 收集所有实现 T 的 Bean → 以 Bean 名称为 key 注入到 Map 中。

新增一种支付方式只需新建一个 @Component 类实现 PayStrategy,零修改 PayContext。策略注册、发现、路由全由 Spring 容器完成。

💭 思考穿插Map<String, PayStrategy> 的 key 到底是「Bean 名」还是 getType() 的返回值?——是 Bean 名。Spring 按每个策略类的 Bean 名做 key 注入 Map,而路由时 strategyMap.get(type) 用的是 getType() 的返回值,所以两者必须对齐。很多人踩的坑是:Bean 名默认是「类名首字母小写」(wechatPayStrategy),而 getType() 返回 wechatget 就取不到。稳妥做法是用 @Component("wechat") 显式把 Bean 名设成和 getType() 一致。

Spring 源码:BeanFactory 的实例化策略

Spring 的 InstantiationStrategy 就是一个教科书式的策略模式——把「Bean 如何被实例化」抽成策略接口,让不同实例化算法(纯反射、CGLIB 方法注入)可插拔。

策略接口(三个 instantiate 重载分别对应「无参构造 / 指定构造器 / 工厂方法」三种实例化入口):

// org.springframework.beans.factory.support.InstantiationStrategy
public interface InstantiationStrategy {

	Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner)
			throws BeansException;

	Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
			Constructor<?> ctor, Object... args) throws BeansException;

	Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
			@Nullable Object factoryBean, Method factoryMethod, @Nullable Object... args)
			throws BeansException;

	default Class<?> getActualBeanClass(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
		return bd.getBeanClass();
	}

}

简单实现:直接用反射调构造器,不支持方法注入:

// org.springframework.beans.factory.support.SimpleInstantiationStrategy
public class SimpleInstantiationStrategy implements InstantiationStrategy {

	@Override
	public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
		// Don't override the class with CGLIB if no overrides.
		if (!bd.hasMethodOverrides()) {
			Constructor<?> constructorToUse;
			synchronized (bd.constructorArgumentLock) {
				constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
				if (constructorToUse == null) {
					Class<?> clazz = bd.getBeanClass();
					if (clazz.isInterface()) {
						throw new BeanInstantiationException(clazz, "Specified class is an interface");
					}
					try {
						constructorToUse = clazz.getDeclaredConstructor();
						bd.resolvedConstructorOrFactoryMethod = constructorToUse;
					}
					catch (Throwable ex) {
						throw new BeanInstantiationException(clazz, "No default constructor found", ex);
					}
				}
			}
			return BeanUtils.instantiateClass(constructorToUse);
		}
		else {
			// Must generate CGLIB subclass.
			return instantiateWithMethodInjection(bd, beanName, owner);
		}
	}

	protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
		throw new UnsupportedOperationException("Method Injection not supported in SimpleInstantiationStrategy");
	}

}

默认实现(继承 + 增强):当 Bean 声明了 lookup-method / replaced-method 这类方法注入时,用 CGLIB 动态生成子类来重写目标方法:

// org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy
public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy {

	@Override
	protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
		return instantiateWithMethodInjection(bd, beanName, owner, null);
	}

	@Override
	protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
			@Nullable Constructor<?> ctor, Object... args) {

		return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
	}

}

按类型持有策略(策略的注入点)AbstractAutowireCapableBeanFactory 用接口类型持有策略引用,构造时默认 new CglibSubclassingInstantiationStrategy(),并提供 setter 允许运行时替换:

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
		implements AutowireCapableBeanFactory {

	/** Strategy for creating bean instances. */
	private InstantiationStrategy instantiationStrategy;

	public AbstractAutowireCapableBeanFactory() {
		super();
		ignoreDependencyInterface(BeanNameAware.class);
		ignoreDependencyInterface(BeanFactoryAware.class);
		ignoreDependencyInterface(BeanClassLoaderAware.class);
		this.instantiationStrategy = new CglibSubclassingInstantiationStrategy();
	}

	public void setInstantiationStrategy(InstantiationStrategy instantiationStrategy) {
		this.instantiationStrategy = instantiationStrategy;
	}

	public InstantiationStrategy getInstantiationStrategy() {
		return this.instantiationStrategy;
	}
}

这里和前面的 Map<String, PayStrategy> 是同一个模式的两个变体:Map 注入是「收集多个实现按名称路由」,BeanFactory 是「持有一个接口引用按需替换」。调用方 AbstractAutowireCapableBeanFactory 只面向 InstantiationStrategy 接口编程,从不关心底层是反射还是 CGLIB,新增/替换实例化算法只需换一个实现类,完全符合开闭原则。CglibSubclassingInstantiationStrategy 通过继承 SimpleInstantiationStrategy 复用无方法注入时的反射逻辑,只重写 instantiateWithMethodInjection 钩子——这正是策略模式叠加模板方法模式的组合拳。

💭 思考穿插:Map 注入和 BeanFactory 的 InstantiationStrategy 为什么是同一个模式?——Map 注入是「收集多个实现、按名称路由」,面向”多选一”;InstantiationStrategy 是「持有一个接口引用、按需替换」,面向”运行时换一个”。两者都是面向接口编程、新增实现不碰调用方,区别只在「策略集合的形态」:一个是一张 Map,一个是单个引用。

策略 vs 工厂 vs 状态

策略工厂状态
目的算法可替换创建对象并隐藏实现状态改变行为
选择时机运行时由调用方决定创建时由工厂决定状态变化自动切换
典型特征调用方传 key 选择方法直接返回产品内部状态机驱动

策略和状态在结构上几乎一样(都组合了一个接口引用),区别在使用方式:策略由外部选择哪个算法(调用方知悉),状态由内部状态机自动切换(调用方不知情)。

💭 思考穿插:策略和状态结构几乎一样,怎么一眼区分?——看「谁决定换」。策略由调用方在运行时显式选(调用方知道有哪些算法,自己传 key);状态由对象内部的状态机自动切换(调用方根本不知道有状态在变)。所以判断标准是:换算法这个动作,是「外部指令」还是「内部自转」。

总结

策略模式在 Spring 中的最佳实践:@Autowired Map<String, Strategy> 一行注入,全部 if-else 归零。 新增策略只需加 @Component 类,Spring 容器自动注册到 Map,完全符合开闭原则。

章末提问

Q1:策略模式解决什么问题?为什么不用 if-else?

结论先行:解决「可替换算法」的扩展问题,新增策略零修改主流程,符合开闭原则。 因为:if-else 每加一个分支都要改主流程、重新测试,分支多了圈复杂度爆炸;策略把算法抽成独立类,新增只加一个 @Component 类,主流程一行不改。

Q2:Spring 的 Map 注入怎么把策略收集起来?key 是什么?

结论先行:当注入目标类型是 Map<String, T> 时,容器收集所有 T 的 Bean、以 Bean 名为 key 注入;路由时用 getType() 的返回值去 get。 因为:Spring 对 Map 类型注入有特殊处理,key 默认是 Bean 名(类名首字母小写)。所以要保证「Bean 名」和 getType() 对齐,否则 get 取不到——常见做法是 @Component("wechat") 显式指定 Bean 名。

Q3:策略模式和工厂模式有什么区别?

结论先行:策略关注「算法可替换」,工厂关注「对象创建并隐藏实现」;选择时机也不同。 因为:策略在运行时由调用方决定用哪个算法(传 key 路由);工厂在创建时由工厂决定返回哪个产品(方法返回产品)。工厂关注「造什么」,策略关注「用哪个算法做」。

Q4:策略模式和状态模式有什么区别?

结论先行:结构几乎一样,区别在「谁决定切换」——策略由调用方显式选,状态由内部状态机自动切换。 因为:策略的调用方知道自己有哪些算法、主动传 key;状态的调用方不知道状态在变,是对象内部根据状态自动换实现。判断标准是「换算法是外部指令还是内部自转」。

Q5:策略模式有什么代价?什么场景不该用?

结论先行:代价是策略类数量增多;只有两三个分支且基本不扩展时不该用。 因为:每个算法一个类会增加类的数量和维护成本;如果分支少且稳定,if-else 反而简单直接,过度设计不值得。判断标准是「扩展频率」——分支会持续增长才值得上策略。


Share this post on:

Previous Post
装饰器模式——Java IO为什么是一层套一层的洋葱?
Next Post
模板方法模式——JdbcTemplate如何把连接管理从业务代码中抽离