关于使用SPI来减少项目中的if else判断

当我们项目中的业务模式比较多且复杂的时候,比如我现在做的项目,多条业务模式,对应的状态流程还都不一样,用FSM也搞不定。在这样的情况下,不同的业务进来,就会需要做很多的分支判断,很明显,这是不符合我们的开发规范的。

那么在这种情况下我们引入了SPI来解决这样的问题。

SPI全称为(Service Provider Interface),是JDK内置的一种服务提供发现机制,我主要使用的是类似Dubbo中的使用方式,简单画一下调用图:


废话不多说,直接代码走起:

1.首先先实现SPI服务接口加载类

public class ExtensionServiceLoader<T extends PatternComponent> {
    private static final Logger LOGGER = Logger.getLogger(ExtensionServiceLoader.class);

    private static final String SERVICES_DIRECTORY = "/META-INF/services/";

    //存放不同类型的loader
    private static final ConcurrentMap<Class<? extends PatternComponent>, ExtensionServiceLoader<? extends PatternComponent>> loaderMap = new ConcurrentHashMap<>();

    //存放不同实例。<模式KEY,具体实现对象>
    private final ConcurrentMap<String, T> instanceMap = new ConcurrentHashMap<>();

    private Class<T> type;

    static {
        init();
    }

    private static void init() {
        final String methodName = "init";
        Class<PatternComponent> patternClass = PatternComponent.class;
        String packageName = patternClass.getPackage().getName();
        String folder = "/" + packageName.replace(".", "/") + "/" + patternClass.getSimpleName() + ".class";
        URL url = ExtensionServiceLoader.class.getResource(folder);
        File[] files = new File(url.getFile()).getParentFile().listFiles();
        if (files == null || files.length == 0) {
            LOGGER.error(methodName, "初始化模式结构异常");
            return;
        }
        String name;
        Class<?> clazz;
        for (File file : files) {
            name = file.getName();
            if (name.endsWith(".class")) {
                try {
                    clazz = Class.forName(packageName + "." + name.substring(0, name.length() - ".class".length()));
                } catch (ClassNotFoundException e) {
                    LOGGER.error(methodName, "未找到相应的类", e);
                    continue;
                }

                if (patternClass.isAssignableFrom(clazz)) {
                    if (clazz.getAnnotation(SPI.class) != null) {
                        getDefaultServiceInstance(clazz.asSubclass(patternClass));
                        LOGGER.info(methodName, clazz.getSimpleName() + "组装完毕");
                    } else {
                        if (patternClass != clazz) {
                            LOGGER.error(methodName, clazz + "是PatternComponent子类,但没有SPI注解");
                        }
                    }
                }
            }
        }
    }

    /**
     * 获取Service实例
     *
     * @param extensionType 组件类型
     * @param modeKey       模式名称
     * @return 返回模式组件实例,可能返回null
     */
    public static <T extends PatternComponent> T getServiceInstance(Class<T> extensionType, String modeKey) {
        ExtensionServiceLoader<T> loader = getServiceLoader(extensionType);
        if (loader == null) {
            return null;
        }
        return loader.getServiceInstance(modeKey);
    }

    /**
     * 获取默认Service实例
     *
     * @param extensionType 组件类型
     * @return 返回默认模式组件实例,可能返回null
     */
    public static <T extends PatternComponent> T getDefaultServiceInstance(Class<T> extensionType) {
        ExtensionServiceLoader<T> loader = getServiceLoader(extensionType);
        if (loader == null) {
            return null;
        }
        String defaultModeKey = extensionType.getAnnotation(SPI.class).value();
        if (StringUtils.isNotBlank(defaultModeKey)) {
            return loader.getServiceInstance(defaultModeKey);
        }

        LOGGER.error("getDefaultServiceInstance", extensionType.getName() + "无默认值组件");
        return null;
    }

