Java使用MethodHandle來替代反射,提高性能問題
一、認(rèn)識(shí)MethodHandle
1、簡(jiǎn)介
Java從最初發(fā)布時(shí)就支持反射,通過反射可以在運(yùn)行時(shí)獲取類型信息,但其有個(gè)缺點(diǎn)就是執(zhí)行速度較慢。
于是從Java 7開始提供了另一套API MethodHandle 。其與反射的作用類似,可以在運(yùn)行時(shí)訪問類型信息,但是其執(zhí)行效率比反射更高(性能幾乎接近方法調(diào)用)
MethodHandle:是java.lang.invoke.MethodHandle的一個(gè)實(shí)例,它是對(duì)Java中某個(gè)方法(包括實(shí)例方法、靜態(tài)方法、構(gòu)造函數(shù)等)的直接可執(zhí)行引用。
- 與傳統(tǒng)的Java反射相比,MethodHandle更加輕量級(jí)和高效,因?yàn)樗@過了許多反射的額外開銷,如訪問控制檢查等。
- MethodHandle是對(duì)方法的直接引用,可以直接通過MethodHandle對(duì)象調(diào)用目標(biāo)方法,無需像反射那樣先獲取Method對(duì)象。
- MethodHandle具有類型檢查的特性,在編譯時(shí)會(huì)檢查MethodHandle的類型與目標(biāo)方法的類型是否匹配。
2、使用方式
1、創(chuàng)建MethodHandle對(duì)象:
- 使用
MethodHandles.Lookup類的lookup()方法獲取一個(gè)MethodHandles.Lookup對(duì)象。 - 使用
MethodHandles.Lookup對(duì)象的findStatic(), findVirtual(), findSpecial(), findConstructor()等方法來查找并獲取目標(biāo)方法的MethodHandle對(duì)象。
2、綁定MethodHandle到目標(biāo)方法(如果需要):
- 如果
MethodHandle指向的是實(shí)例方法,可以使用MethodHandle對(duì)象的bindTo()方法將其綁定到目標(biāo)實(shí)例上。
3、調(diào)用目標(biāo)方法:
- 使用
MethodHandle對(duì)象的invoke()、invokeExact()、invokeWithArguments()等方法來調(diào)用目標(biāo)方法。
3、與反射的區(qū)別
- 性能:MethodHandle通常比反射更快,因?yàn)樗@過了許多反射的額外開銷。
- 類型安全:MethodHandle在編譯時(shí)會(huì)進(jìn)行類型檢查,而反射在運(yùn)行時(shí)進(jìn)行類型檢查,可能導(dǎo)致ClassCastException等異常。
- 用法:反射需要先獲取Method對(duì)象,而MethodHandle直接對(duì)方法進(jìn)行引用。
Method Handles比反射更難用,因?yàn)闆]有列舉類中成員,獲取屬性訪問標(biāo)志之類的機(jī)制。
二、示例
1、基本使用
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
class Calculator {
public String add(int a, int b) {
return String.valueOf(a + b);
}
}
public class MethodHandleExample {
public static void main(String[] args) throws Throwable {
try {
// 獲取Calculator類的方法句柄
// 注意:由于我們是在Class內(nèi)部查找,所以可以使用MethodHandles.lookup()
// 在實(shí)際應(yīng)用中,如果Class是其他包中的類,你可能需要不同的Lookup實(shí)例
MethodHandles.Lookup lookup = MethodHandles.lookup();
// 獲取方法,第一個(gè)參數(shù)為返回值,剩余的參數(shù)為方法參數(shù)
MethodType methodType = MethodType.methodType(String.class, int.class, int.class);
// 通過方法名 查找某個(gè)類的某個(gè)方法
MethodHandle addHandle = lookup.findVirtual(Calculator.class, "add", methodType);
// 創(chuàng)建Calculator的實(shí)例
Calculator calculator = new Calculator();
// 直接調(diào)用方法,第一個(gè)參數(shù)為對(duì)象實(shí)例,剩余的參數(shù)為方法
String result = (String) addHandle.invoke(calculator, 1, 2);
System.out.println(result);
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}2、(重要)提升性能的關(guān)鍵
使用MethodHandle來提升性能,一般是定義static final在啟動(dòng)時(shí)就加載好了,這樣不用在運(yùn)行時(shí)重新查找。
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
class Calculator {
public int add(int a, int b) {
return a + b;
}
}
public class MethodHandleExample {
// 要static final類型的才能提升效率
private static final MethodHandle ADD_HANDLE;
static {
// static final 修飾的 MethodHandle 可以在類加載時(shí)就完成初始化,避免了每次調(diào)用時(shí)的查找開銷。
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType methodType = MethodType.methodType(int.class, int.class, int.class);
ADD_HANDLE = lookup.findVirtual(Calculator.class, "add", methodType);
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Throwable {
Calculator calculator = new Calculator();
int result = (int) ADD_HANDLE.invoke(calculator, 1, 2);
System.out.println(result);
}
}3、實(shí)際使用
//無參數(shù)構(gòu)造器
MethodType con1Mt = MethodType.methodType(void.class);
MethodHandle con1Mh = lookup.findConstructor(HandleTarget.class, con1Mt);
Object target1 = con1Mh.invoke();
//有參數(shù)構(gòu)造器
MethodType con2Mt = MethodType.methodType(void.class, String.class);
MethodHandle con2Mh = lookup.findConstructor(HandleTarget.class, con2Mt);
Object target2 = con2Mh.invoke("ErGouWang");
//調(diào)用非private實(shí)例方法
MethodType getterMt = MethodType.methodType(String.class);
MethodHandle getterMh = lookup.findVirtual(HandleTarget.class, "getName", getterMt);
String name = (String) getterMh.invoke(target2);
System.out.println(name);
//訪問private方法
Method learnMethod = HandleTarget.class.getDeclaredMethod("learnPrograming", String.class);
learnMethod.setAccessible(true);
MethodHandle learnProMh = lookup.unreflect(learnMethod);
learnProMh.invoke(target1, "Java");
//訪問非private屬性
MethodHandle nameMh= lookup.findGetter(HandleTarget.class,"name", String.class);
System.out.println((String) nameMh.invoke(con1Mh.invoke()));
//訪問private的屬性,需要借助反射
Field nameField = HandleTarget.class.getDeclaredField("name");
nameField.setAccessible(true);
MethodHandle nameFromRefMh = lookup.unreflectGetter(nameField);
System.out.println((String) nameFromRefMh.invoke(target1));
//訪問private的屬性,需要借助反射
Field nameField = HandleTarget.class.getDeclaredField("name");
nameField.setAccessible(true);
MethodHandle nameFromRefMh = lookup.unreflectGetter(nameField);
System.out.println((String) nameFromRefMh.invoke(target1));
三、源碼分析
1、創(chuàng)建Lookup
// 以這種方式得到的lookup很強(qiáng)大,凡是調(diào)用類支持的字節(jié)碼操作,它都支持 MethodHandles.Lookup lookup = MethodHandles.lookup(); // 以此種方式創(chuàng)建的lookup能力是受限的,其只能訪問類中public的成員。 MethodHandles.Lookup publicLookup=MethodHandles.publicLookup();
2、MethodType
(1)創(chuàng)建MethodType
// MethodType使用其靜態(tài)方法創(chuàng)建
// 獲取方法,第一個(gè)參數(shù)為返回值,剩余的參數(shù)為方法參數(shù)
// 該類能夠產(chǎn)生對(duì)方法的描述即 (Ljava/lang/Object;)V 該方法接收一個(gè)Object類型的值,沒有返回值
// 其實(shí)該類就是用來對(duì)方法的描述,描述這個(gè)方法接受什么參數(shù),返回什么類型的值
MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes)
MethodType genericMethodType(int objectArgCount, boolean finalArray)
MethodType fromMethodDescriptorString(String descriptor, ClassLoader loader)
想要獲得MethodType對(duì)象,MethodType類提供了三個(gè)靜態(tài)方法,如上所述。
// 第一個(gè)參數(shù)代表返回類型,如果沒有則指定void.class即可,后面的參數(shù)都是這個(gè)方法接收的參數(shù)類型,可以有多個(gè),也可以沒有,MethodType.methodType()有多個(gè)重載方法
MethodType.methodType(Class<?>, Class<?>, Class<?>...)
// 生成一個(gè)MethodType,第一個(gè)參數(shù)表示要生成的參數(shù)個(gè)數(shù),并且都是Object類型,第二個(gè)參數(shù)表示是否要在最后再添加一個(gè)Object類型的數(shù)組,注意是添加哦
MethodType genericMethodType(int objectArgCount, boolean finalArray)
// 從方法描述符來生成一個(gè)MethodType, 第二個(gè)參數(shù)為一個(gè)類加載器,如果為null則使用系統(tǒng)類加載器
MethodType fromMethodDescriptorString(String descriptor, ClassLoader loader)
// 生成一個(gè) 接受int、long和String類型的參數(shù),返回一個(gè)String類型
MethodType.fromMethodDescriptorString("(IJLjava/lang/String;)Ljava/lang/String;", null);
(2)修改MethodType
獲得一個(gè)具體MethodType實(shí)例后,我們可以對(duì)它進(jìn)行一些修改,比如更改參數(shù)類型,添加一個(gè)參數(shù),刪除一個(gè)參數(shù)等等,但是由于MethodType本身是不可變的,所以每對(duì)其進(jìn)行一次更改都會(huì)產(chǎn)生一個(gè)新的MethodType對(duì)象?????
// 在方法后面追加多個(gè)參數(shù) MethodType appendParameterTypes(List<Class<?>> ptypesToInsert) // 在方法后買你追加一個(gè)參數(shù) MethodType appendParameterTypes(Class<?>... ptypesToInsert) // 在指定參數(shù)位置插入一個(gè)參數(shù) 從 0開始 MethodType insertParameterTypes(int num, Class<?>... ptypesToInsert) // 在指定參數(shù)位置插入多個(gè)參數(shù) 從 0開始 MethodType insertParameterTypes(int num, List<Class<?>> ptypesToInsert) // 改變返回值類型 MethodType changeReturnType(Class<?> nrtype) // 改變指定參數(shù)位置的參數(shù)類型 MethodType changeParameterType(int num, Class<?> nptype) // 把基本類型變成對(duì)應(yīng)的包裝類型 (裝箱) MethodType wrap() // 把包裝類型變成對(duì)應(yīng)的基本類型(拆箱) MethodType unwrap() // 把所有引用類型的參數(shù)變?yōu)镺bject類型 MethodType erase() // 把所有參數(shù)都變成Object類型 MethodType generic()
// 構(gòu)造出一個(gè) (int,long,String)String
MethodType methodType = MethodType.fromMethodDescriptorString("(IJLjava/lang/String;)Ljava/lang/String;", null);
// (double, long, String)String
methodType = methodType.changeParameterType(0, Double.TYPE);
// (double, long, String, Object)String
methodType = methodType.appendParameterTypes(Object.class);
// (boolean, double, long, String, Object)String
methodType = methodType.insertParameterTypes(0, Boolean.TYPE);
// (float, double, long, String, Object)String
methodType = methodType.changeParameterType(0, Float.TYPE);
// (float, double, long, String, Object)Object
methodType = methodType.changeReturnType(Object.class);
// (Float, Double, Long, String, Object)Object
methodType = methodType.wrap();
// (float, double, long, String, Object)Object
methodType = methodType.unwrap();
// (float, double, long, Object, Object)Object
methodType = methodType.erase();
// (Object, Object, Object, Object, Object)Object
methodType = methodType.generic();
List<Class<?>> classList = methodType.parameterList();
for (Class<?> clazz : classList) {
System.out.println(clazz.getName());
}
3、創(chuàng)建MethodHandle
(1)MethodHandle (方法句柄)
// 所有方法都會(huì)查找指定類中的指定方法,如果查找到了
// 則會(huì)返回這個(gè)方法的方法句柄
// 返回指定類的指定構(gòu)造函數(shù)
public MethodHandle findConstructor(Class<?> refc,
MethodType type);
// 查找虛方法 final修飾的也可找到
// 可以簡(jiǎn)單理解為虛方法指的是可以被子類覆蓋的方法
public MethodHandle findVirtual(Class<?> refc,
String name,
MethodType type);
// 通過反射獲取方法句柄
public MethodHandle unreflect(Method m);
// 查找靜態(tài)方法
public MethodHandle findStatic(Class<?> refc,
String name,
MethodType type);
// 查找某個(gè)字段,并生成get方法的方法句柄(類中不需要存在這個(gè)字段的get方法)
ublic MethodHandle findGetter(Class<?> refc,
String name,
Class<?> type)
// 查找某個(gè)字段,并生成set方法的方法句柄(類中不需要存在這個(gè)字段的set方法)
public MethodHandle findSetter(Class<?> refc,
String name,
Class<?> type)
(2)MethodHandles中靜態(tài)工廠方法創(chuàng)建通用的方法句柄
// 1、用來創(chuàng)建操作數(shù)組的方法句柄
//MethodHandle arrayElementGetter(Class<?> arrayClass)
//MethodHandle arrayElementSetter(Class<?> arrayClass)
int[] arr = new int[]{1, 2, 3, 4, 5, 6};
MethodHandle getter = MethodHandles.arrayElementGetter(int[].class);
MethodHandle setter = MethodHandles.arrayElementSetter(int[].class);
setter.bindTo(arr).invoke(2, 50);
System.out.println(getter.bindTo(arr).invoke(2));
System.out.println(Arrays.toString(arr));
// 2、MethodHandle identity(Class<?> type)
// 該方法總是返回你給定的值,即你傳的參數(shù)是什么就返回什么
MethodHandle identity = MethodHandles.identity(String.class);
System.out.println(identity.invoke("hello world"));
// 3、MethodHandle constant(Class<?> type, Object value)
//與上面那個(gè)方法不同的是,該方法在創(chuàng)建方法句柄的時(shí)候就指定一個(gè)值,然后每次調(diào)用這個(gè)方法句柄的時(shí)候都會(huì)返回這個(gè)值
MethodHandle helloWorld = MethodHandles.constant(String.class, "hello world");
System.out.println(helloWorld.invoke());
// 4、MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes)
// 可以簡(jiǎn)單理解為在調(diào)用的時(shí)候忽略掉哪些位置上的參數(shù)
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle methodHandle = lookup.findStatic(TestMain.class, "test", MethodType.methodType(int.class, int.class));
// 忽略第0個(gè)參數(shù)并且類型為int類型的參數(shù)
methodHandle = MethodHandles.dropArguments(methodHandle, 0, int.class);
// 實(shí)際傳遞的只有3
methodHandle.invoke(2, 3);
}
public static int test(int i) {
System.out.println(i);
return 3;
}
// 5、MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters)
// 可以在方法調(diào)用的時(shí)候?qū)?shù)進(jìn)行處理
// 下面這個(gè)例子,test方法接收的是一個(gè)int類型的參數(shù),但是我們傳遞的是一個(gè)字符串。因此我們把參數(shù)進(jìn)行了一個(gè)處理 test = MethodHandles.filterArguments(test, 0, length);這行代碼就是表示,test方法句柄調(diào)用的時(shí)候調(diào)用length方法句柄進(jìn)行處理。
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle length = lookup.findVirtual(String.class, "length", MethodType.methodType(int.class));
MethodHandle test = lookup.findStatic(TestMain.class, "test", MethodType.methodType(int.class, int.class));
test = MethodHandles.filterArguments(test, 0, length);
// test()方法實(shí)際接收到的參數(shù)是5
test.invoke("sdfsd");
}
public static int test(int i) {
System.out.println(i);
return 3;
}
// 6、MethodHandle insertArguments(MethodHandle target, int pos, Object... values)
// 給指定位置上的參數(shù)預(yù)先綁定一個(gè)值,這樣在調(diào)用的時(shí)候就不能傳了
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle methodHandle = lookup.findStatic(TestMain.class, "test", MethodType.methodType(int.class, int.class));
// 預(yù)先給指定位置的參數(shù)綁定一個(gè)值
methodHandle = MethodHandles.insertArguments(methodHandle, 0, 22);
// 由于參數(shù)i已經(jīng)綁定值了,在這里調(diào)用的時(shí)候就不能傳遞參數(shù)了
methodHandle.invoke();
}
public static int test(int i) {
System.out.println(i);
return 3;
// 7、MethodHandle foldArguments(MethodHandle target, MethodHandle combiner)
// 與上面的方法類似,不同的是該方法不是在指定位置綁定值,而是通過一個(gè)方法句柄的返回值,將該返回值放到最終在調(diào)用方法的前面
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle methodHandle = lookup.findStatic(TestMain.class, "test", MethodType.methodType(int.class, int.class, int.class, int.class, int.class));
MethodHandle max = lookup.findStatic(Math.class, "max", MethodType.methodType(int.class, int.class, int.class));
methodHandle = MethodHandles.foldArguments(methodHandle, max);
methodHandle.invoke(4, 5, 6);
}
public static int test(int i, int i2, int i3, int i4) {
// 打印5
System.out.println(i);
return 3;
}
// 8、MethodHandle catchException(MethodHandle target, Class<? extends Throwable> exType, MethodHandle handler)
// 如果terget方法句柄出現(xiàn)了指定的異常或其指定的子類異常,則調(diào)用handler方法
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle methodHandle = lookup.findStatic(TestMain.class, "test", MethodType.methodType(int.class, int.class, int.class, int.class, int.class));
MethodHandle exceptionHandle = lookup.findStatic(TestMain.class, "handleException", MethodType.methodType(int.class, Exception.class, int.class, int.class, int.class, int.class));
methodHandle = MethodHandles.catchException(methodHandle, Exception.class, exceptionHandle);
methodHandle.invoke(4, 5, 6, 7);
}
public static int test(int i, int i2, int i3, int i4) {
System.out.println(i);
throw new RuntimeException("test出現(xiàn)異常");
}
public static int handleException( Exception e, int i, int i2, int i3, int i4) {
System.out.println("handleException:\n" + e.getMessage());
return 2;
}
值得注意的是 handleException方法的 異常類型參數(shù)只能在第一個(gè)位置,然后其他參數(shù)必須與出現(xiàn)異常方法的參數(shù)類型一致
// 9、MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType)
// 構(gòu)造出一個(gè)只拋出異常的方法句柄
MethodHandle handle = MethodHandles.throwException(String.class, Exception.class);
String invoke = (String) handle.invoke(new RuntimeException("throw"));
4、MethodHandle中的方法
// MethodHandle bindTo(Object x) 把一個(gè)對(duì)象與綁定并返回綁定后的方法句柄 MethodHandle bindTo(Object x) /** 特點(diǎn): invoke()方法是MethodHandle的一個(gè)通用方法,它允許在調(diào)用時(shí)執(zhí)行類型轉(zhuǎn)換。 如果提供的參數(shù)類型與目標(biāo)方法不匹配,invoke()會(huì)嘗試使用MethodHandle的asType()方法進(jìn)行參數(shù)適配。 參數(shù): invoke()方法接受一個(gè)可變參數(shù)列表(Object... args),其中第一個(gè)參數(shù)(如果目標(biāo)方法是實(shí)例方法)是實(shí)例對(duì)象,后續(xù)參數(shù)是傳遞給目標(biāo)方法的參數(shù)。 */ Object invoke(Object... args) /** 特點(diǎn): invokeExact()方法提供了嚴(yán)格的類型檢查。 如果提供的參數(shù)類型與目標(biāo)方法的參數(shù)類型不匹配,invokeExact()將拋出WrongMethodTypeException。 參數(shù): invokeExact()同樣接受一個(gè)可變參數(shù)列表(Object... args),但要求這些參數(shù)的類型必須與目標(biāo)方法的參數(shù)類型完全匹配。 */ Object invokeExact(Object... args) /** 特點(diǎn): invokeWithArguments()方法允許使用List<Object>作為參數(shù)列表進(jìn)行調(diào)用。 這為調(diào)用者提供了一種更靈活的方式來構(gòu)建參數(shù)列表,尤其是當(dāng)參數(shù)數(shù)量不確定或需要?jiǎng)討B(tài)構(gòu)建時(shí)。 參數(shù): invokeWithArguments()接受一個(gè)List<Object>參數(shù),其中第一個(gè)元素(如果目標(biāo)方法是實(shí)例方法)是實(shí)例對(duì)象,后續(xù)元素是傳遞給目標(biāo)方法的參數(shù)。 */ Object invokeWithArguments(Object... arguments)
invoke():提供類型適配的靈活調(diào)用,允許在運(yùn)行時(shí)轉(zhuǎn)換參數(shù)類型。invokeExact():提供嚴(yán)格的類型檢查,要求參數(shù)類型與目標(biāo)方法完全匹配。invokeWithArguments():允許使用列表作為參數(shù)進(jìn)行調(diào)用,提供了更大的靈活性。
在選擇使用哪種方法時(shí),應(yīng)該根據(jù)具體需求來決定。如果希望進(jìn)行嚴(yán)格的類型檢查,可以使用invokeExact();如果需要更靈活的參數(shù)傳遞方式,可以考慮使用invoke()或invokeWithArguments()。同時(shí),需要注意的是,invokeExact()的性能通常優(yōu)于invoke(),因?yàn)樗苊饬嗽谶\(yùn)行時(shí)進(jìn)行類型適配的開銷。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
使用Java visualVM監(jiān)控遠(yuǎn)程JVM的流程分析
我們經(jīng)常需要對(duì)我們的開發(fā)的軟件做各種測(cè)試, 軟件對(duì)系統(tǒng)資源的使用情況更是不可少,JDK1.6開始自帶的VisualVM就是不錯(cuò)的監(jiān)控工具,本文給大家分享使用Java visualVM監(jiān)控遠(yuǎn)程JVM的問題,感興趣的朋友跟隨小編一起看看吧2021-05-05
通過Spring Security魔幻山谷講解獲取認(rèn)證機(jī)制核心原理
這篇文章主要介紹了通過Spring Security魔幻山谷講解獲取認(rèn)證機(jī)制核心原理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
Java中動(dòng)態(tài)設(shè)置JVM參數(shù)的方法總結(jié)
通過動(dòng)態(tài)設(shè)置JVM參數(shù),開發(fā)者可以更有效地管理資源使用和優(yōu)化性能,本文將詳細(xì)闡述如何在Java中動(dòng)態(tài)設(shè)置JVM參數(shù),感興趣的小伙伴可以了解下2024-12-12
SpringBoot使用FFmpeg實(shí)現(xiàn)M3U8切片轉(zhuǎn)碼播放
FFmpeg是一個(gè)開源跨平臺(tái)的多媒體處理工具套件,它支持音頻、視頻文件的編碼、解碼、流媒體傳輸以及轉(zhuǎn)換等多種操作,本文小編給大家介紹了SpringBoot使用FFmpeg實(shí)現(xiàn)M3U8切片轉(zhuǎn)碼播放的操作,需要的朋友可以參考下2024-08-08
Runtime.getRuntime().exec 路徑包含空格的解決
這篇文章主要介紹了Runtime.getRuntime().exec 路徑包含空格的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
springboot restTemplate連接池整合方式
這篇文章主要介紹了springboot restTemplate連接池整合方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
java string類型轉(zhuǎn)換boolean類型的方法
下面小編就為大家?guī)硪黄猨ava string類型轉(zhuǎn)換boolean類型的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-11-11
springboot讀取application.yml報(bào)錯(cuò)問題及解決
這篇文章主要介紹了springboot讀取application.yml報(bào)錯(cuò)問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
Java程序中十種常見報(bào)錯(cuò)及解決方案(附示例代碼)
這篇文章主要介紹了Java程序中十種常見報(bào)錯(cuò)及解決方案的相關(guān)資料,掌握并解決這些常見的Java錯(cuò)誤,將極大地提高你的開發(fā)效率和代碼質(zhì)量,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2026-01-01
將springboot項(xiàng)目生成可依賴的jar并引入到項(xiàng)目中的方法
SpringBoot項(xiàng)目默認(rèn)打包的是可運(yùn)行jar包,也可以打包成不可運(yùn)行的jar包,本文給大家介紹將springboot項(xiàng)目生成可依賴的jar并引入到項(xiàng)目中的方法,感興趣的朋友一起看看吧2023-11-11

