如果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。
源码分析
由于篇幅所限,我们这里只做最简单的源码分析和原理总结
如果想实现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的源码。不知道大家是不是有兴趣。
欢迎留言交流!!