当策略模式遇上Spring

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

一般策略模式

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

if ("01".equals(type)) {
   firstStrategy.execute();
} else if ("02".equals(type)) {
   secondStrategy.execute();
}
firstStrategy和secondStrategy为策略类,其execute方法封装了处理逻辑。

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

Spring+策略模式

先来看看spring策略模式的套路是如何实现的吧~
类结构如下
spring策略模式类结构.png

定义策略的接口

  • 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,整理流程如下
spring策略模式.png
下面结合代码具体来看看

在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~