使用Spring 原生注解来快速实现 策略模式 + 工厂模式


prtyaa
prtyaa 2024-01-02 20:42:52 63740
分类专栏: 资讯

前言

这阵子在做项目组重构的工作,工作中的一部分就是就目前代码库中与企业交互的逻辑抽离出来,单独做一个微服务,实现企业交互逻辑的关注点分离。

在这里面我很自然而然的就用到了策略模式 + 工厂模式的方式,包装内部实现细节,向外提供统一的调用方式,有效的减少if/else的业务代码,使得代码更容易维护,扩展。

之前看过一些文章,是使用自定义注解+自动BeanProcessor的方式来实现,个人感觉有点麻烦。因为Spring原生就提供类似的特性。

本篇旨在介绍在实际的业务场景,如何借助Spring IoC 依赖注入的特性,使用Spring 原生注解 来快速实现 策略模式 + 工厂模式。希望能够对你有启发。

业务场景

从原项目抽离出来的企业服务,承担的是与外部企业交互的职责。不同企业,虽然会产生的交互行为是相同的,但是交互行为内部的实现逻辑各有不同,比如发送报文接口,不同企业可能报文格式会不同。

针对这种不同企业交互细节不同的场景,将与企业的交互行为抽象出来EntStrategy接口,根据服务消费者传入的企业号选择对应的实现类(策略类),逻辑简化之后如下图。

快速实现

现在让我们用快速用一个DEMO实现上述场景。

我们的期望目标是,根据不同的企业编号,我们能够快速找到对应的策略实现类去执行发送报文的操作。

Step 1 实现策略类

假设我们现在对外提供的服务Api是这样的,

/**  
 * @param entNum 企业编号  
 */  
public void send(String entNum) {  
    // 根据不同的企业编号,我们能够快速找到对应的策略实现类去执行发送报文的操作  
}  

现在我们先定义个EntStrategy接口

/**  
 * @author Richard_yyf  
 */  
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();  
    }  
}  

Step 2 借助Spring 强大的依赖注入

下面的设计是消除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);  
    }  
}  

这一步的关键就是, Spring 会自动将 EntStrategy 接口的实现类注入到这个Map中。前提是你这个实现类得是交给Spring 容器管理的。

这个Map的key值就是你的 bean id,你可以用@Component("value")的方式设置,像我上面直接用默认的方式的话,就是首字母小写。value值则为对应的策略实现类。

到这一步,实际上我们的期望功能大部分已经实现了,先让用一个简单的启动类试一下。

/**  
 * @author Richard_yyf  
 */  
@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标准的报文给对应企业  
发送默认标准的报文给对应企业  

Step 3 别名转换

大家眼睛如果稍微利索的点的话,会发现我上面启动类里面的企业编号entNum填的实际上是bean id的值。那在实际业务中肯定是不会这样的,怎么可能把一个企业编号定义的这么奇怪呢。

所以这里还需要一步操作,将传入的企业编号,转义成对应的策略类的bean id。

实际上这一步的逻辑和你的实际业务是有很强的相关性的,因为在我业务里面的entNum在实际上就是一种标识,程序怎么识别解析这个标识,找到对应的策略实现类,应该是根据你的业务需求定制的。

我这里把这一步也写出来,主要是想给大家提供一种思路。

因为我的微服务是用SpringBoot做基础框架的,所以我借助SpringBoot 外部化配置的一些特性实现了这种方式。

添加EntAlias类

/**  
 * @author Richard_yyf  
 */  
@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: entAStrategy  
    entB: entBStrategy  
  
....省略  

这里注意哦,要实现对应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  
 */  
@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

上面的代码中我采用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 的依赖注入

这里我想再谈一下上面的第二个步骤,第二个步骤的核心就是通过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变量进行赋值感兴趣的话,我会在下一篇文章中讲解相关的源码和调试技巧。

我们搞技术的,知其然更应知其所以然嘛。

网站声明:如果转载,请联系本站管理员。否则一切后果自行承担。

本文链接:https://www.xckfsq.com/news/show.html?id=34313
赞同 0
评论 0 条
prtyaaL0
粉丝 1 发表 2554 + 关注 私信
上周热门
银河麒麟添加网络打印机时,出现“client-error-not-possible”错误提示  1448
银河麒麟打印带有图像的文档时出错  1365
银河麒麟添加打印机时,出现“server-error-internal-error”  1151
统信桌面专业版【如何查询系统安装时间】  1073
统信操作系统各版本介绍  1070
统信桌面专业版【全盘安装UOS系统】介绍  1028
麒麟系统也能完整体验微信啦!  984
统信【启动盘制作工具】使用介绍  627
统信桌面专业版【一个U盘做多个系统启动盘】的方法  575
信刻全自动档案蓝光光盘检测一体机  484
本周热议
我的信创开放社区兼职赚钱历程 40
今天你签到了吗? 27
信创开放社区邀请他人注册的具体步骤如下 15
如何玩转信创开放社区—从小白进阶到专家 15
方德桌面操作系统 14
我有15积分有什么用? 13
用抖音玩法闯信创开放社区——用平台宣传企业产品服务 13
如何让你先人一步获得悬赏问题信息?(创作者必看) 12
2024中国信创产业发展大会暨中国信息科技创新与应用博览会 9
中央国家机关政府采购中心:应当将CPU、操作系统符合安全可靠测评要求纳入采购需求 8

添加我为好友,拉您入交流群!

请使用微信扫一扫!