Java并發(fā)編程學(xué)習(xí)之Unsafe類與LockSupport類源碼詳析
一.Unsafe類的源碼分析
JDK的rt.jar包中的Unsafe類提供了硬件級(jí)別的原子操作,Unsafe里面的方法都是native方法,通過使用JNI的方式來(lái)訪問本地C++實(shí)現(xiàn)庫(kù)。
rt.jar 中 Unsafe 類主要函數(shù)講解, Unsafe 類提供了硬件級(jí)別的原子操作,可以安全的直接操作內(nèi)存變量,其在 JUC 源碼中被廣泛的使用,了解其原理為研究 JUC 源碼奠定了基礎(chǔ)。
首先我們先了解Unsafe類中主要方法的使用,如下:
1.long objectFieldOffset(Field field) 方法:返回指定的變量在所屬類的內(nèi)存偏移地址,偏移地址僅僅在該Unsafe函數(shù)中訪問指定字段時(shí)使用。如下代碼使用unsafe獲取AtomicLong中變量value在AtomicLong對(duì)象中的內(nèi)存偏移,代碼如下:
static {
try {
valueOffset = unsafe.objectFieldOffset(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex);
}
}
2.int arrayBaseOffset(Class arrayClass)方法:獲取數(shù)組中第一個(gè)元素的地址
3.int arrayIndexScale(Class arrayClass)方法:獲取數(shù)組中單個(gè)元素占用的字節(jié)數(shù)
3.boolean compareAndSwapLong(Object obj,long offset,long expect,long update)方法:比較對(duì)象obj中偏移量offset的變量的值是不是和expect相等,相等則使用update值更新,然后返回true,否則返回false。
4.public native long getLongVolative(Object obj,long offset)方法:獲取對(duì)象obj中偏移量offset的變量對(duì)應(yīng)的volative內(nèi)存語(yǔ)義的值。
5.void putOrderedLong(Object obj, long offset, long value) 方法:設(shè)置 obj 對(duì)象中 offset 偏移地址對(duì)應(yīng)的 long 型 field 的值為 value。這是有延遲的 putLongVolatile 方法,并不保證值修改對(duì)其它線程立刻可見。變量只有使用 volatile 修飾并且期望被意外修改的時(shí)候使用才有用。
6.void park(boolean isAbsolute, long time) 方法:阻塞當(dāng)前線程,其中參數(shù) isAbsolute 等于 false 時(shí)候,time 等于 0 表示一直阻塞,time 大于 0 表示等待指定的 time 后阻塞線程會(huì)被喚醒,這個(gè) time 是個(gè)相對(duì)值,是個(gè)增量值,也就是相對(duì)當(dāng)前時(shí)間累加 time 后當(dāng)前線程就會(huì)被喚醒。 如果 isAbsolute 等于 true,并且 time 大于 0 表示阻塞后到指定的時(shí)間點(diǎn)后會(huì)被喚醒,這里 time 是個(gè)絕對(duì)的時(shí)間,是某一個(gè)時(shí)間點(diǎn)換算為 ms 后的值。另外當(dāng)其它線程調(diào)用了當(dāng)前阻塞線程的 interrupt 方法中斷了當(dāng)前線程時(shí)候,當(dāng)前線程也會(huì)返回,當(dāng)其它線程調(diào)用了 unpark 方法并且把當(dāng)前線程作為參數(shù)時(shí)候當(dāng)前線程也會(huì)返回。
7.void unpark(Object thread)方法: 喚醒調(diào)用 park 后阻塞的線程,參數(shù)為需要喚醒的線程。
在JDK1.8中新增加了幾個(gè)方法,這里簡(jiǎn)單的列出Long類型操作的方法如下:
8.long getAndSetLong(Object obj, long offset, long update) 方法: 獲取對(duì)象 obj 中偏移量為 offset 的變量 volatile 語(yǔ)義的值,并設(shè)置變量 volatile 語(yǔ)義的值為 update。使用方法如下代碼:
public final long getAndSetLong(Object obj, long offset, long update)
{
long l;
do
{
l = getLongVolatile(obj, offset);//(1)
} while (!compareAndSwapLong(obj, offset, l, update));
return l;
}
從代碼中可以內(nèi)部代碼(1)處使用了getLongVolative獲取當(dāng)前變量的值,然后使用CAS原子操作進(jìn)行設(shè)置新值,這里使用while循環(huán)是考慮到多個(gè)線程同時(shí)調(diào)用的情況CAS失敗后需要自旋重試。
9.long getAndAddLong(Object obj, long offset, long addValue) 方法 :獲取對(duì)象 obj 中偏移量為 offset 的變量 volatile 語(yǔ)義的值,并設(shè)置變量值為原始值 +addValue。使用方法如下代碼:
public final long getAndAddLong(Object obj, long offset, long addValue)
{
long l;
do
{
l = getLongVolatile(obj, offset);
} while (!compareAndSwapLong(obj, offset, l, l + addValue));
return l;
}
類似于getAndSetLong的實(shí)現(xiàn),只是這里使用CAS的時(shí)候使用了原始值+傳遞的增量參數(shù)addValue的值。
那么如何使用Unsafe類呢?
看到 Unsafe 這個(gè)類如此牛叉,是不是很想進(jìn)行練習(xí),好了,首先看如下代碼所示:
package com.hjc;
import sun.misc.Unsafe;
/**
* Created by cong on 2018/6/6.
*/
public class TestUnSafe {
//獲取Unsafe的實(shí)例(2.2.1)
static final Unsafe unsafe = Unsafe.getUnsafe();
//記錄變量state在類TestUnSafe中的偏移值(2.2.2)
static final long stateOffset;
//變量(2.2.3)
private volatile long state = 0;
static {
try {
//獲取state變量在類TestUnSafe中的偏移值(2.2.4)
stateOffset = unsafe.objectFieldOffset(TestUnSafe.class.getDeclaredField("state"));
} catch (Exception ex) {
System.out.println(ex.getLocalizedMessage());
throw new Error(ex);
}
}
public static void main(String[] args) {
//創(chuàng)建實(shí)例,并且設(shè)置state值為1(2.2.5)
TestUnSafe test = new TestUnSafe();
//(2.2.6)
Boolean sucess = unsafe.compareAndSwapInt(test, stateOffset, 0, 1);
System.out.println(sucess);
}
}
代碼(2.2.1)獲取了Unsafe的一個(gè)實(shí)例,代碼(2.2.3)創(chuàng)建了一個(gè)變量state初始化為0.
代碼(2.2.4)使用unsafe.objectFieldOffset 獲取 TestUnSafe類里面的state變量 在 TestUnSafe對(duì)象里面的內(nèi)存偏移量地址并保存到stateOffset變量。
代碼(2.2.6)調(diào)用創(chuàng)建的unsafe實(shí)例的compareAndSwapInt方法,設(shè)置test對(duì)象的state變量的值,具體意思是如果test對(duì)象內(nèi)存偏移量為stateOffset的state的變量為0,則更新改值為1
上面代碼我們希望輸入true,然而執(zhí)行后會(huì)輸出如下結(jié)果:

