这阵子在做项目组重构的工作,工作中的一部分就是就目前代码库中与企业交互的逻辑抽离出来,单独做一个微服务,实现企业交互逻辑的关注点分离。
在这里面我很自然而然的就用到了 策略模式 + 工厂模式 的方式, 包装内部实现细节,向外提供统一的调用方式,有效的减少 if/else
的业务代码 ,使得代码更容易维护,扩展。
之前看过一些文章,是使用自定义注解+自动BeanProcessor的方式来实现,个人感觉有点麻烦。因为Spring原生就提供类似的特性。
从原项目抽离出来的企业服务,承担的是与外部企业交互的职责。不同企业,虽然会产生的交互行为是相同的,但是交互行为内部的实现逻辑各有不同,比如发送报文接口,不同企业可能报文格式会不同。
针对这种不同企业交互细节不同的场景,将与企业的交互行为抽象出来 EntStrategy
接口,根据服务消费者传入的企业号选择对应的实现类(策略类),逻辑简化之后如下图。
现在让我们用快速用一个DEMO实现上述场景。
我们的期望目标是,根据不同的企业编号,我们能够快速找到对应的策略实现类去执行发送报文的操作。
假设我们现在对外提供的服务Api是这样的,
/** * @param entNum 企业编号 */ public void send(String entNum) { // 根据不同的企业编号,我们能够快速找到对应的策略实现类去执行发送报文的操作 }
现在我们先定义个 EntStrategy
接口
/** * @author Richard_yyf * @version 1.0 2019/10/23 */ public interface EntStrategy { String getStuff(); void send(); }
三个策略类
DefaultStrategy
@Component public class DefaultStrategy implements EntStrategy { @Override public String getStuff() { return "其他企业"; } @Override public void send() { System.out.println("发送默认标准的报文给对应企业"); } @Override public String toString() { return getStuff(); } }
EntAStrategy
@Component public class EntAStrategy implements EntStrategy { @Override public String getStuff() { return "企业A"; } @Override public void send() { System.out.println("发送A标准的报文给对应企业"); } @Override public String toString() { return getStuff(); } }
EntBStrategy
@Component public class EntBStrategy implements EntStrategy { @Override public String getStuff() { return "企业B"; } @Override public void send() { System.out.println("发送B标准的报文给对应企业"); } @Override public String toString() { return getStuff(); } }
下面的设计是消除 if/else
的关键代码,这里我定义了一个 EntStrategyHolder
来当做工厂类。
@Component public class EntStrategyHolder { // 关键功能 Spring 会自动将 EntStrategy 接口的类注入到这个Map中 @Autowired private Map<String, EntStrategy> entStrategyMap; public EntStrategy getBy(String entNum) { return entStrategyMap.get(entNum); } }
EntStrategy
接口的实现类注入到这个Map中。前提是你这个实现类得是交给Spring 容器管理的。 这个Map的key值就是你的 bean id
,你可以用 @Component("value")
的方式设置,像我上面直接用默认的方式的话,就是首字母小写。value值则为对应的策略实现类。
到这一步,实际上我们的期望功能大部分已经实现了,先让用一个简单的启动类试一下。
/** * @author Richard_yyf * @version 1.0 2019/10/23 */ @Configuration @ComponentScan("ric.study.demo.ioc.autowire_all_implementation_demo_set") public class Boostrap { public static void main(String[] args) { String entNum = "entBStrategy"; send(entNum); entNum = "defaultStrategy"; send(entNum); } // 用这个方法模拟 企业代理服务 提供的Api public static void send(String entNum) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Boostrap.class); context.getBean(EntStrategyHolder.class).getBy(entNum).send(); } }
发送B标准的报文给对应企业 发送默认标准的报文给对应企业
大家眼睛如果稍微利索的点的话,会发现我上面启动类里面的企业编号 entNum
填的实际上是 bean id
的值。那在实际业务中肯定是不会这样的,怎么可能把一个企业编号定义的这么奇怪呢。
所以这里还需要一步操作,将传入的企业编号,转义成对应的策略类的 bean id
。
实际上这一步的逻辑和你的实际业务是有很强的相关性的,因为在我业务里面的 entNum
在实际上就是一种标识,程序怎么识别解析这个标识,找到对应的策略实现类,应该是根据你的业务需求定制的。
我这里把这一步也写出来,主要是想给大家提供一种思路。
因为我的微服务是用SpringBoot做基础框架的,所以我借助SpringBoot 外部化配置的一些特性实现了这种方式。
添加 EntAlias
类
/** * @author Richard_yyf * @version 1.0 2019/10/15 */ @Component @EnableConfigurationProperties @ConfigurationProperties(prefix = "ent") public class EntAlias { private HashMap<String, String> aliasMap; public static final String DEFAULT_STATEGY_NAME = "defaultStrategy"; public HashMap<String, String> getAliasMap() { return aliasMap; } public void setAliasMap(HashMap<String, String > aliasMap) { this.aliasMap = aliasMap; } String of(String entNum) { return aliasMap.get(entNum); } }
在对应配置文件 application.yml
中配置:
ent: aliasMap: entA: tpcStrategy entB: rbcStrategy ....省略
这里注意哦,要实现对应 getter
和 setter
的,不然属性会注入不进去的。
改写一下 EntStrategyHolder
类
@Component public class EntStrategyHolder { @Autowired private EntAlias entAlias; // 关键功能 Spring 会自动将 EntStrategy 接口的类注入到这个Map中 @Autowired private Map<String, EntStrategy> entStrategyMap; // 找不到对应的策略类,使用默认的 public EntStrategy getBy(String entNum) { String name = entAlias.of(entNum); if (name == null) { return entStrategyMap.get(EntAlias.DEFAULT_STATEGY_NAME); } EntStrategy entStrategy = entStrategyMap.get(name); if (entStrategy == null) { return entStrategyMap.get(EntAlias.DEFAULT_STATEGY_NAME); } return entStrategy; } }
现在我们再启动一下看看:
/** * @author Richard_yyf * @version 1.0 2019/10/23 */ @Configuration @ComponentScan("ric.study.demo.ioc.autowire_all_implementation_demo_set") public class Boostrap { public static void main(String[] args) { String entNum = "entA"; send(entNum); entNum = "entB"; send(entNum); entNum = "entC"; send(entNum); } public static void send(String entNum) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Boostrap.class); context.getBean(EntStrategyHolder.class).getBy(entNum).send(); } }
发送A标准的报文给对应企业 发送B标准的报文给对应企业 发送默认标准的报文给对应企业
上面的代码中我采用SpringBoot的特性,通过yml文件来管理别名转化,是为了让代码看起来更美观。如果是Spring框架的话。我会这样去实现。(只是参考)
/** * @author Richard_yyf * @version 1.0 2019/10/23 */ public class EntAlias { private static Map<String, String> aliasMap; private static final String ENTA_STATEGY_NAME = "entAStrategy"; private static final String ENTB_STATEGY_NAME = "entBStrategy"; public static final String DEFAULT_STATEGY_NAME = "defaultStrategy"; static { // 这个别名容器怎么注册别名、初始化,有很多种方式。 aliasMap = new LinkedHashMap<>(); aliasMap.put("entA", ENTA_STATEGY_NAME); aliasMap.put("entB", ENTB_STATEGY_NAME); } public static String of(String entNum) { return aliasMap.get(entNum); } }
这里我想再谈一下上面的第二个步骤,第二个步骤的核心就是通过Spring IoC依赖注入的特性,实现了策略实现类的注册过程(这一步自己实现会需要很多工作,并且代码不会很好看)。
实际上除了Map这种变量类型,Spring 还能给List 变量进行自动装配。比如下面的代码。
@Component public class EntStrategyHolder { @Autowired private Map<String, EntStrategy> entStrategyMap; @Autowired private List<EntStrategy> entStrategyList; public EntStrategy getBy(String entNum) { return entStrategyMap.get(entNum); } public void print() { System.out.println("===== implementation Map ====="); System.out.println(entStrategyMap); entStrategyMap.forEach((name, impl)-> { System.out.println(name + ":" + impl.getStuff()); }); System.out.println("===== implementation List ====="); System.out.println(entStrategyList); entStrategyList.forEach(impl-> System.out.println(impl.getStuff())); } }
启动类
@Configuration @ComponentScan("ric.study.demo.ioc.autowire_all_implementation_demo_set") public class Boostrap { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Boostrap.class); context.getBean(EntStrategyHolder.class).print(); } }
===== implementation Map ===== {defaultStrategy=其他企业, entAStrategy=企业A, entBStrategy=企业B} defaultStrategy:其他企业 entAStrategy:企业A entBStrategy:企业B ===== implementation List ===== [其他企业, 企业A, 企业B] 其他企业 企业A 企业B
可以看到 entStrategyList
被成功赋值了。
只不过这个特性我暂时没有找到应用场景,所以单独拿出来说一下。
到这里,整个实现过程已经介绍完了。
过程中用了到Spring最常用的一些注解,通过Spring IoC依赖注入的特性,实现了策略实现类的注册过程,最终实现了整个功能。
希望能对你有所启发。
另外,如果你对上述Spring IoC 是如何对 Map
和 List
变量进行赋值感兴趣的话,我会在下一篇文章中讲解相关的源码和调试技巧。
我们搞技术的,知其然更应知其所以然嘛。
本文由博客一文多发平台 OpenWrite 发布!