【JAVA】 基于Function的单参注册表

编程 / 2022-09-26

需求背景

在公司遇到这样一个需求,现在有一个用户接收消息列表
image-1664177999468
在查看消息详情的时候,他有12种消息类型,各种消息类型所要输出的字段有差集有交集,而不同类型需要的查询方法也各不相同,且查询详情的入口由于列表循环的原因前端只能调用一个接口,通过字段去区分不同的类型。如果不想写高达12层的if-else语句,就必须要寻找其他的设计模式去完成解耦和提高可用性。

技术选择

我选择使用基于Function的单参注册表。

JAVA中有一种接口叫函数式编程

他有supplier无参提供者,Funciton单参激活者,BiFunction双参…等等
他们的作用就是能够把方法保存成为一个Function对象,但是不调用它,当业务逻辑调用到了这个对象里的aware方法的时候,才会去执行保存在对象中的方法。

/**
 * Represents a function that accepts one argument and produces a result.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #apply(Object)}.
 *
 * @param <T> the type of the input to the function
 * @param <R> the type of the result of the function
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }

这样的性质可以让我们把方法保存起来到适合的地方在把他激活。至于为什么这么做就需要有解耦的思维了。

数据结构

我们知道了这样的性质那么我们要是Function对象放入一个哈希表里,那么我们就可以做到通过一个key值得到对应的方法了,然后再去激活它。

/**
 * 注册表基类
 * @param <T> 参数
 * @param <R> 结果
 */
public interface IRegistry<T,R>{

    /**
     * 获取数据
     * @param key 关键字
     * @return 方法
     */
    Function<T,R> get(String key);

    /**
     * 添加数据
     * @param key 关键字
     * @param value 值
     * @throws Exception
     */
    void add(String key, Function<T,R> value);

    /**
     * 移除数据
     * @param key 关键字
     */
    void remove(String key);

    /**
     * 清除所有数据
     */
    void clearup();
}
/**
 * 保存不同消息需要查询的不同方法
 */
public class MessageRegistryImpl implements IRegistry<String,MsgDetailOutVO>{

    private final static Hashtable<String, Function<String,MsgDetailOutVO>> queryWayMap = new Hashtable<>();

    //禁止替换
    public void onAdding(String key,Function<String,MsgDetailOutVO> queryWay){
        if(queryWayMap.containsKey(key)) {
            throw new RuntimeException("存在重复key,请检查配置");
        }
    }

    @Override
    public Function<String,MsgDetailOutVO> get(String type){
        return queryWayMap.get(type);
    }

    @Override
    public void add(String type,Function<String,MsgDetailOutVO> queryWay){
        onAdding(type,queryWay);
        queryWayMap.put(type,queryWay);
    }

    @Override
    public void remove(String type){
        queryWayMap.remove(type);
    }

    @Override
    public void clearup(){
        queryWayMap.clear();
    }
}

于是我们可以编写出这样一个数据结构,注意这里要使用Hashtable,因为hashmap不是线程安全的。

这样做的好处,很简单if-else是一个定死的逻辑分支,但这种方法只要考虑好线程安全的问题,是可以在业务逻辑中动态的新增或者删除某一个逻辑分支的。

但是我的需求需要在程序开始之后立刻将我们需要的方法注册进入这个哈希表中,才能一个基础的方法表使用,于是需要一个初始化的地方。

初始化配置类

于是我写了一个配置类

/**
 * 配置注册表初始化设置接口
 * @param <T> 参数
 * @param <R> 结果
 */
public interface IRegistryConfig<T,R> {
    /**
     * 投递Funtion方法进入注册表
     * @param registry 注册表
     */
    void postToRegistry(IRegistry<T,R> registry);
}
public class JhAmMessageRegistryConfig implements IRegistryConfig<String,JhAmMsgDetailOutVO>{

    private IJhAmMessageEntityService jhAmMessageEntityService;

    private IJhAmAuthMessageEntityService jhAmAuthMessageEntityService;

