策略模式是一种比较简单的模式。一般来说,我们可以根据不同的任务类型,来选择不同的执行策略。

一般策略模式

对于Java语言来说,一般来说可以简化如下:

if ("01".equals(type)) {
   firstStrategy.execute();
} else if ("02".equals(type)) {
   secondStrategy.execute();
}

firstStrategy和secondStrategy为策略类,其execute方法封装了处理逻辑。

在策略模式的处理逻辑中,可以根据type来选择不同的策略类。但是一旦策略类比较多,调用处的if…else…就会特别多,也特别不优雅。
对此,如果使用Spring,利用IOC,可以比较优雅的解决这个问题。

Spring+策略模式

先来看看spring策略模式的套路是如何实现的吧~

类结构如下

java策略模式存在的弊端 策略模式 spring_设计模式

定义策略的接口

  • StrategyService
public interface StrategyService {
    /**
     * 定位策略的key
     *
     * @return key
     */
    String fetchkey();

    /**
     * 具体策略执行的key
     */
    void execute();
}

定义两个策略实现类

  • FirstStrategyImpl
@Component
public class FirstStrategyImpl implements StrategyService {
    @Override
    public String fetchkey() {
        return "01";
    }

    @Override
    public void execute() {
        System.out.println("FirstStrategy executed!");
    }
}
  • SecondStrategyImpl
@Component
public class SecondStrategyImpl implements StrategyService {
    @Override
    public String fetchkey() {
        return "02";
    }

    @Override
    public void execute() {
        System.out.println("SecondStrategy executed!");
    }
}

最后再来一个策略处理类

  • StrategyHandler
/**
 * 策略统筹类
 *
 * @author :hewie
 * @date :Created in 2020/3/21 17:35
 */
@Component
public class StrategyHandler implements InitializingBean, ApplicationContextAware {
    private Map<String, StrategyService> strategyServiceMap = new ConcurrentHashMap<>(8);

    private ApplicationContext applicationContext;

    /**
     * 将StrategyService的类都按照定义好的规则(fetchKey),放入strategyServiceMap中
     */
    @Override
    public void afterPropertiesSet() {
        Map<String, StrategyService> matchBeans = applicationContext.getBeansOfType(StrategyService.class);
        matchBeans.forEach((key, value) -> strategyServiceMap.put(value.fetchkey(), value));
    }

    /**
     * 注入applicationContext
     *
     * @param applicationContext ac
     * @throws BeansException e
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 通过key获取对应的策略实现
     *
     * @param key key
     * @return strategyService
     */
    public StrategyService getStrategy(String key) {
        return strategyServiceMap.get(key);
    }
}

好了,这样就完成了。
测试如下:

import cn.hewie.mapi.DemoMapiWebApplication;
import cn.hewie.mapi.util.strategy.StrategyHandler;
import cn.hewie.mapi.util.strategy.StrategyService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @author :hewie
 * @date :Created in 2020/3/21 17:48
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DemoMapiWebApplication.class})
public class StrategyTest {

    @Autowired
    private StrategyHandler strategyhandler;

    @Test
    public void test() {
        String type = "02";
        StrategyService strategy = strategyhandler.getStrategy(type);
        strategy.execute();
    }
}

结果,策略二调用成功!

SecondStrategy executed!

以上就是spring策略模式的一般套路了。
可以发现,使用spring版的策略模式,扩展起来也非常容易:只需在impl目录下添加策略类,定义好对应的策略key,就可以了

原理

以上只是现象,是皮毛,现在让我们抽丝剥茧,看看spring到底帮我们做了什么,为什么我们可以这么玩?
关键在于 StrategyHandler 这个类!它实现了InitializingBean和ApplicationContextAware两个接口。那么这两个接口有什么用呢?

  • 实现了ApplicationContextAware接口的类,可以注入applicationContext实例

具体来说,实现了ApplicationContextAware接口的类,会重写方法

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException

可以通过此方法,让当前类拿到applicationContext实例。
当然,在Spring中,你也可以通过注解来获取,结果都一样~

@Autowired
private ApplicationContext applicationContext;
  • 实现了InitializingBean的类,会在实例化Bean的时候,调用其afterPropertiesSet方法。

So,在实例化此Bean的时候,我们调用了afterPropertiesSet方法,方法里通过applicationContext对象,拿到所有StrategyService类型的类。然后将每个策略类fetchKey结果作为key,策略类本身作为value,存储到变量strategyServiceMap中了。然后当要使用的时候,直接调用getStrategy方法,根据key来获取策略类。就是这么简单~

撸一下源码

那么spring源码是如何实现的呢?实例化bean和注入applicationContext是什么时候完成的呢?下面具体看一下
ps:spring源码版本(5.0.x)

  • 容器启动阶段,进行了ApplicationContextAwareProcessor的注册

ApplicationContextAwareProcessor你可能比较陌生,但是它和applicationContext的注入息息相关。applicationContext就是靠ApplicationContextAwareProcessor注入我们的StrategyHandler的。这里我们先不看Bean实例化的过程,先看看这个ApplicationContextAwareProcessor是如何注入的吧。