    /**
     * Description: . spi服务加载实现<br/>
     *
     * @param extensionType 模式类型
     * @return null表示异常
     * @throws IllegalArgumentException 参数为null或没有<code>SPI</code>注解
     */
    private static <T extends PatternComponent> ExtensionServiceLoader<T> getServiceLoader(Class<T> extensionType) {
        if (extensionType == null) {
            throw new IllegalArgumentException("extensionType is null!");
        }
        if (!extensionType.isAnnotationPresent(SPI.class)) {
            throw new IllegalArgumentException("extensionType (" + extensionType + ")Invalid services,because: No annotations (@" + SPI.class.getSimpleName() + ")!");
        }
        //从map中获取
        ExtensionServiceLoader<T> serviceLoader = (ExtensionServiceLoader<T>) loaderMap.get(extensionType);
        if (serviceLoader == null) {
            try {
                serviceLoader = new ExtensionServiceLoader<>(extensionType);
            } catch (Exception e) {
                LOGGER.error("getServiceLoader", extensionType + "ExtensionServiceLoader()异常", e);
                return null;
            }
            loaderMap.put(extensionType, serviceLoader);
        }
        return serviceLoader;
    }

    private T getServiceInstance(String name) {
        final String methodName = "getServiceInstance";
        if (name == null || name.trim().length() == 0) {
            LOGGER.error(methodName, "模式名字为空");
            return null;
        }
        //从map中取实例如果取不到 就创建并存放到map中
        return instanceMap.get(name.trim());
    }

    /**
     * 构造方法
     *
     * @param extensionType 类型
     * @throws IOException           读取配置文件异常
     * @throws IllegalStateException 配置文件有效内容为空
     */
    private ExtensionServiceLoader(Class<T> extensionType) throws IOException {
        this.type = extensionType;//该loader对应的类型
        this.loadExtensionClasses(extensionType);
    }

    /**
     * Description: 加载不同的类定义. <br/>
     *
     * @param extensionType 扩展类型
     * @throws IOException           读取配置文件异常
     * @throws IllegalStateException 配置文件有效内容为空
     */
    private void loadExtensionClasses(Class<T> extensionType) throws IOException {
        final String methodName = "loadExtensionClasses";
        //文件名 也就是接口名
        String fileName = SERVICES_DIRECTORY + extensionType.getName();
        Properties properties = new Properties();

        InputStreamReader inputStreamReader = null;
        InputStream inputStream = null;
        try {
            inputStream = ExtensionServiceLoader.class.getResourceAsStream(fileName);
            if (inputStream == null) {
                inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(fileName);
            }
            inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
            properties.load(inputStreamReader);
        } catch (IOException e) {
            LOGGER.error(methodName, "加载文件异常", e);
            throw e;
        } finally {
            IOUtils.close(inputStreamReader, inputStream);
        }

        if (properties.isEmpty()) {
            throw new IllegalStateException(fileName + "配置文件有效内容为空");
        }

        Class<?> clazz;
        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
            try {
                clazz = Class.forName(entry.getValue().toString().trim());
            } catch (ClassNotFoundException e) {
                LOGGER.error(methodName, "未找到相应的类" + entry.getValue().toString(), e);
                continue;
            }
            if (type.isAssignableFrom(clazz)) {
                Component component = clazz.getAnnotation(Component.class);
                if (component == null) {
                    LOGGER.error(methodName, clazz + "不是Spring容器中的实例");
                    continue;
                }
                Class<? extends T> clazzT = clazz.asSubclass(type);
                instanceMap.put(entry.getKey().toString(), SpringContextHolder.getApplicationContext().getBean(clazzT));
                LOGGER.info(methodName, entry.getKey() + ":" + clazzT.getName() + "组装完毕");
            } else {
                LOGGER.error(methodName, entry.getValue() + "不是指定的类型" + extensionType);
            }
        }
    }

}

然后在对应的接口上加@SPI注解

@SPI
public interface S*** extends PatternComponent {

    void s***(String a1, String a2, String a3, String a4);
}

在调用接口的时候直接使用我们实现的SPI服务接口加载类ExtensionServiceLoader获取接口实例。

 S*** s = ExtensionServiceLoader.getServiceInstance(S***.class, modeCode);
                if (s != null) {
                    s.signContract(a1, a2, a3, a4);
                } else {
                    LOGGER.error("模式实现类为null,modeCode:" + modeCode);
                }

当然,如果有新的业务模式加入,我们只需要对这个接口进行实现,并且将新的实现类配置到配置文件中,然后通过新的key就可以展开新的业务开发,而且不会影响到原有业务。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值