<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
最近遇到一個需求場景,開源的工具包,新增了一個高階特性,會依賴json序列化工具,來做一些特殊操作;但是,這個輔助功能並不是必須的,也就是說對於使用這個工具包的業務方而言,正常使用完全不需要json相關的功能;如果我強參照某個json工具,一是對於不適用高階特性的使用者而言沒有必要;二則是我引入的json工具極有可能與使用者的不一致,會增加使用者的成本
因此我希望這個工具包對外提供時,並不會引入具體的json工具依賴;也就是說maven依賴中的<scope>
設定為provided
;具體的json序列化的實現,則取決於呼叫方自身引入了什麼json工具
那麼可以怎麼實現上面這個方式呢?
上面的簡單的說了一下我們需要做的事情,接下來我們重點盤一下,我們到底是要幹什麼
核心訴求相對清晰
對於上面這個場景,常年使用Spring的我們估計不會陌生,Spring整合了很多的第三方開源元件,根據具體的依賴來選擇最終的實現,比如紀錄檔,可以是logback,也可以是log4j;比如redis操作,可以是jedis,也可以是lettuce
那麼Spring是怎麼實現的呢?
在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; /** * @author Spring */ 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"); } } } } } /** * json轉實體類,會根據當前已有的json框架來執行反序列化 * * @param str * @param t * @param <T> * @return */ 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,那麼是不是可以由使用者自己來實現介面,然後自動選擇它呢?
現在的問題就是如何找到使用者自定義的介面實現了
對於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的實現姿勢,其他的兩種感興趣的可以搜尋一下)
@HandlesTypes
)以上就是java專案依賴包選擇具體實現類範例介紹的詳細內容,更多關於java專案依賴包實現類的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45