當策略模式遇上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策略模式

定義策略的接口

  • 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策略模式
    下面結合代碼具體來看看

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章