策略模式 结合 Spring ApplicationContextAware在支付系统中的应用

6,478 阅读4分钟

如果Spring中的类实现了ApplicationContextAware接口,Spring就会为该类的实例注入ApplicationContext实例,从而拥有的访问Spring容器的能力。

在你的业务系统中,需要根据用户在App端选择的支付方式,选择不同的支付类型(支付宝、微信、银联等等),这个时候,前端传递过来的通常是一个类型标识(字符串、或者数字),当后台获取这个标识时,需要根据该标识生成不同的支付类对象,即采用策略模式来获取不同的“支付策略”。

策略模式

我们先复习一下策略模式,策略模式中有3个角色:

1、“上下文”角色(Context):根据不同的策略标识来获取不同的具体策略;

2、“抽象策略”角色(Strategy):策略或算法的抽象;

3、“具体策略”角色(ConcreteStrategy):策略或算法的抽象的实现;

理论都过于抽象,我们还是来看看支付系统的代码吧!

Strategy - 支付策略接口

public interface PaymentStrategy {
    String prePaymentRequest(String json);
    String notifyUrlHandler();
}

ConcreteStrategy1 - 微信支付

public class WechatPaymentStrategy implements PaymentStrategy {
    @Override
    public String prePaymentRequest(String json) {
        return "WechatPaymentStrategy prePaymentRequest";
    }

    @Override
    public String notifyUrlHandler() {
        return "www.wechat.com";
    }
}

ConcreteStrategy2 - 支付宝支付 

public class AlibabaPaymentStrategy implements PaymentStrategy {
    @Override
    public String prePaymentRequest(String json) {
        return "AlibabaPaymentStrategy prePaymentRequest";
    }
    @Override
    public String notifyUrlHandler() {
        return "www.alibaba.com";
    }
}

Context - PaymentContext

public class PaymentContext{
    public static PaymentStrategy getPaymentByType(String type) {
        switch (type){
            case "1":
                return new WechatPaymentStrategy();
            case "2":
                return new AlibabaPaymentStrategy();
            default:
                return null;
        }
    }
}

Client - 客户端

public class PaymentPropertyTest {
    @Test
    public void paymentTest(){
        PaymentStrategy payment = PaymentContext.getPaymentByType("1");
        System.out.println(payment.prePaymentRequest("xxxx"));
    }
}

这里的策略只是一种实现手法,其实还可以使用配置文件、反射来实现有更好扩展性的程序。但不管如何实现,还是没有Spring的方式实现起来更方便更优雅。我们下面就看看使用Spring的方式来实现吧。

使用Spring实现策略模式

我们将具体的支付策略bean的id配置在payment.properties文件中

payment.type1=wechatPaymentStrategy
payment.type2=alibabaPaymentStrategy

在具体的支付策略类中加入@Component注解

@Component
public class WechatPaymentStrategy implements PaymentStrategy {    
   ...
}

@Component
public class AlibabaPaymentStrategy implements PaymentStrategy {
   ...
}


Context角色PaymentContextApplicationContextAware实现如下

@Component
@PropertySource("classpath:payment.properties")
public class PaymentContextApplicationContextAware implements ApplicationContextAware {
    private static ApplicationContext ctx;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        PaymentContextApplicationContextAware.ctx = applicationContext;
    }
    public static PaymentStrategy getPaymentByType(String type) {
        String beanName = ctx.getEnvironment().getProperty("payment.type" + type);
        return (PaymentStrategy) ctx.getBean(beanName);
    }
}

类上加入了@Component,并使用@PropertySource用来获取properties的内容。

上面的代码是从生产环境中抽出来的简化后的代码,使用了策略模式,即根据用户的选择支付类型,使用不同的支付策略。

后续添加支付类型(微信、支付宝的网页版、App版、H5等的支付细节都是不一样的),只需要添加新的具体支付策略类(开闭原则),修改配置文件payment.properties。


源码分析

由于篇幅所限,我们这里只做最简单的源码分析和原理总结

Spring是如何实现PaymentContextApplicationContextAware中ApplicationContext类型的属性注入的呢?

如果想实现ApplicationContext类型的属性注入,类需要实现ApplicationContextAware接口,并将该类纳入容器管理(类上标注@Component)。

这样在容器启动的阶段,实例化该类(PaymentContextApplicationContextAware)并对bean进行初始化,在初始化过程中调用系统已经准备好的一系列BeanPostProcessor,调用applyBeanPostProcessorsBeforeInitialization方法。这样一系列的BeanPostProcessor中就包含ApplicationContextAwareProcessor。

ApplicationContextAwareProcessor的postProcessBeforeInitialization方法中调用invokeAwareInterfaces方法,在invokeAwareInterfaces方法中验证bean是否为ApplicationContextAware类型,如果是,则调用bean(PaymentUtils 的实例)的setApplicationContext方法将applicationContext注入(见下面代码)。

private void invokeAwareInterfaces(Object 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);
      }
   }
}

大家可以在PaymentUtils.setApplicationContext方法上打上断点,debug程序并查看调用栈:


大家对照上面的文字描述查看调用栈,或者自己debug调试。

由于篇幅所限,源码分析的部分比较简单,因为如果把整个流程分析清楚,不是一两篇文章就可以讲清楚的。所以笔者想写个Spring源码分析的专栏,结合各种实例,来系统的分析一下Spring的源码。不知道大家是不是有兴趣。

欢迎留言交流!!