為什么會(huì)這樣呢?必然需要進(jìn)入getUnsafe代碼中如看看里面做了啥:
private static final Unsafe theUnsafe = new Unsafe();
public static Unsafe getUnsafe(){
//(2.2.7)
Class localClass = Reflection.getCallerClass();
//(2.2.8)
if (!VM.isSystemDomainLoader(localClass.getClassLoader())) {
throw new SecurityException("Unsafe");
}
return theUnsafe;
}
//判斷paramClassLoader是不是BootStrap類加載器(2.2.9)
public static boolean isSystemDomainLoader(ClassLoader paramClassLoader){
return paramClassLoader == null;
}
代碼(2.2.7)獲取調(diào)用getUnsafe這個(gè)方法的對(duì)象的Class對(duì)象,這里是TestUnSafe.calss。
代碼(2.2.8)判斷是不是Bootstrap類加載器加載的localClass,這里關(guān)鍵要看是不是Bootstrap加載器加載了TestUnSafe.class。看過Java虛擬機(jī)的類加載機(jī)制的人,很明顯看出是由于TestUnSafe.class 是使用 AppClassLoader 加載的,所以這里直接拋出了異常。
那么問題來(lái)了,為什么需要有這個(gè)判斷呢?
我們知道Unsafe類是在rt.jar里面提供的,而rt.jar里面的類是使用Bootstrap類加載器加載的,而我們啟動(dòng)main函數(shù)所在的類是使用AppClassLoader加載的,所以在main函數(shù)里面加載Unsafe類時(shí)候鑒于雙親委派機(jī)制會(huì)委托給Bootstrap去加載Unsafe類。
如果沒有代碼(2.2.8)這個(gè)鑒權(quán),那么我們應(yīng)用程序就可以隨意使用Unsafe做事情了,而Unsafe類可以直接操作內(nèi)存,是很不安全的,所以JDK開發(fā)組特意做了這個(gè)限制,不讓開發(fā)人員在正規(guī)渠道下使用Unsafe類,而是在rt.jar里面的核心類里面使用Unsafe功能。
問題來(lái)了,如果我們真的想要實(shí)例化Unsafe類,使用Unsafe的功能,那該怎么辦呢?
我們不要忘記了反射這個(gè)黑科技,使用萬(wàn)能的反射來(lái)獲取Unsafe的實(shí)例方法,代碼如下:
package com.hjc;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* Created by cong on 2018/6/6.
*/
public class TestUnSafe {
static final Unsafe unsafe;
static final long stateOffset;
private volatile long state = 0;
static {
try {
// 反射獲取 Unsafe 的成員變量 theUnsafe(2.2.10)
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// 設(shè)置為可存?。?.2.11)
field.setAccessible(true);
// 獲取該變量的值(2.2.12)
unsafe = (Unsafe) field.get(null);
//獲取 state 在 TestUnSafe 中的偏移量 (2.2.13)
stateOffset = unsafe.objectFieldOffset(TestUnSafe.class.getDeclaredField("state"));
} catch (Exception ex) {
System.out.println(ex.getLocalizedMessage());
throw new Error(ex);
}
}
public static void main(String[] args) {
TestUnSafe test = new TestUnSafe();
Boolean sucess = unsafe.compareAndSwapInt(test, stateOffset, 0, 1);
System.out.println(sucess);
}
}
如果上面的代碼(2.2.10 2.2.11 2.2.12)反射獲取unsafe的實(shí)例,運(yùn)行結(jié)果如下:
二.LockSupport類源碼探究
JDK中的rt.jar里面的LockSupport是一個(gè)工具類,主要作用是掛起和喚醒線程,它是創(chuàng)建鎖和其他同步類的基礎(chǔ)。
LockSupport類與每個(gè)使用他的線程都會(huì)關(guān)聯(lián)一個(gè)許可證,默認(rèn)調(diào)用LockSupport 類的方法的線程是不持有許可證的,LockSupport內(nèi)部使用Unsafe類實(shí)現(xiàn)。
這里要注意LockSupport的幾個(gè)重要的函數(shù),如下:
1.void park() 方法: 如果調(diào)用 park() 的線程已經(jīng)拿到了與 LockSupport 關(guān)聯(lián)的許可證,則調(diào)用 LockSupport.park() 會(huì)馬上返回,否者調(diào)用線程會(huì)被禁止參與線程的調(diào)度,也就是會(huì)被阻塞掛起。例子如下代碼:
package com.hjc;
import java.util.concurrent.locks.LockSupport;
/**
* Created by cong on 2018/6/6.
*/
public class LockSupportTest {
public static void main( String[] args ) {
System.out.println( "park start!" );
LockSupport.park();
System.out.println( "park stop!" );
}
}
如上面代碼所示,直接在main函數(shù)里面調(diào)用park方法,最終結(jié)果只會(huì)輸出park start! 然后當(dāng)前線程會(huì)被掛起,這是因?yàn)槟J(rèn)下調(diào)用線程是不持有許可證的。運(yùn)行結(jié)果如下:
在看到其他線程調(diào)用 unpark(Thread thread) 方法并且當(dāng)前線程作為參數(shù)時(shí)候,調(diào)用park方法被阻塞的線程會(huì)返回,另外其他線程調(diào)用了阻塞線程的interrupt()方法,設(shè)置了中斷標(biāo)志時(shí)候或者由于線程的虛假喚醒原因后阻塞線程也會(huì)返回,所以調(diào)用 park() 最好也是用循環(huán)條件判斷方式。
需要注意的是調(diào)用park()方法被阻塞的線程被其他線程中斷后阻塞線程返回時(shí)候并不會(huì)拋出InterruptedException 異常。
2.void unpark(Thread thread) 方法 當(dāng)一個(gè)線程調(diào)用了 unpark 時(shí)候,如果參數(shù) thread 線程沒有持有 thread 與 LockSupport 類關(guān)聯(lián)的許可證,則讓 thread 線程持有。如果 thread 之前調(diào)用了 park() 被掛起,則調(diào)用 unpark 后,該線程會(huì)被喚醒。
如果 thread 之前沒有調(diào)用 park,則調(diào)用 unPark 方法后,在調(diào)用 park() 方法,會(huì)立刻返回,上面代碼修改如下:
package com.hjc;
import java.util.concurrent.locks.LockSupport;
/**
* Created by cong on 2018/6/6.
*/
public class LockSupportTest {
public static void main( String[] args ) {
System.out.println( "park start!" );
//使當(dāng)前線程獲取到許可證
LockSupport.unpark(Thread.currentThread());
//再次調(diào)用park
LockSupport.park();
System.out.println( "park stop!" );
}
}
運(yùn)行結(jié)果如下:
接下來(lái)我們?cè)诳匆粋€(gè)例子來(lái)加深對(duì) park,unpark 的理解,代碼如下:
import java.util.concurrent.locks.LockSupport;
/**
* Created by cong on 2018/6/6.
*/
public class LockSupportTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子線程 park start!");
// 調(diào)用park方法,掛起自己
LockSupport.park();
System.out.println("子線程 unpark!");
}
});
//啟動(dòng)子線程
thread.start();
//主線程休眠1S
Thread.sleep(1000);
System.out.println("主線程 unpark start!");
//調(diào)用unpark讓thread線程持有許可證,然后park方法會(huì)返回
LockSupport.unpark(thread);
}
}
運(yùn)行結(jié)果如下:
上面的代碼首先創(chuàng)建了一個(gè)子線程thread,啟動(dòng)后子線程調(diào)用park方法,由于默認(rèn)子線程沒有持有許可證,會(huì)把自己掛起
主線程休眠1s 目的是主線程在調(diào)用unpark方法讓子線程輸出 子線程park start! 并阻塞。
主線程然后執(zhí)行unpark方法,參數(shù)為子線程,目的是讓子線程持有許可證,然后子線程調(diào)用的park方法就返回了。
park方法返回時(shí)候不會(huì)告訴你是因?yàn)楹畏N原因返回,所以調(diào)用者需要根據(jù)之前是處于什么目前調(diào)用的park方法,再次檢查條件是否滿足,如果不滿足的話,還需要再次調(diào)用park方法。
例如,線程在返回時(shí)的中斷狀態(tài),根據(jù)調(diào)用前后中斷狀態(tài)對(duì)比就可以判斷是不是因?yàn)楸恢袛嗖欧祷氐摹?/p>
為了說(shuō)明調(diào)用 park 方法后的線程被中斷后會(huì)返回,修改上面例子代碼,刪除 LockSupport.unpark(thread); 然后添加 thread.interrupt(); 代碼如下:
import java.util.concurrent.locks.LockSupport;
/**
* Created by cong on 2018/6/6.
*/
public class LockSupportTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子線程 park start!");
// 調(diào)用park方法,掛起自己,只有中斷才會(huì)退出循環(huán)
while (!Thread.currentThread().isInterrupted()) {
LockSupport.park();
}
System.out.println("子線程 unpark!");
}
});
//啟動(dòng)子線程
thread.start();
//主線程休眠1S
Thread.sleep(1000);
System.out.println("主線程 unpark start!");
//中斷子線程
thread.interrupt();
}
}
運(yùn)行結(jié)果如下:

