在项目中可能会用到这样的一种需求,比如说支付账单,通过不同的支付渠道有不同的优惠价格。
上图是一个思维导图,一共有四种方式可以实现该需求。
具体的实现步骤:
第一简单完成银行的CRUD。
第二定义支付接口
/**
*
* 功能:定义支付接口<br>
* 作者:张<br>
* 时间:2018年12月7日<br>
* 版本:1.0<br>
*
*/
public interface Strategy {
BigDecimal pay(String channelId);
}
第三、某个银行实现支付接口
public class ICBCBank implements Strategy{
@Resource
private BjPayMethodServiceImpl bjPayMethodServiceImpl;
@Override
public BigDecimal pay(String channelId) {
BigDecimal price = new BigDecimal("5000");
BjPayMethod payMethod = bjPayMethodServiceImpl.getById(channelId);
if (payMethod == null)
return null;
return price.multiply(new BigDecimal(payMethod.getDiscount()));
}
}
第四、定义上下文
public class Context {
// 根据上文(渠道Id),得到下文的某个实现类。
public BigDecimal pay(String channelId) throws Exception{
// 我们需要一个类帮我们创建具体的实现类(通过工厂模式),条件就是channelId
StrategyFactory strategyFactory = StrategyFactory.getInstance();
Strategy strategy = strategyFactory.create(channelId);
return strategy.pay(channelId);
}
}
第五、创建注解并添加到银行的支付接口实现类上通过map对注解进行维护
@Target(ElementType.TYPE)// @Target:小时注解可以写在什么地方, TYPE:Class、FIELD:变量、METHOD:方法
@Retention(RetentionPolicy.RUNTIME)// @Retention注解生命周期 , SOURCE:编译器直接忽略,CLASS:JVM忽略
public @interface Pay {
String channelId();
}
第六、通过工厂创建Strategy
public class StrategyFactory {
private static StrategyFactory strategyFactory = new StrategyFactory();
// 通过map 维护注解(自定义注解)
public static Map<String, String> source_map = new HashMap<>();
static {
// 通过包路径获取其下所有的类
Reflections reflections = new Reflections("com.baojian.zhang.api.service.impl");
// 获取有 @Pay 注解的类
Set<Class<?>> classSet = reflections.getTypesAnnotatedWith(Pay.class);
for (Class<?> c : classSet) {
// 获取类上注解的信息
Pay pay = (Pay)c.getAnnotation(Pay.class);
// 维护source_map。 key:channelId,value:类名
source_map.put(pay.channelId(), c.getCanonicalName());
}
}
private StrategyFactory(){}
// 获取的 StrategyFactory (单例模式;饿汉式)
public static StrategyFactory getInstance() {
return strategyFactory;
}
// 通过map 获得具体的创建某个银行的实现类的方法。
public Strategy create(String channelId) throws Exception{
String className = source_map.get(channelId);
Class<?> name = Class.forName(className);
return (Strategy) name.newInstance();
}
}
最后进行测试
@RestController
@RequestMapping("/api/pay")
@Api(value="/api/pay", description="支付API模块")
public class PayController {
@RequestMapping(value = "/testPay", method = RequestMethod.GET)
@ApiOperation(value = "测试支付", notes = "测试支付", response = BaseVO.class)
public BaseVO<BigDecimal> testPay(
@RequestParam(value="channelId",required=true)@ApiParam(name="channelId", value="支付渠道",required=true)String channelId) {
Context context = new Context();
BigDecimal price = null;
try {
price = context.pay(channelId);
} catch (Exception e) {
e.printStackTrace();
}
return new BaseVO<BigDecimal>(1, "支付成功", price);
}
}
返回结果是空……
提示我一开始写好的银行CRUD实现类通过注解不能获取到。
经查找原因是:当我们在通过工厂创建支付接口的实现类时,已经脱离了spring的管理,所以注入不能生效,获取不到实例。
这时我们可也去 实现 ApplicationContextAware 接口,获取到 applicationContext,这样就能通过applicationContext获取到所有的Bean,
@Component
public class BeanUtils implements ApplicationContextAware{
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public BeanUtils() {
if (applicationContext == null)
return;
try {
// 获取集成该类的类,扫描成员变量
Reflections reflections = new Reflections(this.getClass(), new FieldAnnotationsScanner());
// 获取所有含有 @Resource 注解的成员变量
Set<Field> fields = reflections.getFieldsAnnotatedWith(javax.annotation.Resource.class);
// 循环成员变量
for (Field field : fields) {
// 循环目的就是把不能被spring 管理的注入对象,手动让applicationContext set 进去
String simpleName = field.getType().getSimpleName();
// 因为我们spring 里面管理的 bean 的name 都是首字母小写,所以需要转换
String beanName = toLowerCaseFirstOne(simpleName);
// 通过 beanName 让 applicationContext 去获取beanName 对象
Object bean = applicationContext.getBean(beanName);
if (bean == null)
return;
// 如果变量是 private修饰的,要打破封装
field.setAccessible(true);
field.set(this, bean);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//首字母转小写
public static String toLowerCaseFirstOne(String s){
if(Character.isLowerCase(s.charAt(0)))
return s;
else
return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
}
}
测试成功。
当我们在添加一个支付渠道的时候,只要再次重复第三步并添加相应 channelId 注解即可。