java項目依賴包選擇具體實現(xiàn)類示例介紹
正文
最近遇到一個需求場景,開源的工具包,新增了一個高級特性,會依賴json序列化工具,來做一些特殊操作;但是,這個輔助功能并不是必須的,也就是說對于使用這個工具包的業(yè)務(wù)方而言,正常使用完全不需要json相關(guān)的功能;如果我強引用某個json工具,一是對于不適用高級特性的用戶而言沒有必要;二則是我引入的json工具極有可能與使用者的不一致,會增加使用者的成本
因此我希望這個工具包對外提供時,并不會引入具體的json工具依賴;也就是說maven依賴中的<scope>設(shè)置為provided;具體的json序列化的實現(xiàn),則取決于調(diào)用方自身引入了什么json工具
那么可以怎么實現(xiàn)上面這個方式呢?
1. 任務(wù)說明
上面的簡單的說了一下我們需要做的事情,接下來我們重點盤一下,我們到底是要干什么
核心訴求相對清晰
- 不強引入某個json工具
- 若需要使用高級特性,則直接使用當(dāng)前環(huán)境中已集成的json序列化工具;若沒有提供,則拋異常,不支持
對于上面這個場景,常年使用Spring的我們估計不會陌生,Spring集成了很多的第三方開源組件,根據(jù)具體的依賴來選擇最終的實現(xiàn),比如日志,可以是logback,也可以是log4j;比如redis操作,可以是jedis,也可以是lettuce
那么Spring是怎么實現(xiàn)的呢?
2.具體實現(xiàn)
在Spring中有個注解名為ConditionalOnClass,表示當(dāng)某個類存在時,才會干某些事情(如初始化bean對象)
它是怎么是實現(xiàn)的呢?(感興趣的小伙伴可以搜索一下,或者重點關(guān)注下 SpringBootCondition 的實現(xiàn))
這里且拋開Spring的實現(xiàn)姿勢,我們采用傳統(tǒng)的實現(xiàn)方式,直接判斷是否有加載對應(yīng)的類,來判斷有沒有引入相應(yīng)的工具包
如需要判斷是否引入了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;
}
}
上面這種實現(xiàn)方式就可以達(dá)到我們的效果了;接下來我們參考下Spring的ClassUtils實現(xiàn),做一個簡單的封裝,以判斷是否存在某個類
// 這段代碼來自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;
}
}
工具類存在之后,我們實現(xiàn)一個簡單的json工具類,根據(jù)已有的json包來選擇具體的實現(xiàn)
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轉(zhuǎn)實體類,會根據(jù)當(dāng)前已有的json框架來執(zhí)行反序列化
*
* @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);
}
}
上面的實現(xiàn)中,根據(jù)已有的json序列化工具,選擇具體的實現(xiàn)類,我們定義了一個JsonApi接口,然后分別gson,jackson,fastjson給出默認(rèn)的實現(xiàn)類
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);
}
}
}
最后的問題來了,如果調(diào)用方并沒有使用上面三個序列化工具,而是使用其他的呢,可以支持么?
既然我們定義了一個JsonApi,那么是不是可以由用戶自己來實現(xiàn)接口,然后自動選擇它呢?
現(xiàn)在的問題就是如何找到用戶自定義的接口實現(xiàn)了
3. 擴展機制
對于SPI機制比較熟悉的小伙伴可能非常清楚,可以通過在配置目錄META-INF/services/下新增接口文件,內(nèi)容為實現(xiàn)類的全路徑名稱,然后通過 ServiceLoader.load(JsonApi.class) 的方式來獲取所有實現(xiàn)類
除了SPI的實現(xiàn)方式之外,另外一個策略則是上面提到的Spring的實現(xiàn)原理,借助字節(jié)碼來處理(詳情原理后面專文說明)
當(dāng)然也有更容易想到的策略,掃描包路徑下的class文件,遍歷判斷是否為實現(xiàn)類(額外注意jar包內(nèi)的實現(xiàn)類場景)
接下來以SPI的方式來介紹下擴展實現(xiàn)方式,首先初始化JsonApi的方式改一下,優(yōu)先使用用戶自定義實現(xiàn)
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");
}
}
}
}
}
對于使用者而言,首先是實現(xiàn)接口
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) {
// ...
}
}
接著就是實現(xiàn)定義, resources/META-INF/services/ 目錄下,新建文件名為 com.github.hui.quick.plugin.qrcode.util.json.JsonApi
內(nèi)容如下
com.github.hui.quick.plugin.test.DemoJsonImpl
然后完工~
小結(jié)
主要介紹一個小的知識點,如何根據(jù)應(yīng)用已有的jar包來選擇具體的實現(xiàn)類的方式;本文介紹的方案是通過ClassLoader來嘗試加載對應(yīng)的類,若能正常加載,則認(rèn)為有;否則認(rèn)為沒有;這種實現(xiàn)方式雖然非常簡單,但是請注意,它是有缺陷的,至于缺陷是啥...
除此之外,也可以考慮通過字節(jié)碼的方式來判斷是否有某個類,或者獲取某個接口的實現(xiàn);文中最后拋出了一個問題,如何獲取接口的所有實現(xiàn)類
常見的方式有下面三類(具體介紹了SPI的實現(xiàn)姿勢,其他的兩種感興趣的可以搜索一下)
- SPI定義方式
- 掃描包路徑
- 字節(jié)碼方式(如Spring,如Tomcat的
@HandlesTypes)
以上就是java項目依賴包選擇具體實現(xiàn)類示例介紹的詳細(xì)內(nèi)容,更多關(guān)于java項目依賴包實現(xiàn)類的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
spring @Scheduled注解的使用誤區(qū)及解決
這篇文章主要介紹了spring @Scheduled注解的使用誤區(qū)及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
Flask接口如何返回JSON格式數(shù)據(jù)自動解析
這篇文章主要介紹了Flask接口如何返回JSON格式數(shù)據(jù)自動解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-11-11
Spring Data JPA 整合QueryDSL的使用案例
QueryDSL 是一個用于構(gòu)建類型安全的 SQL 查詢的 Java 庫,它的主要目標(biāo)是簡化在 Java 中構(gòu)建和執(zhí)行 SQL 查詢的過程,同時提供類型安全性和更好的編碼體驗,對Spring Data JPA 整合QueryDSL使用案例感興趣的朋友跟隨小編一起看看吧2023-08-08
java實現(xiàn)基于TCP協(xié)議網(wǎng)絡(luò)socket編程(C/S通信)
這篇文章主要介紹了java實現(xiàn)基于TCP協(xié)議網(wǎng)絡(luò)socket編程(C/S通信),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
Spring Boot 2 整合 QuartJob 實現(xiàn)定時器實時管理功能
Quartz是一個完全由java編寫的開源作業(yè)調(diào)度框架,形式簡易,功能強大。接下來通過本文給大家分享Spring Boot 2 整合 QuartJob 實現(xiàn)定時器實時管理功能,感興趣的朋友一起看看吧2019-11-11
關(guān)于Java的對象序列化流和反序列化流詳細(xì)解讀
這篇文章主要介紹了關(guān)于Java的對象序列化流和反序列化流,對象序列化:就是將對象保存到磁盤中,或者在網(wǎng)絡(luò)中傳輸對象,反之,自己序列還可以從文件中讀取回來,重構(gòu)對象,對它進行反序列化,需要的朋友可以參考下2023-05-05