正如上面代碼,也就是只有當(dāng)子線程被中斷后子線程才會(huì)運(yùn)行結(jié)束,如果子線程不被中斷,即使你調(diào)用unPark(Thread) 子線程也不會(huì)結(jié)束。
3.void parkNanos(long nanos)方法:和 park 類似,如果調(diào)用 park 的線程已經(jīng)拿到了與 LockSupport 關(guān)聯(lián)的許可證,則調(diào)用 LockSupport.park() 會(huì)馬上返回,不同在于如果沒有拿到許可調(diào)用線程會(huì)被掛起 nanos 時(shí)間后在返回。
park 還支持三個(gè)帶有blocker參數(shù)的方法,當(dāng)線程因?yàn)闆]有持有許可證的情況下調(diào)用park 被阻塞掛起的時(shí)候,這個(gè)blocker對(duì)象會(huì)被記錄到該線程內(nèi)部。
使用診斷工具可以觀察線程被阻塞的原因,診斷工具是通過調(diào)用getBlocker(Thread)方法來(lái)獲取該blocker對(duì)象的,所以JDK推薦我們使用帶有blocker參數(shù)的park方法,并且blocker設(shè)置為this,這樣當(dāng)內(nèi)存dump排查問題時(shí)候就能知道是哪個(gè)類被阻塞了。
例子如下:
import java.util.concurrent.locks.LockSupport;
/**
* Created by cong on 2018/6/6.
*/
public class TestPark {
public void testPark(){
LockSupport.park();//(1)
}
public static void main(String[] args) {
TestPark testPark = new TestPark();
testPark.testPark();
}
}
運(yùn)行結(jié)果如下:

