纯干货-如何用Spring-原生注解-快速实现策略模式工厂模式

4次阅读

共计 6379 个字符,预计需要花费 16 分钟才能阅读完成。

前言

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

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

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

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

业务场景

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

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

快速实现

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

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

Step 1 实现策略类

假设我们现在对外提供的服务 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();
    }
}

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

Step 3 别名转换

大家眼睛如果稍微利索的点的话,会发现我上面启动类里面的企业编号 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: entAStrategy
    entB: entBStrategy

.... 省略

这里注意哦,要实现对应 gettersetter的,不然属性会注入不进去的。

改写一下 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

上面的代码中我采用 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 是如何对 MapList变量进行赋值感兴趣的话,我会在下一篇文章中讲解相关的源码和调试技巧。

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

本文由博客一文多发平台 OpenWrite 发布!

正文完
 0

纯干货-如何用Spring-原生注解-快速实现策略模式工厂模式

4次阅读

共计 6383 个字符,预计需要花费 16 分钟才能阅读完成。

前言

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

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

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

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

业务场景

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

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

快速实现

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

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

Step 1 实现策略类

假设我们现在对外提供的服务 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();
    }
}

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

Step 3 别名转换

大家眼睛如果稍微利索的点的话,会发现我上面启动类里面的企业编号 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: entAStrategy
    entB: entBStrategy~~~~

.... 省略

这里注意哦,要实现对应 gettersetter的,不然属性会注入不进去的。

改写一下 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

上面的代码中我采用 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 是如何对 MapList变量进行赋值感兴趣的话,我会在下一篇文章中讲解相关的源码和调试技巧。

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

本文由博客一文多发平台 OpenWrite 发布!

正文完
 0