ObjectProvider的使用教程

简介:

在spring的传统开发中,我们拿bean对象,通常通过beanFactory接口里面的getBean方法获得,但是在spring4.3版本开始,为了进一步规范bean的管理机制,提供了ObjectProvider操作接口。个人感觉主要用法可以用来获得一个类型的多个对象,替代@Autowired注入一个接口,并能够获取Bean实例数组,或者转为集合类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {

/**
* 获取对象
*/
T getObject(Object... args) throws BeansException;

/**
* 它实际存在时才检索bean
*/
@Nullable
T getIfAvailable() throws BeansException;

/**
* Return an instance (possibly shared or independent) of the object
* managed by this factory.
* @return an instance of the bean, or {@code null} if not available or
* not unique (i.e. multiple candidates found with none marked as primary)
* @throws BeansException in case of creation errors
* @see #getObject()
* 唯一的时候获取
*/
@Nullable
T getIfUnique() throws BeansException;


default Stream<T> orderedStream() {
throw new UnsupportedOperationException("Ordered element access not supported");
}
}

从策略模式展开

策略模式(Strategy Pattern)定义了一组策略,分别在不同类中封装起来,每种策略都可以根据当前场景相互替换,从而使策略的变化可以独立于操作者。比如在做制单付款操作时,对付款单有一些校验,比如校验金额和账号余额,账号是否是公司黑名单的。


何时使用策略模式

阿里开发规约-编程规约-控制语句-第六条 :超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现。相信大家都见过这种代码:

1
2
3
4
5
6
7
8
9
10

if (conditionA) {
逻辑1
} else if (conditionB) {
逻辑2
} else if (conditionC) {
逻辑3
} else {
逻辑4
}

这种代码虽然写起来简单,但是很明显违反了面向对象的 2 个基本原则:

  • 单一职责原则(一个类应该只有一个发生变化的原因):因为之后修改任何一个逻辑,当前类都会被修改
  • 开闭原则(对扩展开放,对修改关闭):如果此时需要添加(删除)某个逻辑,那么不可避免的要修改原来的代码

因为违反了以上两个原则,尤其是当 if-else 块中的代码量比较大时,后续代码的扩展和维护就会逐渐变得非常困难且容易出错,使用卫语句也同样避免不了以上两个问题。因此根据我的经验,得出一个我个人认为比较好的实践:

  • if-else 不超过 2 层,块中代码 1~5 行,直接写到块中,否则封装为方法
  • if-else 超过 2 层,但块中的代码不超过 3 行,尽量使用卫语句
  • if-else 超过 2 层,且块中代码超过 3 行,尽量使用策略模式

实战

需求背景,对于页面的付款单,在后台可以开启各种类型的校验,如账号余额校验,黑名单校验。

1 定义策略接口

此接口一般做两件事 1 获取类型,用于后期定位具体的处理类 2 具体的处理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @name: VerifyStragety
* @author: chenghao
* @date: 2023/7/22
*/
public interface VerifyStrategy {

/**
* 获取校验类型
*/
String getVerifyType();

/**
* 校验
*/
void verify(PaymentApplicationEntity paymentApplicationEntity);
}

2 相关实现类

黑名单校验,对付款账号进行校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @name: BlackVerifyStrategy
* @author: chenghao
* @date: 2023/7/22
*
* 检查付款账号释放是黑名单配置的付款账户
* 如果是黑名单账户,则提示付款账户不可用
*/
@Component
public class BlackVerifyHandler implements VerifyStrategy{

@Override
public String getVerifyType() {
return VerifyTypeEnum.BLACK_TYPE.getType();
}

@Override
public void verify(PaymentApplicationEntity paymentApplicationEntity) {
// mock 获取付款账户是否是黑名单账户
List<String> blackPayerAccountNoList = Arrays.asList("932033", "334422");
if(blackPayerAccountNoList.contains(paymentApplicationEntity.getPayerAccountNo())){
throw new RuntimeException("付款账户不可用");
}
}
}

账号余额校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @name: AmountVerifyHandler
* @author: chenghao
* @date: 2023/7/22
*
* 获取付款账户余额 和当前制单金额进行比较,如果余额小于制单金额,则提示余额不足
*/
@Component
public class AmountVerifyHandler implements VerifyStrategy{
@Override
public String getVerifyType() {
return VerifyTypeEnum.AMOUNT_TYPE.getType();
}

@Override
public void verify(PaymentApplicationEntity paymentApplicationEntity) {
// mock 获取账户余额 得到金额 500
BigDecimal balanceAmount = new BigDecimal("500");
if(balanceAmount.compareTo(paymentApplicationEntity.getAmount()) < 0){
throw new RuntimeException("余额不足");
}
}
}