可以看到運(yùn)行在阻塞,那么我們要使用JDK/bin目錄下的工具看一下了,如果不知道的讀者,建議去先看一下JVM的監(jiān)控工具。
運(yùn)行后使用jstack pid 查看線程堆棧的時(shí)候,可以看到的結(jié)果如下:


然后我們進(jìn)行上面的代碼(1)進(jìn)行修改如下:
LockSupport.park(this);//(1)
再次運(yùn)行,再用jstack pid 查看的結(jié)果如下:

可以知道,帶blocker的park方法后,線程堆??梢蕴峁└嘤嘘P(guān)阻塞對(duì)象的信息。
那么我們接下來(lái)進(jìn)行park(Object blocker) 函數(shù)的源代碼查看,源碼如下:
public static void park(Object blocker) {
//獲取調(diào)用線程
Thread t = Thread.currentThread();
//設(shè)置該線程的 blocker 變量
setBlocker(t, blocker);
//掛起線程
UNSAFE.park(false, 0L);
//線程被激活后清除 blocker 變量,因?yàn)橐话愣际蔷€程阻塞時(shí)候才分析原因
setBlocker(t, null);
}
Thread類里面有個(gè)變量volatile Object parkBlocker 用來(lái)存放park傳遞的blocker對(duì)象,也就是把blocker變量存放到了調(diào)用park方法的線程的成員變量里面
4.void parkNanos(Object blocker, long nanos) 函數(shù) 相比 park(Object blocker) 多了個(gè)超時(shí)時(shí)間。
5.void parkUntil(Object blocker, long deadline) parkUntil源代碼如下:
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
//isAbsolute=true,time=deadline;表示到 deadline 時(shí)間時(shí)候后返回
UNSAFE.park(true, deadline);
setBlocker(t, null);
}
可以看到是一個(gè)設(shè)置deadline,時(shí)間單位為milliseconds,是從1970到現(xiàn)在某一個(gè)時(shí)間點(diǎn)換算為毫秒后的值,這個(gè)和parkNanos(Object blocker,long nanos)區(qū)別是后者是從當(dāng)前算等待nanos時(shí)間的,而前者是指定一個(gè)時(shí)間點(diǎn),
比如我們需要等待到2018.06.06 日 20:34,則把這個(gè)時(shí)間點(diǎn)轉(zhuǎn)換為從1970年到這個(gè)時(shí)間點(diǎn)的總毫秒數(shù)。
我們?cè)賮?lái)看一個(gè)例子,代碼如下:
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
/**
* Created by cong on 2018/6/6.
*/
public class FIFOMutex {
private final AtomicBoolean locked = new AtomicBoolean(false);
private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();
public void lock() {
boolean wasInterrupted = false;
Thread current = Thread.currentThread();
waiters.add(current);
// 只有隊(duì)首的線程可以獲取鎖(1)
while (waiters.peek() != current || !locked.compareAndSet(false, true)) {
LockSupport.park(this);
if (Thread.interrupted()) // (2)
wasInterrupted = true;
}
waiters.remove();
if (wasInterrupted) // (3)
current.interrupt();
}
public void unlock() {
locked.set(false);
LockSupport.unpark(waiters.peek());
}
}
可以看到這是一個(gè)先進(jìn)先出的鎖,也就是只有隊(duì)列首元素可以獲取所,代碼(1)如果當(dāng)前線程不是隊(duì)首或者當(dāng)前鎖已經(jīng)被其他線程獲取,則調(diào)用park方法掛起自己。
接著代碼(2)做判斷,如果park方法是因?yàn)楸恢袛喽祷兀瑒t忽略中斷,并且重置中斷標(biāo)志,只做個(gè)標(biāo)記,然后再次判斷當(dāng)前線程是不是隊(duì)首元素或者當(dāng)先鎖是否已經(jīng)被其他線程獲取,如果是則繼續(xù)調(diào)用park方法掛起自己。
然后代碼(3)中如果標(biāo)記為true 則中斷該線程,這個(gè)怎么理解呢?其實(shí)就是其他線程中斷了該線程,雖然我對(duì)中斷信號(hào)不感興趣,忽略它,但是不代表其他線程對(duì)該標(biāo)志不感興趣,所以要恢復(fù)下。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Java封裝數(shù)組之改進(jìn)為泛型數(shù)組操作詳解
這篇文章主要介紹了Java封裝數(shù)組之改進(jìn)為泛型數(shù)組操作,結(jié)合實(shí)例形式詳細(xì)分析了Java封裝數(shù)組為泛型數(shù)組相關(guān)原理、操作技巧與注意事項(xiàng),需要的朋友可以參考下2020-03-03
springboot組件初始化后的4種啟動(dòng)方式及常用方法
在Spring Boot中,您可以通過幾種方式在組件初始化后執(zhí)行啟動(dòng)任務(wù),下面小編給大家分享springboot組件初始化后的4種啟動(dòng)方式及常用方法,感興趣的朋友一起看看吧2024-06-06
spring kafka框架中@KafkaListener 注解解讀和使用案例
Kafka 目前主要作為一個(gè)分布式的發(fā)布訂閱式的消息系統(tǒng)使用,也是目前最流行的消息隊(duì)列系統(tǒng)之一,這篇文章主要介紹了kafka @KafkaListener 注解解讀,需要的朋友可以參考下2023-02-02
SpringBoot中API接口參數(shù)獲取方式小結(jié)
在Spring Boot中,API接口參數(shù)可以通過多種方式獲取,具體取決于你定義的API接口參數(shù)類型(如路徑參數(shù)、查詢參數(shù)、請(qǐng)求體參數(shù)、請(qǐng)求頭等),本文給大家就介紹了一些常見的參數(shù)獲取方式,需要的朋友可以參考下2024-06-06
java實(shí)現(xiàn)多線程的兩種方式繼承Thread類和實(shí)現(xiàn)Runnable接口的方法
下面小編就為大家?guī)?lái)一篇java實(shí)現(xiàn)多線程的兩種方式繼承Thread類和實(shí)現(xiàn)Runnable接口的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2016-09-09
java 動(dòng)態(tài)加載的實(shí)現(xiàn)代碼
這篇文章主要介紹了java 動(dòng)態(tài)加載的實(shí)現(xiàn)代碼的相關(guān)資料,Java動(dòng)態(tài)加載類主要是為了不改變主程序代碼,通過修改配置文件就可以操作不同的對(duì)象執(zhí)行不同的功能,需要的朋友可以參考下2017-07-07
springboot攔截器過濾token,并返回結(jié)果及異常處理操作
這篇文章主要介紹了springboot攔截器過濾token,并返回結(jié)果及異常處理操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2020-09-09

