多日前在项目中使用了ApplicationContextAware这个接口,用来初始化策略类供选择不同的短信平台发送短信。抱着不能copy进来能用就完事了,必须要去弄懂为什么这么用的心态,还是多方学习实践,总结出该文
ApplicationContextAware接口的意义:
首先,我们都知道ApplicationContext是spring的容器吧,我们那些bean对象啊都是放在这个容器的,那么aware的意思是“知道的,察觉的”,即该接口的字面意思就是通晓spring容器,说人话就是我可以通过实现这个接口去获取spring的ApplicationContext上下文容器。
为什么用ApplicationContextAware接口
因为我这里是springboot项目,省去了那些xml的配置文件,然后我们又知道Spring/SpringMVC中,我们拿到IOC容器有三种方式,就是使用ApplicationContext接口下的三个实现类:ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext。但是在springboot中没有我们要指定的xml的配置文件(我们new这些对象的时候都需要默认到classPath下面查找我们new ClassPathXmlApplicationContext("*****.xml"),如果没有的话springboot中就会收获如下报错
BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext
所以我们就需要这个实现这个接口然后重写他的setApplicationContext方法来获取我们的ApplicationContext
使用场景(举例子)
不多BB,直接贴上我的使用场景,我的场景上下文就是:我的系统里需要实现发送短信的功能,因为发送短信的平台有很多,比如阿里云,聚石塔等等平台都有接入,用户可以自主选择某个平台发送短信,当然就会有用户自己短信平台的账号,然后根据用户每次选择的平台的发送短信,动态的去使用那个平台对接的代码去发送短信,这里用的就是一个策略工厂模式:
请忽略我这个命名Bridge,因为命名风格还是要向前兼容,所以没标准的使用strategy。
SmsBridge接口:表示发送短信的接口
/**
* @author zhihuan
*/
public interface SmsBridge {
/**
* 获取实现类枚举值
* @return
*/
AccountDetailsType getCode();
/**
* 发送短信
* 可使用一个模板批量发送短信
* @param t
* @return
*/
SmsSendResponse sendSms(SmsSendRequest t);
/**
* 批量发送短信
* 可根据不同模板批量发送短信
* @param t
* @return
*/
SmsSendResponse sendBatchSms(SmsSendRequest t);
/**
* 查询发送记录
* @param t
* @return
*/
SmsSendResponse querySendDetails(SmsSendRequest t);
}
上图中使用的枚举类如下:
/**
* 该枚举类值必须跟产品名称保持一致
*
* @author zhihuan
* @Date 2021/5/10
*/
public enum AccountDetailsType {
/**
* 阿里云短信账号类型
*/
ALI_YUN_SMS("阿里云短信"),
/**
* 聚石塔短信账号类型
*/
JU_SHI_TA_SMS("聚石塔短信");
AccountDetailsType(String caption) {
this.caption = caption;
}
@Getter
private final String caption;
}
这里是使用实现类的实现方法返回的枚举类型来判断该实现类属于哪个平台的。比如我这里目前SmsBridge只有两个实现类分别是
ALiYunSmsBridge跟JushiTaSmsBridge分别代表阿里云短信平台跟聚石塔短信平台
所以分别要在这两个实现类中重写getCode方法,返回对应的枚举值如下:
然后我们思考,如何能够动态地根据用户选择的平台进行选择使用哪个实现类去发送短信呢?这里我们就用到了ApplicationContextAware这个接口,我们需要获取到我们的spring容器,然后取出所有的SmsBride的类型的bean,放入到一个缓存的map中,然后根据我们用户选择的短信平台类型来进行动态的使用哪个bridge进行发送短信!
下面代码就是我们就创建了这个发送短信Bridge类的工厂,实现ApplicationContextAware接口,然后通过setApplicationContext获取到的容器把所有的实现类取出来放进map里
/**
* @author zhihuan
* @Date 2021/5/10
*/
@Component
public class SmsBridgeFactory implements ApplicationContextAware {
private static final Map<AccountDetailsType, SmsBridge> SMS_SERVICES = new EnumMap<>(AccountDetailsType.class);
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, SmsBridge> types = applicationContext.getBeansOfType(SmsBridge.class);
types.values().forEach(e -> SMS_SERVICES.putIfAbsent(e.getCode(), e));
}
public SmsBridge getSmsBridge(AccountDetailsType accountDetailsType){
return SMS_SERVICES.get(accountDetailsType);
}
}
然后再在实际的业务代码中获取到我们相应的实现类就可以了(这里也可以看出我们面向接口,面向抽象编程的好处)