前言
策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在spring框架中,我们可以利用@resource注解和map集合来优雅地实现策略模式。
在spring框架中,当你使用@resource注解注入一个map<string, t>或list时,spring会自动将所有类型为t的bean收集到这个map或者list集合中,其中:
map
- key是bean的名称
- value是bean实例
list
- bean实例
底层机制解析
spring的集合类型自动装配
spring框架对集合类型的依赖注入有特殊处理:
- 当注入list时,会收集所有类型为t的bean
- 当注入map<string, t>时,会收集所有类型为t的bean,并以bean名称作为key
@resource注解的行为
@resource注解默认按名称装配,但当目标是一个map时,spring会特殊处理:
- 如果map的key是string类型,value是某个接口/类
- spring会查找所有实现该接口/继承该类的bean
- 将这些bean以"bean名称->bean实例"的形式放入map
实现原理
spring在依赖注入时的处理流程:
- 发现字段/方法参数是map<string, t>类型
- 在应用上下文中查找所有类型为t的bean
- 创建一个新的map实例
- 遍历找到的所有bean,以bean名称作为key,bean实例作为value放入map
- 将这个map注入到目标字段/参数中
使用
直接使用map<string,t>
我们直接定义一个controller,并且在controller中使用@resource和map<string,t>
@restcontroller
@requestmapping("/test")
public class testcontroller {
@resource
private map<string, object> beanmap = new concurrenthashmap<>();
public void beanmap() {
system.out.println(beanmap.size());
}
}
验证:
可以看到map中存了项目中所有的bean对象

指定map中的bean类型
在实际的开发中,我们希望map中只是存储需要的bean,并且controller中可以根据beanname进行转发到不同的service中,步骤如下:
定义策略接口
public interface paymentstrategy {
void pay();
}
定义实现类
@service("ali")
@slf4j
public class alistrategyservice implements paymentstrategy {
@override
public void pay() {
log.info("使用支付宝支付");
}
}
@service("wx")
@slf4j
public class wxstrategyservice implements paymentstrategy {
@override
public void pay() {
log.info("使用微信支付");
}
}
策略使用
@restcontroller
@requestmapping("/test")
public class testcontroller {
@resource
private map<string, paymentstrategy> beanmap = new concurrenthashmap<>();
public void beanmap() {
paymentstrategy wx = beanmap.get("wx");
wx.pay();
paymentstrategy ali = beanmap.get("ali");
ali.pay();
}
}
验证
可以看到map中,就只有两个bean,并且key就是我们通过@service(value)定义的名称


自定义注解实现
- 自定义一个注解
import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;
@target({elementtype.type})
@retention(retentionpolicy.runtime)
@component
public @interface paymenttype {
string value();
}
- 注解替换:将原有的
@service(value)替换为@paymenttype (value),比如:
@paymenttype("card")
@slf4j
public class cardstrategyservice implements paymentstrategy {
@override
public void pay() {
log.info("使用银行卡支付");
}
}
- 意义 可以更好表示策略模式,让其他开发人员一眼可以看出
当前的service使用了策略模式
结合list进行优化
在上面直接使用map来注入bean的场景下,如果service(“xxname”)的名称和某个枚举或者常量一致的情况,并且会在多处使用,那么枚举或者常量变更后service(“xxname”)没有修改到就容易出现问题(启动不会报错,容易忽略修改);
- 可以结合list的方式来解决这个问题,比如存在常量如下:
public interface paywayconstants {
string ali_pay = "ali";
string wx_pay = "wx";
}
- 为
paymentstrategy接口添加一个公共的方法:getway(),改造后如下:
@service(“ali”)中不再需要写value值
// 接口
public interface paymentstrategy {
string getway();
void pay();
}
// 各个实现类
@service
@slf4j
public class alistrategyservice implements paymentstrategy {
@override
public string getway() {
return paywayconstants.ali_pay;
}
@override
public void pay() {
log.info("使用支付宝支付");
}
}
@service
@slf4j
public class wxstrategyservice implements paymentstrategy {
@override
public string getway() {
return paywayconstants.wx_pay;
}
@override
public void pay() {
log.info("使用微信支付");
}
}
- bean的注入调整,单独使用一个类来加载这些策略模式的bean对象
@component
public class paymentstrategycomponent {
private map<string, paymentstrategy> beanmap = new concurrenthashmap<>();
@resource
private list<paymentstrategy> list;
@postconstruct
public void init() {
for (paymentstrategy service : list) {
beanmap.put(service.getway(), service);
log.info("registered: {}", service.getway());
}
}
public paymentstrategy getpaymentstrategy(string way) {
return beanmap.get(way);
}
}
- 策略使用,使用时就可以使用常量来代替字符串的形式,这样便于后期常量或者枚举值的变更
@restcontroller
@requestmapping("/test")
public class testcontroller {
@resource
private paymentstrategycomponent strategycomponent;
public void beanmap() {
paymentstrategy wx = strategycomponent.getpaymentstrategy(paywayconstants.wx_pay);
wx.pay();
paymentstrategy ali = strategycomponent.getpaymentstrategy(paywayconstants.ali_pay);
ali.pay();
}
}
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论