Spring 最佳路由方式

不同的业务,需要走不同的实现。

除了 if/else,我们还有其他的方式吗?

接口的定义

  • 验证接口
interface IValid() {
    void valid(ValidReq req);
}

不同的实现

  • 验证金额
@Service
class ValidNum implements IValid() {
    @Override
    void valid(ValidReq req) {
        //num....
    }
}
  • 验证金额
@Service
class ValidAmount implements IValid() {
    @Override
    void valid(ValidReq req) {
        //amount....
    }
}

需求

根据不同的业务,执行不同的验证方式(多个组合)。

比如业务 simple,只需要验证数量。

业务 complex,需要验证数量和金额。

  • 拓展性

后续可能会继续新增校验方式,会有不同的业务。

基本版本

最基本的版本,直接来一个 if/else 判断即可。

简单明了。

@Service
public class BaseValidService implments ValidService {

    @Autowired
    private ValidAmount validAmount;

    @Autowired
    private ValidNum validNum;

    public void doValid(final String bizType, final ValidReq req) {
        if("simple".equals(bizType)) {
            validNum.valid(req);
        } else if("complex".equals(bizType)) {
            validNum.valid(req);
            validAmount.valid(req);
        }
    }
}

思考

  • 优点

if/else 的方式,非常的简单粗暴。也是非常容易想到的。

但是缺点也比较明显。

  • 缺点

if/else 的拓展性比较差。

比如提出下面的需求:

(1)新增了很多校验方式,要求不同的业务验证还是不同

(2)业务有很多种

(3)要求如果一个验证遇到内部异常,try-catch 掉,继续执行其他验证。尽可能的验证规则。

(4)不同验证的执行有一定的顺序优先级

责任链模式

单独针对(3),可以使用责任链模式。

先不考虑业务的不同验证方式,主要是演示下 @PostConstruct 的使用。

@Service
public class ValidChainService implments ValidService {

    private List<IValid> validList;

    @Autowired
    private ValidAmount validAmount;

    @Autowired
    private ValidNum validNum;

    @PostConstruct
    public void init() {
        validList.add(validNum);
        validList.add(validAmount);
    }

    public void doValid(final String bizType, final ValidReq req) {
        for(IValid valid : validList) {
            try {
                valid.valid(req);
            } catch(Exception ex) {
                // 继续一下个验证
            }
        }
    }
}

当然这里不够灵活,因为不同的业务还没有版本区分需要哪些实现。

责任链模式+spring

针对上面提到的几个问题,我们经过思考。

针对(1),容易想到利用 spring 容器管理接口的实现。动态发现实现,而不是手动设置处理。

针对(2),不同的业务在变化,不同的业务需要的业务验证也不同,可以利用注解维护这种灵活的关系。

针对(3),比较容易想到责任链模式。统一处理方法的执行,而不是每一个方法 try-catch。

针对(4),可以利用注解属性,指定不同实现的优先级别。类似于 Spring 的各种 Filter。

编码

  • 注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Valid {

    /**
     * 需要执行当前验证的业务类型
     * @return 对应的业务类型数组
     */
    String[] bizTypes();

    /**
     * 执行的优先级
     * 1. 根据 order 对所有需要执行的先后顺序进行排序。从小到大。
     * 2. 从1开始
     * 注意:获取的时候利用了 TreeMap 排序,所以必须保证 order 的唯一性,否则会出现覆盖。
     * @return 整数
     */
    int order();

}
  • 使用注解

在我们定义的方法实现类中使用上面的注解

表示 ValidNum 在 ValidAmount 前面执行。

业务 simple,只需要验证数量。业务 complex,需要验证数量和金额。

@Service
@Valid(bizType={"complex", "simple"}, order=1)
class ValidNum implements IValid {
    //XXX
}
@Service
@Valid(bizType={"complex"}, order=2)
class ValidAmount implements IValid {
    //XXX
}
  • 不同实现的类发现

这个方法时简化后的实现,去掉了很多校验和对应的日志等信息。

public class ValidServiceContainer {
    // 可以自动注入 IValid 的所有子类
    @Autowired
    private List<IValid> validList;

    public List<IValid> getValidList(String bizType) {
        List<IValid> resultList = new ArrayList<>();
        //paramCheck
        for(IValid valid : validList) {
             final Class serviceClass = valid.getClass();
             final Valid valid = (Valid) serviceClass.getAnnotation(Valid.class);
             if(null == valid) {
                 continue;
             }

             String[] bizTypes = valid.bizTypes();
             if(ArrayUtils.contains(bizTypes, bizType)) {
                 int order = valid.order();
                 // 可以根据 order+valid 构建新的对象,然后排序。
                 // 此处不再演示
                 resultList.add(valid);
             }
         }
         return resultList;
    }
}
  • 责任链执行

这里针对原来的责任链模式,调整下对应的 validList 即可。

@Service
public class ValidChainService implments ValidService {

    @Autowired
    private ValidServiceContainer validServiceContainer;

    public void doValid(final String bizType, final ValidReq req) {
        final List<IValid> validList = validServiceContainer.getValidList(bizType);
        for(IValid valid : validList) {
            try {
                valid.valid(req);
            } catch(Exception ex) {
                // 继续一下个验证
            }
        }
    }
}

性能的思考

到这里我们已经基本实现了需求,但是等等。

这个方法性能好吗?

不同的业务和验证列表的关系我们都得到了,但是每一笔交易都需要进行这种反射处理一遍吗?

显然是不需要得,我们可以将业务和验证的列表关系预先处理好,后面直接获取即可。

优化注解版本

主要针对 container 进行优化

public class ValidServiceContainer {

    //所有的业务类型
    private static List<String> BIZ_TYPE = Arrays.asList("simple", "complex");

    private static Map<String, List<IValid>> map = new HashMap();

    // 可以自动注入 IValid 的所有子类
    @Autowired
    private List<IValid> validList;

    @PostConstruct
    public void init() {
        for(String bizType : BIZ_TYPE) {
            List<IValid> resultList = new ArrayList<>();
            //paramCheck
            for(IValid valid : validList) {
                final Class serviceClass = valid.getClass();
                final Valid valid = (Valid) serviceClass.getAnnotation(Valid.class);
                if(null == valid) {
                    continue;
                }

                String[] bizTypes = valid.bizTypes();
                if(ArrayUtils.contains(bizTypes, bizType)) {
                    int order = valid.order();
                    // 可以根据 order+valid 构建新的对象,然后排序。
                    // 此处不再演示
                    resultList.add(valid);
                }
            }

            map.put(bizType, resultList);
        }
    }

    public List<IValid> getValidList(String bizType) {
        return map.get(bizType);
    }
}

总结

偷懒最好的方式就是一次把事情做完。

一般都会有更好的方式去处理。

拓展阅读

TreeMap 排序方式