文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

java项目依赖包选择具体实现类示例介绍

2022-12-22 15:01

关注

正文

最近遇到一个需求场景,开源的工具包,新增了一个高级特性,会依赖json序列化工具,来做一些特殊操作;但是,这个辅助功能并不是必须的,也就是说对于使用这个工具包的业务方而言,正常使用完全不需要json相关的功能;如果我强引用某个json工具,一是对于不适用高级特性的用户而言没有必要;二则是我引入的json工具极有可能与使用者的不一致,会增加使用者的成本

因此我希望这个工具包对外提供时,并不会引入具体的json工具依赖;也就是说maven依赖中的<scope>设置为provided;具体的json序列化的实现,则取决于调用方自身引入了什么json工具

那么可以怎么实现上面这个方式呢?

1. 任务说明

上面的简单的说了一下我们需要做的事情,接下来我们重点盘一下,我们到底是要干什么

核心诉求相对清晰

对于上面这个场景,常年使用Spring的我们估计不会陌生,Spring集成了很多的第三方开源组件,根据具体的依赖来选择最终的实现,比如日志,可以是logback,也可以是log4j;比如redis操作,可以是jedis,也可以是lettuce

那么Spring是怎么实现的呢?

2.具体实现

在Spring中有个注解名为ConditionalOnClass,表示当某个类存在时,才会干某些事情(如初始化bean对象)

它是怎么是实现的呢?(感兴趣的小伙伴可以搜索一下,或者重点关注下 SpringBootCondition 的实现)

这里且抛开Spring的实现姿势,我们采用传统的实现方式,直接判断是否有加载对应的类,来判断有没有引入相应的工具包

如需要判断是否引入了gson包,则判断ClassLoader是否有加载com.google.gson.Gson

public static boolean exist(String name) {
    try {
        return JsonUtil.class.getClassLoader().loadClass(name) != null;
    } catch (Exception e) {
        return false;
    }
}

上面这种实现方式就可以达到我们的效果了;接下来我们参考下Spring的ClassUtils实现,做一个简单的封装,以判断是否存在某个类

// 这段代码来自Spring
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.Map;

public abstract class ClassUtils {
    private static final Map<String, Class<?>> primitiveTypeNameMap = new HashMap(32);
    private static final Map<String, Class<?>> commonClassCache = new HashMap(64);
    private ClassUtils() {
    }
    public static boolean isPresent(String className) {
        try {
            forName(className, getDefaultClassLoader());
            return true;
        } catch (IllegalAccessError var3) {
            throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" + className + "]: " + var3.getMessage(), var3);
        } catch (Throwable var4) {
            return false;
        }
    }
    public static boolean isPresent(String className, ClassLoader classLoader) {
        try {
            forName(className, classLoader);
            return true;
        } catch (IllegalAccessError var3) {
            throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" + className + "]: " + var3.getMessage(), var3);
        } catch (Throwable var4) {
            return false;
        }
    }
    public static Class<?> forName(String name, ClassLoader classLoader) throws ClassNotFoundException, LinkageError {
        Class<?> clazz = resolvePrimitiveClassName(name);
        if (clazz == null) {
            clazz = (Class) commonClassCache.get(name);
        }
        if (clazz != null) {
            return clazz;
        } else {
            Class elementClass;
            String elementName;
            if (name.endsWith("[]")) {
                elementName = name.substring(0, name.length() - "[]".length());
                elementClass = forName(elementName, classLoader);
                return Array.newInstance(elementClass, 0).getClass();
            } else if (name.startsWith("[L") && name.endsWith(";")) {
                elementName = name.substring("[L".length(), name.length() - 1);
                elementClass = forName(elementName, classLoader);
                return Array.newInstance(elementClass, 0).getClass();
            } else if (name.startsWith("[")) {
                elementName = name.substring("[".length());
                elementClass = forName(elementName, classLoader);
                return Array.newInstance(elementClass, 0).getClass();
            } else {
                ClassLoader clToUse = classLoader;
                if (classLoader == null) {
                    clToUse = getDefaultClassLoader();
                }
                try {
                    return Class.forName(name, false, clToUse);
                } catch (ClassNotFoundException var9) {
                    int lastDotIndex = name.lastIndexOf(46);
                    if (lastDotIndex != -1) {
                        String innerClassName = name.substring(0, lastDotIndex) + '$' + name.substring(lastDotIndex + 1);
                        try {
                            return Class.forName(innerClassName, false, clToUse);
                        } catch (ClassNotFoundException var8) {
                        }
                    }
                    throw var9;
                }
            }
        }
    }
    public static Class<?> resolvePrimitiveClassName(String name) {
        Class<?> result = null;
        if (name != null && name.length() <= 8) {
            result = (Class) primitiveTypeNameMap.get(name);
        }
        return result;
    }
    public static ClassLoader getDefaultClassLoader() {
        ClassLoader cl = null;
        try {
            cl = Thread.currentThread().getContextClassLoader();
        } catch (Throwable var3) {
        }
        if (cl == null) {
            cl = ClassUtils.class.getClassLoader();
            if (cl == null) {
                try {
                    cl = ClassLoader.getSystemClassLoader();
                } catch (Throwable var2) {
                }
            }
        }
        return cl;
    }
}

工具类存在之后,我们实现一个简单的json工具类,根据已有的json包来选择具体的实现

