Java調(diào)用本地庫的JNA快速入門教程
簡介:JNA是一個開源庫,它簡化了Java代碼調(diào)用操作系統(tǒng)API的過程,無需編寫JNI代碼。本文通過一個示例項目展示了如何使用JNA與C/C++編寫的DLL交互,包括定義原生接口、數(shù)據(jù)類型映射、指針和引用的處理、結(jié)構(gòu)體的使用以及異常處理等。項目包含DLL文件、JNA接口定義和主程序,幫助讀者理解和實踐JNA的使用。

1. JNA簡介和優(yōu)點
Java Native Access(JNA)是一個開源的Java庫,它提供了直接在Java代碼中調(diào)用本地(非Java)庫功能的能力,而不必編寫本地代碼。它的主要優(yōu)點在于大大簡化了跨平臺訪問原生代碼的復(fù)雜性,允許開發(fā)者專注于Java層的開發(fā),同時復(fù)用已有的本地代碼庫。
1.1 JNA的應(yīng)用場景
JNA廣泛應(yīng)用于需要與原生API交互的Java應(yīng)用程序中,例如,當開發(fā)者需要訪問操作系統(tǒng)級別的API或者調(diào)用一些老舊的本地庫時,JNA提供了一個無縫接口。
import com.sun.jna.Native;
import com.sun.jna.Library;
import com.sun.jna.platform.win32.User32;
public class JNAExample {
public interface CLibrary extends Library {
CLibrary INSTANCE = Native.load("c", CLibrary.class);
int MessageBox(int hWnd, String text, String caption, int type);
}
public static void main(String[] args) {
CLibrary.INSTANCE.MessageBox(0, "Hello, world!", "JNA Example", 0);
}
}通過上述代碼,我們在Java程序中直接調(diào)用了Windows平臺的MessageBox函數(shù)。
1.2 JNA的優(yōu)勢
使用JNA的優(yōu)勢在于它提供了一種簡潔的方式來調(diào)用原生庫,無需借助JNI(Java Native Interface)編寫復(fù)雜的本地代碼。此外,JNA自帶跨平臺支持,無需為不同的操作系統(tǒng)編寫不同的本地代碼。這也意味著,作為Java開發(fā)者,可以更輕松地處理不同平臺間差異帶來的問題。
2. 定義Native Interface
2.1 接口基礎(chǔ)
2.1.1 接口定義與Java類的關(guān)聯(lián)
在Java Native Access (JNA) 中,定義Native Interface 是讓Java程序能夠調(diào)用本地代碼的第一步。接口定義是通過Java中的接口(Interface)來完成的,這種接口與普通Java接口不同,它主要負責(zé)聲明本地方法。與本地代碼的關(guān)聯(lián)通過注解 @Structure.FieldOrder 來實現(xiàn),它指定了在結(jié)構(gòu)體中字段的順序,以確保Java能夠按照正確的順序?qū)?shù)據(jù)映射到本地代碼中。
以下是一個簡單的例子:
public interface MyNativeInterface extends Library {
void myNativeFunction(int arg);
}
在這個例子中, MyNativeInterface 接口擴展了 Library 接口,允許它繼承 load 方法,用于加載動態(tài)鏈接庫。聲明的 myNativeFunction 方法代表了將要在本地庫中被調(diào)用的函數(shù)。為了使這個接口能夠與本地庫中的函數(shù)相對應(yīng),JNA 需要知道具體要調(diào)用的函數(shù)名稱。這可以通過在接口中使用 @Function 注解來指定函數(shù)名稱來實現(xiàn)。
2.1.2 接口方法映射到本地函數(shù)
在定義了接口之后,我們需要確保接口中的每個方法都映射到了正確的本地函數(shù)。這通常是通過JNA的注解來完成的,如 @Function 注解用于指定本地函數(shù)的名稱。
public interface MyNativeInterface extends Library {
void myNativeFunction(int arg);
@Function(value="myNativeFunction")
void theRealFunction(int arg);
}在這個例子中, myNativeFunction 是Java接口中的方法名,而通過 @Function(value="myNativeFunction") 注解,我們告訴JNA這個Java方法對應(yīng)于本地庫中的函數(shù) myNativeFunction 。
2.2 動態(tài)鏈接庫的加載
2.2.1 庫的加載過程
加載動態(tài)鏈接庫(DLLs或.so文件)是使用JNA非常關(guān)鍵的一步。JNA通過 Library 接口中的 load 方法來加載動態(tài)鏈接庫。 load 方法是一個靜態(tài)方法,需要提供庫名作為參數(shù)。例如,在Windows上,你可以通過 MyNativeInterface.INSTANCE.load("mylib") 來加載名為 mylib.dll 的庫;在Linux上則是加載名為 libmylib.so 的共享庫。
MyNativeInterface instance = Native.load("mylib", MyNativeInterface.class);在這段代碼中, instance 對象將代表加載的動態(tài)鏈接庫,并且可以用來調(diào)用映射到Java接口的方法。
2.2.2 動態(tài)鏈接庫的卸載
在JNA中,卸載動態(tài)鏈接庫不像加載那樣直接。通常,動態(tài)鏈接庫在Java虛擬機(JVM)停止運行時會自動被卸載。但是,如果你需要在程序運行期間手動控制資源,可以通過平臺相關(guān)的代碼來實現(xiàn)庫的顯式卸載。
以Windows為例,可以通過調(diào)用 FreeLibrary 函數(shù)來卸載動態(tài)鏈接庫,這需要使用 Kernel32 庫。在JNA中,你可以這樣做:
Kernel32 kernel32 = Native.load("kernel32", Kernel32.class);
boolean result = kernel32.FreeLibrary(MyNativeInterface.INSTANCE.getPointer());
在這段代碼中, MyNativeInterface.INSTANCE.getPointer() 獲取了動態(tài)鏈接庫的句柄,并傳遞給 FreeLibrary 方法。需要注意的是,手動卸載庫可能會導(dǎo)致不穩(wěn)定的程序行為,特別是如果庫中的某些資源還沒有被釋放或者正在被使用時。
以上內(nèi)容介紹了定義Native Interface時需要進行的操作和注意事項。接下來的章節(jié)將討論如何處理數(shù)據(jù)類型映射,以便能夠在Java和本地代碼之間安全有效地傳遞數(shù)據(jù)。
3. 數(shù)據(jù)類型映射規(guī)則
3.1 基本數(shù)據(jù)類型映射
3.1.1 原生Java類型與本地類型的對應(yīng)關(guān)系
JNA(Java Native Access)框架的主要優(yōu)勢之一是在Java世界和本地世界之間提供了一種無縫的數(shù)據(jù)類型映射機制。原生Java類型映射到本地類型是JNA核心功能的基礎(chǔ),它保證了數(shù)據(jù)在Java和本地代碼之間正確無誤地傳輸。
在JNA中,Java的基本數(shù)據(jù)類型,例如 int 、 long 、 short 等,會自動映射到相應(yīng)大小和精度的本地類型。對于浮點數(shù), float 類型映射到本地的 float 類型,而 double 類型映射到本地的 double 類型。對于布爾類型,JNA使用 byte 類型(在C語言中為 bool 類型)來表示。
這種映射不是簡單的一對一映射,它還考慮到了平臺差異,比如在某些平臺上 int 和 long 的大小可能不同。JNA的內(nèi)部機制能夠處理這些差異,確保無論在什么平臺上,Java代碼的編譯和本地代碼的編譯都能正常進行。
public interface NativeLib {
int add(int a, int b); // Java int maps to native int
long getLong(); // Java long maps to native long
float getFloat(); // Java float maps to native float
double getDouble(); // Java double maps to native double
}3.1.2 字符串與字符數(shù)組的映射處理
在Java和C語言中處理字符串的方式有所不同。Java中字符串是對象,而在C語言中是字符數(shù)組。在JNA中,字符串映射通常使用 String 類型來處理。JNA提供了多種字符串處理選項,如使用 String 、 char[] 或者 WString (用于寬字符字符串,即 wchar_t[] )等。
JNA處理字符串的方式取決于定義的Java方法簽名以及目標本地庫的字符串處理約定。比如,如果本地庫期望一個以null結(jié)尾的UTF-8編碼的字符串,那么相應(yīng)的Java方法簽名可能如下:
public interface NativeLib {
String sayHello(String name); // Java String maps to null-terminated UTF-8 string in C
}JNA會自動處理字符串到本地的轉(zhuǎn)換,包括編碼轉(zhuǎn)換和添加空字符(null-termination)。
3.2 復(fù)雜數(shù)據(jù)類型映射
3.2.1 結(jié)構(gòu)體的映射
JNA提供了復(fù)雜數(shù)據(jù)類型的映射能力,最典型的如結(jié)構(gòu)體(struct)的映射。在Java中,結(jié)構(gòu)體可以通過創(chuàng)建一個簡單的接口來映射,JNA框架會處理好從Java對象到本地結(jié)構(gòu)體的轉(zhuǎn)換。
public interface MyStruct extends Structure {
public static class ByValue extends MyStruct implements Structure.ByValue {}
public static class ByReference extends MyStruct implements Structure.ByReference {}
public int field1;
public String field2; // JNA will convert this to native string type
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("field1", "field2");
}
}在上面的代碼示例中, MyStruct 是一個映射到本地結(jié)構(gòu)體的接口, ByValue 和 ByReference 類型用于區(qū)分是通過值傳遞還是通過引用傳遞。JNA支持直接的結(jié)構(gòu)體映射,結(jié)構(gòu)體內(nèi)的字段順序需要通過 getFieldOrder 方法來顯式指定,確保與本地結(jié)構(gòu)體的布局一致。
3.2.2 數(shù)組和字符串的映射
數(shù)組的映射在JNA中比較直接。如果本地庫需要接收一個數(shù)組參數(shù),可以直接在Java接口中定義一個數(shù)組類型的參數(shù)。
public interface NativeLib {
void processArray(int[] array); // Java int[] maps to native int*
}
這里的 int[] 會被JNA自動映射到對應(yīng)的本地指針類型 int* 。
字符串數(shù)組的映射稍微復(fù)雜一些,因為需要考慮字符串的編碼以及數(shù)組結(jié)束的標識。JNA提供了 PointerByReference 或者 Structure 的數(shù)組來映射字符串數(shù)組,同時提供了 String[] 、 Pointer[] 和 byte[] 等不同的映射方式,以適應(yīng)不同情況下對字符串數(shù)組的處理需求。
JNA通過靈活的映射機制和強大的類型轉(zhuǎn)換能力,使得Java調(diào)用本地庫的復(fù)雜數(shù)據(jù)類型變得簡單直接,極大地降低了Java應(yīng)用和本地代碼交互的難度。
4. 指針和引用的處理方法
4.1 指針的使用
4.1.1 Java中的指針概念
在Java語言中,通常不直接使用指針這一概念,而是通過對象引用來操作對象。然而,在使用Java Native Access (JNA) 進行本地方法調(diào)用時,指針成為了不可回避的話題。在本地代碼中,指針是訪問內(nèi)存的基礎(chǔ),而JNA提供了一種機制將這些指針映射回Java世界,使得Java程序能夠與本地代碼中的指針進行交互。
指針在JNA中的使用主要涉及Java中的 Pointer 類,這個類代表了一個指向原始數(shù)據(jù)的指針。JNA允許通過 Pointer 類的實例訪問本地內(nèi)存,可以將其看作是對原生指針的抽象封裝。
4.1.2 指針類型的聲明和操作
在JNA中,指針類型的聲明非常直觀,你可以直接使用 Pointer 類的實例作為參數(shù)傳遞給本地方法。操作指針主要包括獲取和設(shè)置指針指向的內(nèi)存數(shù)據(jù),例如:
Pointer pointer = new Pointer(內(nèi)存地址); int value = pointer.getInt(0); // 獲取指針指向的內(nèi)存中偏移0位置的整數(shù)值 pointer.setInt(0, newValue); // 設(shè)置指針指向的內(nèi)存中偏移0位置的整數(shù)值為newValue
此外, Pointer 類還提供了許多有用的方法來處理內(nèi)存,比如 getString 和 setString 用于處理字符串, size 用于獲取指針指向內(nèi)存的大小等。
String str = pointer.getString(0); // 獲取指針指向的內(nèi)存中偏移0位置的字符串 pointer.setString(0, "newString"); // 設(shè)置指針指向的內(nèi)存中偏移0位置的字符串為"newString"
指針的使用往往涉及到內(nèi)存操作,所以在使用時需要格外小心,確保不會出現(xiàn)內(nèi)存泄漏或者越界訪問等問題。
4.2 引用的傳遞
4.2.1 引用類型數(shù)據(jù)的映射
在JNA中,引用通常表示為指針。傳遞引用可以允許本地方法修改Java對象的值,或者實現(xiàn)回調(diào)函數(shù)等高級特性。當需要傳遞引用時,我們通常使用 Pointer 類的實例來傳遞Java對象的引用。
public class MyObject {
public int value;
}
public class NativeLib {
public native void modifyByReference(Pointer reference);
}
NativeLib lib = NativeLoader.load(NativeLib.class);
MyObject obj = new MyObject();
Pointer ref = new Pointer(ByReference.Util.objectToHandle(obj));
lib.modifyByReference(ref);
// 之后可以通過obj.value獲取修改后的值在上面的例子中, ByReference.Util.objectToHandle 方法用于獲取Java對象對應(yīng)的本地引用,這樣就可以在本地方法中對它進行操作。
4.2.2 引用在本地方法中的應(yīng)用實例
引用的傳遞在本地方法調(diào)用中非常有用,尤其是在需要修改傳遞給本地方法的Java對象狀態(tài)時。舉個例子,假設(shè)有一個本地方法可以修改傳入?yún)?shù)的值:
void incrementByPointer(int *value) {
(*value)++;
}
在Java中,我們可以這樣調(diào)用:
public class IncrementExample {
static {
Native.register("yourLibraryName");
}
public static void main(String[] args) {
Pointer ptr = new Pointer(0); // 初始值為0
incrementByPointer(ptr);
System.out.println("Incremented value: " + ptr.getInt(0)); // 輸出1
}
private static native void incrementByPointer(Pointer value);
}在這個例子中, incrementByPointer 方法通過指針引用修改了指針指向的值。在Java代碼中,我們使用 Pointer 類來傳遞引用,并在本地方法調(diào)用后通過 getInt 方法檢索更新后的值。
引用的傳遞機制是JNA中非常重要的特性,它允許Java程序與本地代碼之間有著更深層次的交互和數(shù)據(jù)共享。
5. 結(jié)構(gòu)體的定義和使用
5.1 結(jié)構(gòu)體的定義
5.1.1 Java中定義結(jié)構(gòu)體的方式
在Java中,通常沒有直接對應(yīng)C語言中的結(jié)構(gòu)體(struct)的概念。然而,Java Native Access(JNA)庫提供了一種方式來模擬結(jié)構(gòu)體的行為,這使得Java程序能夠與本地代碼進行交互。在JNA中,結(jié)構(gòu)體是通過定義一個Java類來實現(xiàn)的,該類繼承自 Structure 類,而且必須被聲明為 public 和 abstract 。每個字段都必須在結(jié)構(gòu)體類中聲明,并使用 @Field 注解來標記其在內(nèi)存中的位置。
以下是一個簡單的結(jié)構(gòu)體定義示例:
import com.sun.jna.Structure;
import com.sun.jna.ByReference;
public abstract class MyStruct extends Structure {
@Field(0)
public int field1;
@Field(1)
public double field2;
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("field1", "field2");
}
public MyStruct() {
super();
}
public MyStruct(int size) {
super(ByReference.class, size);
}
}在這個例子中,我們創(chuàng)建了一個名為 MyStruct 的類,它有 field1 和 field2 兩個字段。 getFieldOrder 方法返回一個包含字段名稱的列表,這些字段名稱對應(yīng)于結(jié)構(gòu)體中字段的順序,這是因為在結(jié)構(gòu)體中字段的順序至關(guān)重要。
5.1.2 結(jié)構(gòu)體字段的映射規(guī)則
在JNA中,結(jié)構(gòu)體字段映射到本地代碼時需要遵循一些規(guī)則。首先,每個字段都必須使用 @Field 注解來標明它的內(nèi)存位置。如果沒有提供位置信息,JNA將為每個字段分配連續(xù)的內(nèi)存地址,但有時候這樣的映射方式并不符合本地代碼的預(yù)期布局。因此,顯式地指定字段位置是非常重要的,特別是在與現(xiàn)有的本地庫進行交互時。
JNA支持多種字段類型,包括原生Java類型(如 int 、 double 等)、數(shù)組、指針、Java對象以及自定義的結(jié)構(gòu)體。每種類型都有相應(yīng)的映射規(guī)則,這允許結(jié)構(gòu)體能夠以一種透明的方式與本地庫進行交互。例如,一個原生類型的字段將會映射為本地結(jié)構(gòu)體中相同類型的字段,而一個數(shù)組字段將會映射為一個指針,指向該數(shù)組的數(shù)據(jù)。
5.2 結(jié)構(gòu)體的應(yīng)用
5.2.1 結(jié)構(gòu)體的創(chuàng)建和初始化
一旦定義了結(jié)構(gòu)體,接下來就是創(chuàng)建和初始化它們的實例。創(chuàng)建結(jié)構(gòu)體實例時,可以使用無參構(gòu)造函數(shù),這將創(chuàng)建一個空的結(jié)構(gòu)體實例,但是它不會分配任何內(nèi)存。為了分配內(nèi)存,可以使用帶有特定大小參數(shù)的構(gòu)造函數(shù),這樣可以創(chuàng)建一個引用類型的結(jié)構(gòu)體,該結(jié)構(gòu)體可以指向分配的本地內(nèi)存。
MyStruct myStruct = new MyStruct(); myStruct.field1 = 10; myStruct.field2 = 3.14; // 創(chuàng)建一個引用類型的結(jié)構(gòu)體,分配了16字節(jié)的內(nèi)存 MyStruct myStructRef = new MyStruct(16);
在使用結(jié)構(gòu)體時,如果要將其作為參數(shù)傳遞給本地函數(shù),應(yīng)確保使用引用類型(由 ByReference 標記的類型)。
5.2.2 結(jié)構(gòu)體與本地函數(shù)的數(shù)據(jù)交互
在本地代碼中定義了相應(yīng)的結(jié)構(gòu)體后,就能夠在Java和本地代碼之間傳遞復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。例如,考慮一個本地函數(shù) doSomethingWithStruct ,它接受一個結(jié)構(gòu)體參數(shù)并對其進行操作。
// 本地代碼示例
typedef struct {
int field1;
double field2;
} MyStruct;
void doSomethingWithStruct(MyStruct *s) {
s->field1 *= 2;
s->field2 += 1.0;
}在Java中,你可以這樣使用這個本地函數(shù):
Pointer pointer = new Memory(16); // 為結(jié)構(gòu)體分配本地內(nèi)存
MyStruct struct = new MyStruct(pointer);
struct.field1 = 1;
struct.field2 = 5.5;
// 獲取本地函數(shù)的引用
Function doSomethingFunc = Native.loadLibrary("library", Library.class).getFunction("doSomethingWithStruct");
doSomethingFunc.invoke(struct);
System.out.println("Field1: " + struct.field1); // 輸出 2
System.out.println("Field2: " + struct.field2); // 輸出 6.5在上面的Java代碼中,我們首先創(chuàng)建了一個指向本地內(nèi)存的 Pointer 對象,并用它來初始化我們的 MyStruct 實例。然后,我們加載了包含 doSomethingWithStruct 函數(shù)的本地庫,并調(diào)用該函數(shù)。最后,我們通過 struct 實例輸出了被修改過的值,以驗證函數(shù)的調(diào)用結(jié)果。
結(jié)構(gòu)體的這些操作提供了Java和本地代碼之間復(fù)雜數(shù)據(jù)交互的基礎(chǔ),為涉及復(fù)雜數(shù)據(jù)結(jié)構(gòu)的應(yīng)用程序提供了強大的支持。通過這種方式,開發(fā)者可以利用JNA的優(yōu)勢,以最小的努力來調(diào)用本地庫,而無需擔(dān)心底層的復(fù)雜性。
6. 回調(diào)函數(shù)在JNA中的實現(xiàn)
回調(diào)函數(shù)是編程中一個非常重要的概念,它允許Java程序在執(zhí)行某些操作時,能夠調(diào)用預(yù)先定義好的本地函數(shù)。這種機制在處理異步任務(wù)、事件通知或者實現(xiàn)復(fù)雜交互時尤為有用。
6.1 回調(diào)函數(shù)基礎(chǔ)
6.1.1 回調(diào)函數(shù)的概念和作用
回調(diào)函數(shù)本質(zhì)上是一個被引用的對象,它指向一個方法。在JNA中,我們可以通過定義接口來實現(xiàn)回調(diào)功能。當某個本地操作需要執(zhí)行一些操作時,它會調(diào)用這個接口中預(yù)定義的方法。這允許Java代碼在本地代碼執(zhí)行期間有機會執(zhí)行自己的代碼。
6.1.2 在Java中聲明回調(diào)函數(shù)的方法
在Java中聲明回調(diào)函數(shù)通常涉及以下步驟:
- 定義一個接口,其中包含預(yù)期被回調(diào)的方法。
- 使用
Callback接口標記接口。 - 在Java類中實現(xiàn)這個接口。
interface MyCallbackInterface extends Callback {
void callbackFunction(int arg);
}
6.2 回調(diào)函數(shù)高級應(yīng)用
6.2.1 利用回調(diào)函數(shù)實現(xiàn)異步通信
異步通信允許程序在等待一個長時間操作(如磁盤I/O或網(wǎng)絡(luò)通信)完成時繼續(xù)執(zhí)行其他任務(wù)。在JNA中,我們可以使用回調(diào)函數(shù)來接收操作完成的通知。
例如,我們定義一個本地函數(shù) doLongOperation ,它接受一個回調(diào)函數(shù)參數(shù)。當操作完成時,通過調(diào)用回調(diào)函數(shù)來通知Java程序。
public interface NativeLib extends Library {
void doLongOperation(MyCallbackInterface callback);
}
然后我們可以在Java中實現(xiàn)接口,并將其傳遞給本地函數(shù):
public class CallbackDemo {
public static void main(String[] args) {
NativeLib lib = (NativeLib) Native.loadLibrary("theLibrary", NativeLib.class);
MyCallbackInterface callback = new MyCallbackInterface() {
public void callbackFunction(int arg) {
System.out.println("Operation completed with result: " + arg);
}
};
lib.doLongOperation(callback);
// 程序可以繼續(xù)執(zhí)行其他任務(wù),而不是在此等待
}
}
6.2.2 回調(diào)函數(shù)中的數(shù)據(jù)管理和回調(diào)策略
在回調(diào)函數(shù)中處理數(shù)據(jù)時,需要特別注意線程安全和資源管理。例如,如果回調(diào)函數(shù)是在其他線程中執(zhí)行的,我們需要確保數(shù)據(jù)訪問是線程安全的,避免競態(tài)條件。
此外,處理回調(diào)的策略也可以多種多樣,例如:
- 阻塞回調(diào) :等待回調(diào)函數(shù)執(zhí)行完畢后繼續(xù)執(zhí)行。
- 非阻塞回調(diào) :將回調(diào)任務(wù)提交到一個線程池,然后繼續(xù)執(zhí)行當前線程。
使用JNA時,通常需要根據(jù)應(yīng)用程序的上下文和性能要求來選擇合適的回調(diào)策略。
通過本章節(jié)的介紹,我們了解了JNA中回調(diào)函數(shù)的基礎(chǔ)和高級應(yīng)用。
到此這篇關(guān)于Java調(diào)用本地庫的JNA快速入門的文章就介紹到這了,更多相關(guān)java調(diào)用jna內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實現(xiàn)枚舉狀態(tài)轉(zhuǎn)換的方法詳解
在軟件開發(fā)中,我們經(jīng)常需要處理不同系統(tǒng)或模塊間的狀態(tài)轉(zhuǎn)換,今天,我將通過一個電商訂單與物流狀態(tài)的轉(zhuǎn)換案例,展示如何優(yōu)雅地實現(xiàn)枚舉間的互相轉(zhuǎn)換,需要的朋友可以參考下2025-04-04
Jmeter內(nèi)置變量vars和props的使用詳解
JMeter是一個功能強大的負載測試工具,它提供了許多有用的內(nèi)置變量來支持測試過程,其中最常用的變量是 vars 和 props,本文通過代碼示例詳細給大家介紹了Jmeter內(nèi)置變量vars和props的使用,需要的朋友可以參考下2024-08-08
Spring框架應(yīng)用的權(quán)限控制系統(tǒng)詳解
在本篇文章里小編給大家整理的是關(guān)于基于Spring框架應(yīng)用的權(quán)限控制系統(tǒng)的研究和實現(xiàn),需要的朋友們可以學(xué)習(xí)下。2019-08-08
java根據(jù)擴展名獲取系統(tǒng)圖標和文件圖標示例
這篇文章主要介紹了java根據(jù)擴展名獲取系統(tǒng)圖標和文件圖標示例,需要的朋友可以參考下2014-03-03

