前言
前几天有个需求是根据不同的用户,会走不同的业务,由于业务之间的耦合性比较复杂,如果使用 if-else 语法的话,改动的地方比较多,代码更加丑陋,产生代码负债。于是想到了策略模式来实现这个功能,根据用户来确定具体的策略,并将策略交给 spring 来管理,只要将原来的一部分代码抽离出来,和新的需求构成两个策略来实现,既可以对原来的代码将修改降到最低,也提高了代码的扩展性,更加符合开闭原则。
在此写了一个 demo,可以在以后在遇到就可以套用了。
一、环境准备
为了模拟根据不同用户走不同的业务,使用 nacos 作为配置中心来模拟不同用户确定的不同分支(为了实现动态配置策略,这里卡的时间比较久)。
为了省事,找了一些 demo,但是由于和我的 nacos (1.X和 2.X之间的差距还是比较大的,后期有时间了去看看具体源码) 和springBoot 的版本不兼容问题,也走了很多弯路,如果是匹配的就不用处理这个问题,如果不一样,先解决版本问题,以下是我执行完成的环境信息。
JDK:1.8
springboot、springcloud、nacos 版本见 pom.xml 文件
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring-cloud-alibaba.version>2.2.9.RELEASE</spring-cloud-alibaba.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
bootstrap.yml
spring:
application:
name: myconfig
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yml
nacos 配置如下
二、代码
代码结构
策略模式具体实现
策略模式类图
主要是根据这个 demo 画的,不过策略模式的类图基本结构就是这样的。
策略抽象类
实现 InitializingBean 接口,重写afterPropertiesSet() 将策略bean交给 spring
import org.springframework.beans.factory.InitializingBean;
import javax.annotation.Resource;
/**
* 实现 InitializingBean 接口提供了 bean 被 BeanFactory 设置了所有属性后执行的处理结果
*/
public abstract class APlanStrategy implements InitializingBean {
@Resource
private PlanStrategyContext context;
/**
* 策略名称
*
* @return 策略名称
*/
public abstract String getStrategyName();
/**
* 业务方法 1
*/
public abstract void createPlan();
/**
* 业务方法 2
*/
public abstract String queryPlan();
/**
* 业务方法 3
*/
public abstract String handlerPlan();
/**
* 后置处理方法
* @throws Exception Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
// 将具体的实现类注册到策略上下文中
context.register(this);
// spring 启动时可以查看具体注入 spring 的策略
System.out.println("注册策略" + this.getStrategyName());
}
}
策略A
使用 @Component 交个 spring 管理
import org.springframework.stereotype.Component;
@Component
public class PlanAStrategy extends APlanStrategy {
/**
* 策略名称
*/
private static final String STRATEGY_NAME = "PLAN_A";
@Override
public String getStrategyName() {
return STRATEGY_NAME;
}
@Override
public void createPlan() {
System.out.println("create plan " + STRATEGY_NAME);
}
@Override
public String queryPlan() {
return "result: " + STRATEGY_NAME;
}
@Override
public String handlerPlan() {
return "handler plan..." + STRATEGY_NAME;
}
}
策略B
使用 @Component 交个 spring 管理
import org.springframework.stereotype.Component;
@Component
public class PlanBStrategy extends APlanStrategy {
/**
* 策略名称
*/
private static final String STRATEGY_NAME = "PLAN_B";
@Override
public String getStrategyName() {
return STRATEGY_NAME;
}
@Override
public void createPlan() {
System.out.println("create plan " + STRATEGY_NAME);
}
@Override
public String queryPlan() {
return "result: " + STRATEGY_NAME;
}
@Override
public String handlerPlan() {
return "handler plan..." + STRATEGY_NAME;
}
}
策略上下文
使用 @Component 交个 spring 管理
@RefreshScope 监听 nacos 中配置文件修改后即使更新变化。
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
@RefreshScope
public class PlanStrategyContext {
/**
* 将具体策略存放到 strategyMap
*/
public static Map<String, APlanStrategy> strategyMap = new HashMap<>();
/**
* 将具体策略存放到 strategyMap 中
* 策略抽象类中调用
*
* @param strategy strategy
*/
public void register(APlanStrategy strategy) {
strategyMap.put(strategy.getStrategyName(), strategy);
}
/**
* nacos 中读取需要的具体策略
*/
@Value("${planName}")
private String planName;
/**
* 根据 nacos 配置动态获取 bean
* 可以根据业务来获具体的策略 bean
*
* @return APlanStrategy
*/
public APlanStrategy getOneStrategy() {
return strategyMap.get(planName);
}
}
三、启动项目调试
策略bean注入spring
启动时,会将所有继承抽象类的具体实现策略注入到 spring
执行策略A
执行策略B
四、后记
在策略上下文中获取具体策略的时候,刚开始想不来怎么才能根据具体的配置动态获取bean的,想了好久,其实很简单,context 本身也是一个 Bean,所有的策略 bean 已经在 map 中了,想办法根据配置获取到具体的 bean 即可,即使配置在数据库中,也可以写 sql 调用 DTO 层来获取,哈哈。有时候遇到想不通的问题的时候,所有的逻辑都通了,稍微远离以下问题本身,也许就想通了,就差那么灵光一闪。