最近遇到一个需求场景,开源的工具包,新增了一个高级特性,会依赖json序列化工具,来做一些特殊操作;但是,这个辅助功能并不是必须的,也就是说对于使用这个工具包的业务方而言,正常使用完全不需要json相关的功能;如果我强引用某个json工具,一是对于不适用高级特性的用户而言没有必要;二则是我引入的json工具极有可能与使用者的不一致,会增加使用者的成本
因此我希望这个工具包对外提供时,并不会引入具体的json工具依赖;也就是说maven依赖中的<scope>
设置为provided
;具体的json序列化的实现,则取决于调用方自身引入了什么json工具包
那么可以怎么实现上面这个方式呢?
1. 任务说明
上面的简单的说了一下我们需要做的事情,接下来我们重点盘一下,我们到底是要干什么
核心诉求相对清晰
- 不强引入某个json工具
- 若需要使用高级特性,则直接使用当前环境中已集成的json序列化工具;若没有提供,则抛异常,不支持
对于上面这个场景,常年使用Spring的我们估计不会陌生,Spring集成了很多的第三方开源组件,根据具体的依赖来选择最终的实现,比如日志,可以是logback,也可以是log4j;比如redis操作,可以是jedis,也可以是lettuce
那么Spring是怎么实现的呢?
2.具体实现
在Spring中有个注解名为ConditionalOnClass
,表示当某个类存在时,才会干某些事情(如初始化bean对象)
它是怎么是实现的呢?(感兴趣的小伙伴可以搜索一下,或者重点关注下 SpringBootCondition
的实现)
这里且抛开Spring的实现姿势,我们采用传统的实现方式,直接判断是否有加载对应的类,来判断有没有引入相应的工具包
如需要判断是否引入了gson包,则判断ClassLoader是否有加载com.google.gson.Gson
类
1 2 3 4 5 6 7
| public static boolean exist(String name) { try { return JsonUtil.class.getClassLoader().loadClass(name) != null; } catch (Exception e) { return false; } }
|
上面这种实现方式就可以达到我们的效果了;接下来我们参考下Spring的ClassUtils实现,做一个简单的封装,以判断是否存在某个类
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
|
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包来选择具体的实现
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 34 35 36 37 38 39
| 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给出默认的实现类
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| 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的方式改一下,优先使用用户自定义实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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"); } } } } }
|
对于使用者而言,首先是实现接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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
内容如下
1
| com.github.hui.quick.plugin.test.DemoJsonImpl
|
然后完工~
4. 小结
主要介绍一个小的知识点,如何根据应用已有的jar包来选择具体的实现类的方式;本文介绍的方案是通过ClassLoader来尝试加载对应的类,若能正常加载,则认为有;否则认为没有;这种实现方式虽然非常简单,但是请注意,它是有缺陷的,至于缺陷是啥…
除此之外,也可以考虑通过字节码的方式来判断是否有某个类,或者获取某个接口的实现;文中最后抛出了一个问题,如何获取接口的所有实现类
常见的方式有下面三类(具体介绍了SPI的实现姿势,其他的两种感兴趣的可以搜索一下)
- SPI定义方式
- 扫描包路径
- 字节码方式(如Spring,如Tomcat的
@HandlesTypes
)
一灰灰的联系方式
尽信书则不如无书,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激