在spring启动的refresh()方法中,对BeanFactory进行各种功能填充(prepareBeanFactory())时,代码中硬编码添加了一个ApplicationContextAwareProcessor类

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			// 对BeanFactory进行各种功能填充
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// 以下省略...
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		// Tell the internal bean factory to use the context's class loader etc.
		beanFactory.setBeanClassLoader(getClassLoader());
		beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
		beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

		// Configure the bean factory with context callbacks.
		// 硬编码注册了ApplicationContextAwareProcessor
		beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
		beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
		beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
		beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
		beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
		beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
		beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

		// BeanFactory interface not registered as resolvable type in a plain factory.
		// MessageSource registered (and found for autowiring) as a bean.
		beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
		beanFactory.registerResolvableDependency(ResourceLoader.class, this);
		beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
		beanFactory.registerResolvableDependency(ApplicationContext.class, this);

		// 以下代码省略...

另外,注意ApplicationContextAwareProcessor实现了BeanPostProcessor接口

  • Bean实例化时,会进行Bean的初始化,完成StrategyHandler的初始化
    最外层方法在org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean,整理流程如下

    下面结合代码具体来看看

在doCreateBean方法中,属性填充完成后,调用了initializeBean方法

// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

追踪进入到initializeBean方法,可以看到如下代码

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
		if (System.getSecurityManager() != null) {
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				invokeAwareMethods(beanName, bean);
				return null;
			}, getAccessControlContext());
		}
		else {
			// $-- 对特殊的bean处理:Aware、BeanClassLoaderAware、BeanFactoryAware
			invokeAwareMethods(beanName, bean);
		}

		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
			// $-- 应用后处理器applyBeanPostProcessorsBeforeInitialization
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}

		try {
			// $-- 调用用户定义的init-method方法
			invokeInitMethods(beanName, wrappedBean, mbd);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					(mbd != null ? mbd.getResourceDescription() : null),
					beanName, "Invocation of init method failed", ex);
		}
		if (mbd == null || !mbd.isSynthetic()) {
			// $-- 应用后处理器applyBeanPostProcessorsAfterInitialization
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}

		return wrappedBean;
	}

在应用后处理器applyBeanPostProcessorsBeforeInitialization方法中,会获取到所有的BeanPostProcessor类,然后调用其postProcessBeforeInitialization方法

@Override
	public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			Object current = processor.postProcessBeforeInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}

我们上面说到,spring容器启动的时候,手动注入了一个ApplicationContextAwareProcessor类,它也是BeanPostProcessor的实现类。我们的StrategyHandler调用到此处时,会调用ApplicationContextAwareProcessor的postProcessBeforeInitialization方法,可以看到如下代码

@Override
	@Nullable
	public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
		AccessControlContext acc = null;

		if (System.getSecurityManager() != null &&
				(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
						bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
						bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
			acc = this.applicationContext.getBeanFactory().getAccessControlContext();
		}

		if (acc != null) {
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				invokeAwareInterfaces(bean);
				return null;
			}, acc);
		}
		else {
			invokeAwareInterfaces(bean);
		}

		return bean;
	}

好吧,它又调用了一个invokeAwareInterfaces方法,再去看一看

private void invokeAwareInterfaces(Object bean) {
		// $-- 实现了Aware接口的bean,可以拿到相应的信息
		if (bean instanceof Aware) {
			if (bean instanceof EnvironmentAware) {
				((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
			}
			if (bean instanceof EmbeddedValueResolverAware) {
				((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
			}
			if (bean instanceof ResourceLoaderAware) {
				((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
			}
			if (bean instanceof ApplicationEventPublisherAware) {
				((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
			}
			if (bean instanceof MessageSourceAware) {
				((MessageSourceAware) bean).setMessageSource(this.applicationContext);
			}
			if (bean instanceof ApplicationContextAware) {
				((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
			}
		}
	}

抽丝剥茧,终于看到了调用方法了!因为我们的StrategyHander实现了ApplicationContextAware方法,所以类的实例化进程执行到此处时,会将applicationContext注入

那么afterPropertiesSet()又是什么时候调用的呢?
别急,回头看看上面的initializeBean方法,在应用了后处理器applyBeanPostProcessorsBeforeInitialization后,会调用invokeInitMethods方法,该方法如下:

protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
			throws Throwable {

		boolean isInitializingBean = (bean instanceof InitializingBean);
		if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
			// $-- 如果是InitializingBean,则需要调用afterPropertiesSet方法
			if (logger.isDebugEnabled()) {
				logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
			}
			if (System.getSecurityManager() != null) {
				try {
					AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
						((InitializingBean) bean).afterPropertiesSet();
						return null;
					}, getAccessControlContext());
				}
				catch (PrivilegedActionException pae) {
					throw pae.getException();
				}
			}
			else {
				((InitializingBean) bean).afterPropertiesSet();
			}
		}

		if (mbd != null && bean.getClass() != NullBean.class) {
			String initMethodName = mbd.getInitMethodName();
			if (StringUtils.hasLength(initMethodName) &&
					!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
					!mbd.isExternallyManagedInitMethod(initMethodName)) {
				// $-- 调用自定义初始化方法
				invokeCustomInitMethod(beanName, bean, mbd);
			}
		}
	}

从方法里,我们终于可以看到熟悉的afterPropertiesSet方法了。在注入了applicationContext之后,我们紧接着在调用初始化方法时,就调用了afterPropertiesSet方法。

至此,spring策略模式的这一套就清清楚楚的展现在我们眼前了!
spring+策略模式套路get~