    private IJhAmApplicationInfoEntityService jhAmApplicationInfoEntityService;

    private IJhAmAuthLogEntityService jhAmAuthLogEntityService;

    //注入bean
    private void getMessageService(){
        this.jhAmMessageEntityService = (IJhAmMessageEntityService) BeanLocator.findBeanByName("jhAmMessageEntityServiceImpl");
    }

    private void getAuthMessageService(){
        this.jhAmAuthMessageEntityService = (IJhAmAuthMessageEntityService) BeanLocator.findBeanByName("jhAmAuthMessageEntityServiceImpl");
    }

    private void getApplicationInfoService(){
        this.jhAmApplicationInfoEntityService = (IJhAmApplicationInfoEntityService) BeanLocator.findBeanByName("jhAmApplicationInfoEntityServiceImpl");
    }

    private void getJhAmAuthLogEntityService(){
        this.jhAmAuthLogEntityService = (IJhAmAuthLogEntityService) BeanLocator.findBeanByName("jhAmAuthLogEntityServiceImpl");
    }
        /**
     *
     *
     * @param registry 注册表
     */
    public void postToRegistry(IRegistry<String,JhAmMsgDetailOutVO> registry){
        registry.add(JhAmCommonConstant.MESSAGE_TYPE_PRODUCT_OPEN, this::queryProductMes);
        registry.add(JhAmCommonConstant.MESSAGE_TYPE_PRODUCT_DELEY,this::queryProductMesUnlessMechineCode);
        registry.add(JhAmCommonConstant.MESSAGE_TYPE_PRODUCT_OVERDUE,this::queryProductMesUnlessMechineCode);
        registry.add(JhAmCommonConstant.MESSAGE_TYPE_PRODUCT_RENEW,this::queryProductMes);
        registry.add(JhAmCommonConstant.MESSAGE_TYPE_PRODUCT_DUE,this::queryProductMesUnlessMechineCode);
        registry.add(JhAmCommonConstant.MESSAGE_TYPE_PRODUCT_DOMAINCHANGE,this::queryDomainChangeMes);
        registry.add(JhAmCommonConstant.MESSAGE_TYPE_PRODUCT_MACHINECODECHANGE,this::queryMachineCodeChangeMes);
        registry.add(JhAmCommonConstant.MESSAGE_TYPE_PRODUCT_REJECTOPEN,this::queryNoArgsMes);
        registry.add(JhAmCommonConstant.MESSAGE_TYPE_FEEDBACK_QUESTION,this::queryNoArgsMes);
        registry.add(JhAmCommonConstant.MESSAGE_TYPE_FEEDBACK_SUGGEST,this::queryNoArgsMes);
        registry.add(JhAmCommonConstant.MESSAGE_TYPE_PRODUCT_MOVE,this::queryProductMesRemove);
        registry.add(JhAmCommonConstant.MESSAGE_TYPE_PRODUCT_EXPAND,this::queryProductMesExpand);
    }
}

中间的查询方法就不展示了,我采用的是Springboot框架,Funtion里放的由于是一个方法,是无法进行注入依赖的,需要手动从bean工厂里面找出对应的依赖。

@Component
public class BeanLocator implements BeanFactoryAware {
    private static BeanFactory beanFactory; //BEAN工厂

    @Override
    public void setBeanFactory(BeanFactory f) throws BeansException {
        this.beanFactory = f;
    }

    /**
     * 根据bean的名字找bean的对象
     *
     * @param name
     * @return
     */
    public static Object findBeanByName(String name) {
        Object obj = null;
        try{
            obj = beanFactory.getBean(name);
        }catch (Exception e){

        }
        return obj;
    }
}

一般来说,默认的bean的实例的名字是service的实现类,第一个字母小写。

管理者

然后我要把配置类和注册表绑定在一起,于是我写了一个管理者

/**
 * 注册表管理器
 * @param <T>
 * @param <R>
 */
public interface IRegistryManage<T,R> extends IRegistry<T,R>,IRegistryConfig<T,R>{
}