3 相关的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @name: VerifyEntity
* @author: chenghao
* @date: 2023/7/22
*
* 验证Dto 对于不同类型 对付款单做不同校验
*/
public class VerifyDto {

/**
* 验证类型 @see VerifyTypeEnum
*/
private String verifyType;

/**
* 付款单
*/
private PaymentApplicationEntity paymentApplicationEntity;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @name: VerifyTypeEnum
* @author: chenghao
* @date: 2023/7/22
*
* 付款单校验类型
*/
@Getter
public enum VerifyTypeEnum {

BLACK_TYPE("black", "黑名单校验"),

AMOUNT_TYPE("amount", "金额校验");
/**
* 校验类型
*/
private final String type;

private final String desc;

VerifyTypeEnum(String type, String desc) {
this.type = type;
this.desc = desc;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* @name: PaymentApplicationEntity
* @author: chenghao
* @date: 2023/7/22
*
* 付款单实体
*/
@Data
public class PaymentApplicationEntity {
/**
* 付款单号
*/
private String paymentApplicationNo;
/**
* 付款单金额
*/
private BigDecimal amount;
/**
* 收款人账号
*/
private String payeeAccountNo;

/**
* 付款人账号
*/
private String payerAccountNo;

/**
* 校验类型
*/
private String verifyType;

}

工厂类生产不同校验器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* @name: VerifyFactory
* @author: chenghao
* @date: 2023/7/22
*/
@Component
public class VerifyHandlerFactory implements InitializingBean, ApplicationContextAware {

public static Map<String, VerifyStrategy> VERIFY_HANDLER_MAP = new HashMap<>();
private ApplicationContext applicationContext;

@Override
public void afterPropertiesSet() throws Exception {
applicationContext.getBeansOfType(VerifyStrategy.class).values()
.forEach(verifyStrategy -> VERIFY_HANDLER_MAP.put(verifyStrategy.getVerifyType(), verifyStrategy));
}

public VerifyStrategy getVerifyHandler(String verifyType){
VerifyStrategy verifyStrategy = VERIFY_HANDLER_MAP.get(verifyType);
if(Objects.isNull(verifyStrategy)){
throw new RuntimeException("未找到对应的校验类型");
}
return verifyStrategy;
}

@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

付款单service类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @name: IPaymentApplicationService
* @author: chenghao
* @date: 2023/7/22
* 付款单服务
*/
public interface IPaymentApplicationService {

/**
* 提交付款单
*
* @param paymentApplicationEntity paymentApplicationEntity
*/
void submitPaymentApplication(PaymentApplicationEntity paymentApplicationEntity);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @name: PaymentApplicationServiceImpl
* @author: chenghao
* @date: 2023/7/22
*/
@Service
public class PaymentApplicationServiceImpl implements IPaymentApplicationService {

@Resource
private VerifyHandlerFactory verifyHandlerFactory;

@Override
public void submitPaymentApplication(PaymentApplicationEntity paymentApplicationEntity) {
// 1.校验付款单
verifyHandlerFactory.getVerifyHandler(paymentApplicationEntity.getVerifyType())
.verify(paymentApplicationEntity);
// 2.保存提交付款单
}
}

controller和http请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @name: PaymentApplicationController
* @author: chenghao
* @date: 2023/7/22
*/
@RestController
@RequestMapping("/paymentApplication")
public class PaymentApplicationController {
@Resource
private IPaymentApplicationService paymentApplicationService;

@PostMapping("/submit")
public String submitPaymentApplication(@RequestBody PaymentApplicationEntity paymentApplicationEntity) {
paymentApplicationService.submitPaymentApplication(paymentApplicationEntity);
return "success";
}
}
1
2
3
4
5
6
7
8
9
POST http://localhost:8081/paymentApplication/submit
Content-Type: application/json

{
"amount": "1000",
"payerAccountNo": "123456789",
"payeeAccountNo": "987654321",
"verifyType":"amount"
}

之前是利用VerifyHandlerFactory类中的applicationContext.getBeansOfType(VerifyStrategy.class),获取所有的VerifyStrategy实现。

现在利用ObjectProvider修改VerifyHandlerFactory类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @name: VerifyHandlerFactory2
* @author: chenghao
* @date: 2023/7/22
*/
@Component
public class VerifyHandlerFactory2 {

public static Map<String, VerifyStrategy> VERIFY_HANDLER_MAP = new HashMap<>();

VerifyHandlerFactory2(ObjectProvider<VerifyStrategy> objectProvider){
objectProvider.orderedStream().forEach(verifyStrategy -> VERIFY_HANDLER_MAP.put(verifyStrategy.getVerifyType(), verifyStrategy));
}

public VerifyStrategy getVerifyHandler(String verifyType){
VerifyStrategy verifyStrategy = VERIFY_HANDLER_MAP.get(verifyType);
if(verifyStrategy == null){
throw new RuntimeException("未找到对应的校验类型");
}
return verifyStrategy;
}
}