public class JsonUtil {
    private static JsonApi jsonApi;
    private static void initJsonApi() {
        if (jsonApi == null) {
            synchronized (JsonUtil.class) {
                if (jsonApi == null) {
                    if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", JsonUtil.class.getClassLoader())) {
                        jsonApi = new JacksonImpl();
                    } else if (ClassUtils.isPresent("com.google.gson.Gson", JsonUtil.class.getClassLoader())) {
                        jsonApi = new GsonImpl();
                    } else if (ClassUtils.isPresent("com.alibaba.fastjson.JSONObject", JsonUtil.class.getClassLoader())) {
                        jsonApi = new JacksonImpl();
                    } else {
                        throw new UnsupportedOperationException("no json framework to deserialize string! please import jackson|gson|fastjson");
                    }
                }
            }
        }
    }
    
    public static <T> T toObj(String str, Class<T> t) {
        initJsonApi();
        return jsonApi.toObj(str, t);
    }
    public static <T> String toStr(T t) {
        initJsonApi();
        return jsonApi.toStr(t);
    }
}

上面的实现中,根据已有的json序列化工具,选择具体的实现类,我们定义了一个JsonApi接口,然后分别gson,jackson,fastjson给出默认的实现类

public interface JsonApi {
    <T> T toObj(String str, Class<T> clz);
    <T> String toStr(T t);
}
public class FastjsonImpl implements JsonApi {
    public <T> T toObj(String str, Class<T> clz) {
        return JSONObject.parseObject(str, clz);
    }
    public <T> String toStr(T t) {
        return JSONObject.toJSONString(t);
    }
}
public class GsonImpl implements JsonApi {
    private static final Gson gson = new Gson();
    public <T> T toObj(String str, Class<T> t) {
        return gson.fromJson(str, t);
    }
    public <T> String toStr(T t) {
        return gson.toJson(t);
    }
}
public class JacksonImpl implements JsonApi{
    private static final ObjectMapper jsonMapper = new ObjectMapper();
    public <T> T toObj(String str, Class<T> clz) {
        try {
            return jsonMapper.readValue(str, clz);
        } catch (Exception e) {
            throw new UnsupportedOperationException(e);
        }
    }
    public <T> String toStr(T t) {
        try {
            return jsonMapper.writeValueAsString(t);
        } catch (Exception e) {
            throw new UnsupportedOperationException(e);
        }
    }
}

最后的问题来了,如果调用方并没有使用上面三个序列化工具,而是使用其他的呢,可以支持么?

既然我们定义了一个JsonApi,那么是不是可以由用户自己来实现接口,然后自动选择它呢?

现在的问题就是如何找到用户自定义的接口实现了

3. 扩展机制

对于SPI机制比较熟悉的小伙伴可能非常清楚,可以通过在配置目录META-INF/services/下新增接口文件,内容为实现类的全路径名称,然后通过 ServiceLoader.load(JsonApi.class) 的方式来获取所有实现类

除了SPI的实现方式之外,另外一个策略则是上面提到的Spring的实现原理,借助字节码来处理(详情原理后面专文说明)

当然也有更容易想到的策略,扫描包路径下的class文件,遍历判断是否为实现类(额外注意jar包内的实现类场景)

接下来以SPI的方式来介绍下扩展实现方式,首先初始化JsonApi的方式改一下,优先使用用户自定义实现

private static void initJsonApi() {
    if (jsonApi == null) {
        synchronized (JsonUtil.class) {
            if (jsonApi == null) {
                ServiceLoader<JsonApi> loader = ServiceLoader.load(JsonApi.class);
                for (JsonApi value : loader) {
                    jsonApi = value;
                    return;
                }
                if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", JsonUtil.class.getClassLoader())) {
                    jsonApi = new JacksonImpl();
                } else if (ClassUtils.isPresent("com.google.gson.Gson", JsonUtil.class.getClassLoader())) {
                    jsonApi = new GsonImpl();
                } else if (ClassUtils.isPresent("com.alibaba.fastjson.JSONObject", JsonUtil.class.getClassLoader())) {
                    jsonApi = new JacksonImpl();
                } else{
                    throw new UnsupportedOperationException("no json framework to deserialize string! please import jackson|gson|fastjson");
                }
            }
        }
    }
}

对于使用者而言,首先是实现接口

package com.github.hui.quick.plugin.test;
import com.github.hui.quick.plugin.qrcode.util.json.JsonApi;
public class DemoJsonImpl implements JsonApi {
    @Override
    public <T> T toObj(String str, Class<T> clz) {
        // ...
    }
    @Override
    public <T> String toStr(T t) {
        // ...
    }
}

接着就是实现定义, resources/META-INF/services/ 目录下,新建文件名为 com.github.hui.quick.plugin.qrcode.util.json.JsonApi

内容如下

com.github.hui.quick.plugin.test.DemoJsonImpl

然后完工~

小结

主要介绍一个小的知识点,如何根据应用已有的jar包来选择具体的实现类的方式;本文介绍的方案是通过ClassLoader来尝试加载对应的类,若能正常加载,则认为有;否则认为没有;这种实现方式虽然非常简单,但是请注意,它是有缺陷的,至于缺陷是啥...

除此之外,也可以考虑通过字节码的方式来判断是否有某个类,或者获取某个接口的实现;文中最后抛出了一个问题,如何获取接口的所有实现类

常见的方式有下面三类(具体介绍了SPI的实现姿势,其他的两种感兴趣的可以搜索一下)

以上就是java项目依赖包选择具体实现类示例介绍的详细内容,更多关于java项目依赖包实现类的资料请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