这是一个空接口,为了进行多继承的统一接口


public class DefaultMessageManage implements IRegistryManage<String, JhAmMsgDetailOutVO>{

    protected IRegistry<String,JhAmMsgDetailOutVO> registry;

    public DefaultMessageManage(){
        this.registry = new JhAmMessageRegistryImpl();
    }

    public DefaultMessageManage(IRegistry<String,JhAmMsgDetailOutVO> registry){
        this.registry = registry;
    }

    @Override
    public Function<String,JhAmMsgDetailOutVO> get(String key){
        return this.registry.get(key);
    }

    @Override
    public void add(String key,Function<String,JhAmMsgDetailOutVO> value){
        this.registry.add(key,value);
    }

    @Override
    public void remove(String key){
        this.registry.remove(key);
    }

    @Override
    public void clearup(){
        this.registry.clearup();
    }

    @Override
    public void postToRegistry(IRegistry<String, JhAmMsgDetailOutVO> registry) {}

    public IRegistry<String, JhAmMsgDetailOutVO> getRegistry() {
        return registry;
    }

    public void setRegistry(IRegistry<String, JhAmMsgDetailOutVO> registry) {
        this.registry = registry;
    }
}

首先我们要先做一个注册表的管理者,将注册的功能委托给注册表实例,这样做的好处是,我即可以设置默认要委托的注册表实例JhAmMessageRegistryImpl,也可通过后续逻辑修改成另外一个统一符合IRegistry<String,JhAmMsgDetailOutVO>的实例,这是一种高可用的体现,或者其他程序员同样接入IRegistryManage<String, JhAmMsgDetailOutVO>自己在实现一个自己的默认manage都是可以的。
然后,再将配置类也绑定进来

/**
 * 在spring载入bean的时候让配置初始化注册表
 * 注:配置和注册表被修改后应使用postToRegistry方法重写绑定
 */
@Slf4j
@Component
public class JhAmMessageManage extends DefaultMessageManage{

    protected IRegistryConfig<String,JhAmMsgDetailOutVO> registryConfig;

    public JhAmMessageManage() {
        super();
        this.registryConfig = new JhAmMessageRegistryConfig();
        this.registryConfig.postToRegistry(this.registry);
    }

    public JhAmMessageManage(IRegistry<String,JhAmMsgDetailOutVO> registry,IRegistryConfig<String,JhAmMsgDetailOutVO> registryConfig) {
        super(registry);
        this.registryConfig = registryConfig;
        this.registryConfig.postToRegistry(this.registry);
    }

    @Override
    public void postToRegistry(IRegistry<String,JhAmMsgDetailOutVO> registry){
        this.registryConfig.postToRegistry(registry);
    }

    public void postToRegistry(){
        this.registryConfig.postToRegistry(this.registry);
    }

    public IRegistryConfig<String, JhAmMsgDetailOutVO> getRegistryConfig() {
        return registryConfig;
    }

    public void setRegistryConfig(IRegistryConfig<String, JhAmMsgDetailOutVO> registryConfig) {
        this.registryConfig = registryConfig;
    }
}

通过继承的方式获取到注册表,然后在设置默认配置类进行对注册表的初始化,而初始化的方法委托给了JhAmMessageRegistryConfig由它来实现。同样的我们可以通过这个类修改配置类的实现类,也能通过继承来达到多态的目的。

而作为最底层的IRegistryManage<T,R>实现类,我们就需要将他注入到Bean容器中了,这样我们就可以通过依赖注入找到IRegistryManage.

    @Resource(name = "jhAmMessageManage")
    private IRegistryManage<String,JhAmMsgDetailOutVO> manage;

值得注意的是,如果你的接口有多个实现类,最后要指定你要注入的依赖实例

使用的话就直接get对应的key

        Function<String,JhAmMsgDetailOutVO> jhAmMsgDetailOutVOSupplier = manage.get(type);
        return jhAmMsgDetailOutVOSupplier.apply(id);

粤ICP备2022112743号 粤公网安备 44010502002407号