欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java線程之間數(shù)據(jù)傳遞的實(shí)現(xiàn)示例(4種)

 更新時(shí)間:2023年08月06日 09:00:48   作者:立瑩Sir  
我們經(jīng)常會(huì)遇到父子線程數(shù)據(jù)傳遞(非調(diào)用參數(shù))的場(chǎng)景,本文主要介紹了Java線程之間數(shù)據(jù)傳遞的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

在業(yè)務(wù)系統(tǒng)的開發(fā)過程中,我們經(jīng)常會(huì)遇到父子線程數(shù)據(jù)傳遞(非調(diào)用參數(shù))的場(chǎng)景,如:登陸信息,調(diào)用者信息,TraceId的傳遞等業(yè)務(wù)場(chǎng)景,固總結(jié)4中方式進(jìn)行線程之間數(shù)據(jù)傳遞。

ThreadLocal

代碼如下:

public class TtlParameterWrapper {
? ? private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
? ? private TtlParameterWrapper() {
? ? }
? ? public static String getCaller() {
? ? ? ? return THREAD_LOCAL.get();
? ? }
? ? public static void setCaller(String caller) {
? ? ? ?THREAD_LOCAL.set(caller);
? ? }
? ? public static void clear() {
? ? ? ?THREAD_LOCAL.remove();
? ? }
}

那么子線程想要獲取這個(gè)TtlParameterWrapper如何做呢?

  • 獲取父線程的TtlParameterWrapper
  • 將TtlParameterWrapper設(shè)置到子線程,達(dá)到復(fù)用
public void handler(){
        // 1. 獲取父線程
       TtlParameterWrapper.setCaller("caller path");
       log.info("父線程的值 ->{}",TtlParameterWrapper.get());
       CompletableFuture.runAsync(()->{
            // 2. 設(shè)置子線程的值,復(fù)用
          TtlParameterWrapper.setCaller("caller path");
          log.info("子線程的值 ->{}", TtlParameterWrapper.getCaller());
        });
    }

總結(jié)

雖然最終達(dá)成了傳遞的目的,但是每次開異步線程都需要手動(dòng)設(shè)置,代碼冗余繁雜,如果不這樣設(shè)置則無(wú)法跨線程進(jìn)行傳遞;如果手動(dòng)設(shè)置,將無(wú)法進(jìn)行線程間進(jìn)行傳遞,因?yàn)門headLocal中的數(shù)據(jù)無(wú)法進(jìn)行線程間進(jìn)行傳遞。

InheritableThreadLocal

這種方案不建議使用,InheritableThreadLocal雖然能夠?qū)崿F(xiàn)父子線程間的復(fù)用,但是在線程池中使用會(huì)存在失敗的問題,原因:InheritableThreadLocal 在父線程創(chuàng)建子線程的時(shí)候,會(huì)將父線程中InheritableThreadLocal中存儲(chǔ)的數(shù)據(jù) 拷貝一份存儲(chǔ)到子線程的 InheritableThreadLocal中,但是在web的容器中使用了線程池,線程會(huì)被創(chuàng)建回收重復(fù)的利用,不會(huì)被銷毀重新創(chuàng)建,所以會(huì)存在實(shí)效的場(chǎng)景。

這種方案使用也是非常簡(jiǎn)單,直接用InheritableThreadLocal替換ThreadLocal即可。

代碼如下:

public class TtlParameterWrapper {
? ? private static ?final ?InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
? ? public static String getCaller(){
? ? ? ? return inheritableThreadLocal.get();
? ? }
? ? public static void setCaller(LoginVal loginVal){
? ? ? ?inheritableThreadLocal.set(loginVal);
? ? }
? ? public static void clear(){
? ? ? ?inheritableThreadLocal.remove();
? ? }
}

TransmittableThreadLocal

TransmittableThreadLocal是阿里開源的工具,解決了InheritableThreadLocal不能進(jìn)行線程池間傳遞數(shù)據(jù)的缺陷,在使用線程池等會(huì)池化復(fù)用線程的執(zhí)行組件情況下,提供ThreadLocal值的傳遞功能,解決異步執(zhí)行時(shí)上下文傳遞的問題。

添加依賴

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.14.2</version>
</dependency>

TtlParameterWrapper改造

public class TtlParameterWrapper {
    private static final TransmittableThreadLocal<String> TRANSMITTABLE_THREAD_LOCAL = new TransmittableThreadLocal<>();
    private TtlParameterWrapper() {
    }
    public static String getCaller() {
        return TRANSMITTABLE_THREAD_LOCAL.get();
    }
    public static void setCaller(String caller) {
       TRANSMITTABLE_THREAD_LOCAL.set(caller);
    }
    public static void clear() {
        TRANSMITTABLE_THREAD_LOCAL.remove();
    }
}

原理

從定義來(lái)看,TransimittableThreadLocal繼承于InheritableThreadLocal,并實(shí)現(xiàn)TtlCopier接口,它里面只有一個(gè)copy方法。所以主要是對(duì)InheritableThreadLocal的擴(kuò)展。

public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T> implements TtlCopier<T>

在TransimittableThreadLocal中添加holder屬性。這個(gè)屬性的作用就是被標(biāo)記為具備線程傳遞資格的對(duì)象都會(huì)被添加到這個(gè)對(duì)象中。
要標(biāo)記一個(gè)類,比較容易想到的方式,就是給這個(gè)類新增一個(gè)Type字段,還有一個(gè)方法就是將具備這種類型的的對(duì)象都添加到一個(gè)靜態(tài)全局集合中。之后使用時(shí),這個(gè)集合里的所有值都具備這個(gè)標(biāo)記。

//1. holder本身是一個(gè)InheritableThreadLocal對(duì)象
//2. 這個(gè)holder對(duì)象的value是WeakHashMap<TransmittableThreadLocal<Object>,?>
// ? 2.1WeekHashMap的value總是null,且不可能被使用。
// ? ?2.2WeekHasshMap支持value=null
private static InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>,?>> holder = new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>,?>>(){
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
?? ?return new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
}
/**
?* 重寫了childValue方法,實(shí)現(xiàn)上直接將父線程的屬性作為子線程的本地變量對(duì)象。 ??
?*/
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?>parentValue){
?? ?return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue);
}};

應(yīng)用代碼是通過TtlExecutors工具類對(duì)線程池對(duì)象進(jìn)行包裝。工具類只是簡(jiǎn)單的判斷,輸入的線程池是否已經(jīng)被包裝過、非空校驗(yàn)等,然后返回包裝類ExecutorServiceTtlWrapper。根據(jù)不同的線程池類型,有不同和的包裝類。

@Nullable
public static ExecutorServicegetTtlExecutorService(@Nullable ExecutorService executorService){
    if (TtlAgent.isTtlAgentLoaded() || executorService == null || executorService instanceof TtlEnhanced){
        return executorService;
    }
    return new ExecutorServiceTtlWrapper(executorService);
}

進(jìn)入包裝類ExecutorServiceTtlWrapper。可以注意到不論是通過ExecutorServiceTtlWrapper#submit方法或者是ExecutorTtlWrapper#execute方法,都會(huì)將線程對(duì)象包裝成TtlCallable或者TtlRunnable,用于在真正執(zhí)行run方法前做一些業(yè)務(wù)邏輯。

/** 
 * 在ExecutorServiceTtlWrapper實(shí)現(xiàn)submit方法
 */
@NonNull
@Override
public <T> Future<T> submit(@NonNull Callable<T> task){
    return executorService.submit(TtlCallable.get(task));
}
/** 
 * 在ExecutorTtlWrapper實(shí)現(xiàn)execute方法
 */
@Override
public void execute(@NonNull Runnable command){
    executor.execute(TtlRunnable.get(command));
}

重點(diǎn)的核心邏輯應(yīng)該是在TtlCallable#call()或者TtlRunnable#run()中。以下以TtlCallable為例,TtlRunnable同理類似。在分析call()方法之前,先看一個(gè)類Transmitter。

public static class Transmitter {
    /**    
     * 捕獲當(dāng)前線程中的是所有TransimittableThreadLocal和注冊(cè)ThreadLocal的值。    
     */
    @NonNull
    public static Object capture(){
        return new Snapshot(captureTtlValues(), captureThreadLocalValues());
    }
    /**    
     * 捕獲TransimittableThreadLocal的值,將holder中的所有值都添加到HashMap后返回。    
     */
    private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
        HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
        for(TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
            ttl2Value.put(threadLocal, threadLocal.copyValue());
        }
        return ttl2Value;
    }
    /**    
     * 捕獲注冊(cè)的ThreadLocal的值,也就是原本線程中的ThreadLocal,可以注冊(cè)到TTL中,在    
     * 進(jìn)行線程池本地變量傳遞時(shí)也會(huì)被傳遞。   
     */
    private static HashMap<ThreadLocal<Object>, Object> captureThreadLocalValues() {
        final HashMap<ThreadLocal<Object>, Object> threadLocal2Value = new HashMap<ThreadLocal<Object>, Object>();
        for(Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) {
            final ThreadLocal<Object> threadLocal = entry.getKey();
            final TtlCopier<Object> copier = entry.getValue();
            threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get()));
        }
        return threadLocal2Value;
    }
    /**    
     * 將捕獲到的本地變量進(jìn)行替換子線程的本地變量,并且返回子線程現(xiàn)有的本地變量副本backup。   
     * 用于在執(zhí)行run/call方法之后,將本地變量副本恢復(fù)。    
     */
    @NonNull
    public static Object replay(@NonNull Object captured) {
        final Snapshot capturedSnapshot = (Snapshot) captured;
        return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value));
    }
    /**    
     * 替換TransmittableThreadLocal    
     */
    @NonNull
    private static HashMap<TransmittableThreadLocal<Object>, Object> replayTtlValues(@NonNull HashMap<TransmittableThreadLocal<Object>,Object> captured) {
        // 創(chuàng)建副本backup
        HashMap<TransmittableThreadLocal<Object>, Object> backup = new HashMap<TransmittableThreadLocal<Object>, Object>();
        for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext();) {
            TransmittableThreadLocal<Object> threadLocal = iterator.next();
            // 對(duì)當(dāng)前線程的本地變量進(jìn)行副本拷貝
            backup.put(threadLocal, threadLocal.get());
            // 若出現(xiàn)調(diào)用線程中不存在某個(gè)線程變量,而線程池中線程有,則刪除線程池中對(duì)應(yīng)的本地變量
            if (!captured.containsKey(threadLocal)){
                iterator.remove();
                threadLocal.superRemove();
            }
        }
        // 將捕獲的TTL值打入線程池獲取到的線程TTL中。
        setTtlValuesTo(captured);
        // 是一個(gè)擴(kuò)展點(diǎn),調(diào)用TTL的beforeExecute方法。默認(rèn)實(shí)現(xiàn)為空
        doExecuteCallback(true);
        return backup;
    }
    private static HashMap<ThreadLocal<Object>, Object> replayThreadLocalValues(@NonNull HashMap<ThreadLocal<Object>, Object> captured) {
        final HashMap<ThreadLocal<Object>, Object> backup = new HashMap<ThreadLocal<Object>, Object>();
        for (Map.Entry<ThreadLocal<Object>, Object> entry : captured.entrySet()) {
            final ThreadLocal<Object> threadLocal = entry.getKey();
            backup.put(threadLocal, threadLocal.get());
            final Objectvalue = entry.getValue();
            if (value == threadLocalClearMark) threadLocal.remove();
            else threadLocal.set(value);
        }
        return backup;
    }
    /**    
     * 清除單線線程的所有TTL和TL,并返回清除之氣的backup    
     */
    @NonNull
    public static Object clear() {
        final HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
        final HashMap<ThreadLocal<Object>, Object> threadLocal2Value = new HashMap<ThreadLocal<Object>, Object>();
        for (Map.Entry<ThreadLocal<Object>,TtlCopier<Object>>entry : threadLocalHolder.entrySet()) {
            final ThreadLocal<Object> threadLocal = entry.getKey();
            threadLocal2Value.put(threadLocal, threadLocalClearMark);
        }
        return replay(new Snapshot(ttl2Value, threadLocal2Value));
    }
    /**    
     * 還原    
     */
    public static void restore(@NonNull Object backup) {
        final Snapshot backupSnapshot = (Snapshot) backup;
        restoreTtlValues(backupSnapshot.ttl2Value);
        restoreThreadLocalValues(backupSnapshot.threadLocal2Value);
    }
    private static void restoreTtlValues(@NonNull HashMap<TransmittableThreadLocal<Object>,Object> backup) {
        // 擴(kuò)展點(diǎn),調(diào)用TTL的afterExecute
        doExecuteCallback(false);
        for (finalIterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext();) {
            TransmittableThreadLocal<Object> threadLocal = iterator.next();
            if (!backup.containsKey(threadLocal)) {
                iterator.remove();
                threadLocal.superRemove();
            }
        }
        // 將本地變量恢復(fù)成備份版本
        setTtlValuesTo(backup);
    }
    private static void setTtlValuesTo(@NonNull HashMap<TransmittableThreadLocal<Object>,Object> ttlValues) {
        for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry :ttlValues.entrySet()) {
            TransmittableThreadLocal<Object> threadLocal = entry.getKey();
            threadLocal.set(entry.getValue());
        }
    }
    private staticvoid restoreThreadLocalValues(@NonNull HashMap<ThreadLocal<Object>,Object> backup) {
        for (Map.Entry<ThreadLocal<Object>, Object> entry : backup.entrySet()) {
            final ThreadLocal<Object> threadLocal = entry.getKey();
            threadLocal.set(entry.getValue());
        }
    }
    /**   
     * 快照類,保存TTL和TL   
     */
    private static class Snapshot {
        final HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value;
        final HashMap<ThreadLocal<Object>, Object> threadLocal2Value;
        private Snapshot(HashMap<TransmittableThreadLocal<Object>, Object>ttl2Value, HashMap<ThreadLocal<Object>, Object> threadLocal2Value) {
            this.ttl2Value= ttl2Value;
            this.threadLocal2Value= threadLocal2Value;
        }
    }
    // 進(jìn)入TtlCallable#call()方法。
    @Override
    public V call() throws Exception {
        Object captured = capturedRef.get();
        if (captured == null|| releaseTtlValueReferenceAfterCall && !capturedRef.compareAndSet(captured, null)) {
            throw new IllegalStateException("TTLvalue reference is released after call!");
        }
        // 調(diào)用replay方法將捕獲到的當(dāng)前線程的本地變量,傳遞給線程池線程的本地變量,
        // 并且獲取到線程池線程覆蓋之前的本地變量副本。
        Object backup = replay(captured);
        try {
            // 線程方法調(diào)用
            return callable.call();
        } finally {
            // 使用副本進(jìn)行恢復(fù)。
            restore(backup);
        }
    }
}

線程池方式傳遞本地變量的核心代碼已經(jīng)完畢。總的來(lái)說(shuō)在創(chuàng)建TtlCallable對(duì)象是,調(diào)用capture()方法捕獲調(diào)用方的本地線程變量,在call()執(zhí)行時(shí),將捕獲到的線程變量,替換到線程池所對(duì)應(yīng)獲取到的線程的本地變量中,并且在執(zhí)行完成之后,將其本地變量恢復(fù)到調(diào)用之前。

TaskDecorator

線程池設(shè)置TaskDecorator,TaskDecorator是什么?

官方釋義:這是一個(gè)執(zhí)行回調(diào)方法的裝飾器,主要應(yīng)用于傳遞上下文,或者提供任務(wù)的監(jiān)控/統(tǒng)計(jì)信息。

代碼如下

public class ContextTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable){
        //獲取父線程的值
        String callerPath = TtlParameterWrapper.getCaller();
        return () -> {
            try {
                // 將主線程的請(qǐng)求信息,設(shè)置到子線程中
               TtlParameterWrapper.setCaller(callerPath);
                // 執(zhí)行子線程,這一步不要忘了
               runnable.run();
            } finally {
                // 線程結(jié)束,清空這些信息,否則可能造成內(nèi)存泄漏
               TtlParameterWrapper.clear();
            }
        };
    }
}

TaskDecorator需要結(jié)合線程池使用,實(shí)際開發(fā)中異步線程建議使用線程池,只需要在對(duì)應(yīng)的線程池配置一下

代碼

@Bean("taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
       ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
       poolTaskExecutor.setCorePoolSize(xx);
       poolTaskExecutor.setMaxPoolSize(xx);
        // 設(shè)置線程活躍時(shí)間(秒)
       poolTaskExecutor.setKeepAliveSeconds(xx);
        // 設(shè)置隊(duì)列容量
       poolTaskExecutor.setQueueCapacity(xx);
        //設(shè)置TaskDecorator,用于解決父子線程間的數(shù)據(jù)復(fù)用
       poolTaskExecutor.setTaskDecorator(new ContextTaskDecorator());
       poolTaskExecutor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任務(wù)結(jié)束后再關(guān)閉線程池
       poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
       return poolTaskExecutor;
}

此時(shí)業(yè)務(wù)代碼就不需要去設(shè)置子線程的值,直接使用即可

代碼

public void handlerAsync() {
       log.info("父線程的用戶信息 -> {}", TtlParameterWrapper.get());
        //執(zhí)行異步任務(wù),需要指定的線程池
       CompletableFuture.runAsync(() -> 
           log.info("子線程的用戶信息 -> {}", TtlParameterWrapper.get()
       ),taskExecutor);
}

這里使用的是CompletableFuture執(zhí)行異步任務(wù),使用@Async這個(gè)注解同樣是可行的。

注意:無(wú)論使用何種方式,都需要指定線程池

到此這篇關(guān)于Java線程之間數(shù)據(jù)傳遞的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)Java線程之間數(shù)據(jù)傳遞內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • dubbo自定義異常的完整步驟與測(cè)試

    dubbo自定義異常的完整步驟與測(cè)試

    最近在項(xiàng)目上遇到一個(gè)有關(guān)dubbo的問題,想著給大家總結(jié)下,這篇文章主要給大家介紹了關(guān)于dubbo自定義異常的完整步驟與測(cè)試的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-06-06
  • JDK源碼之線程并發(fā)協(xié)調(diào)神器CountDownLatch和CyclicBarrier詳解

    JDK源碼之線程并發(fā)協(xié)調(diào)神器CountDownLatch和CyclicBarrier詳解

    我一直認(rèn)為程序是對(duì)于現(xiàn)實(shí)世界的邏輯描述,而在現(xiàn)實(shí)世界中很多事情都需要各方協(xié)調(diào)合作才能完成,就好比完成一個(gè)平臺(tái)的交付不可能只靠一個(gè)人,而需要研發(fā)、測(cè)試、產(chǎn)品以及項(xiàng)目經(jīng)理等不同角色人員進(jìn)行通力合作才能完成最終的交付
    2022-02-02
  • 使用HandlerMethodArgumentResolver用于統(tǒng)一獲取當(dāng)前登錄用戶

    使用HandlerMethodArgumentResolver用于統(tǒng)一獲取當(dāng)前登錄用戶

    這篇文章主要介紹了使用HandlerMethodArgumentResolver用于統(tǒng)一獲取當(dāng)前登錄用戶實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • IDEA集成git和使用步驟的實(shí)現(xiàn)方法

    IDEA集成git和使用步驟的實(shí)現(xiàn)方法

    這篇文章主要介紹了IDEA集成git和使用步驟的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • SpringBoot?SpringSecurity?詳細(xì)介紹(基于內(nèi)存的驗(yàn)證)

    SpringBoot?SpringSecurity?詳細(xì)介紹(基于內(nèi)存的驗(yàn)證)

    這篇文章主要介紹了SpringBoot?SpringSecurity?介紹(基于內(nèi)存的驗(yàn)證),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-04-04
  • Java利用Zxing生成二維碼的簡(jiǎn)單實(shí)例

    Java利用Zxing生成二維碼的簡(jiǎn)單實(shí)例

    下面小編就為大家?guī)?lái)一篇Java利用Zxing生成二維碼的簡(jiǎn)單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧
    2016-08-08
  • 一文精通Java中的volatile關(guān)鍵字

    一文精通Java中的volatile關(guān)鍵字

    volatile是java中的關(guān)鍵詞之一,這篇文章主要給大家介紹了關(guān)于Java中volatile關(guān)鍵字的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • SpringBoot+JWT實(shí)現(xiàn)單點(diǎn)登錄完美解決方案

    SpringBoot+JWT實(shí)現(xiàn)單點(diǎn)登錄完美解決方案

    單點(diǎn)登錄是一種統(tǒng)一認(rèn)證和授權(quán)機(jī)制,指在多個(gè)應(yīng)用系統(tǒng)中,用戶只需要登錄一次就可以訪問所有相互信任的系統(tǒng),不需要重新登錄驗(yàn)證,這篇文章主要介紹了SpringBoot+JWT實(shí)現(xiàn)單點(diǎn)登錄解決方案,需要的朋友可以參考下
    2023-07-07
  • java實(shí)現(xiàn)計(jì)算周期性提醒的示例

    java實(shí)現(xiàn)計(jì)算周期性提醒的示例

    本文分享一個(gè)java實(shí)現(xiàn)計(jì)算周期性提醒的示例,可以計(jì)算父親節(jié)、母親節(jié)這樣的節(jié)日,也可以定義如每月最好一個(gè)周五,以方便安排會(huì)議
    2014-04-04
  • Java正則表達(dá)式之Pattern和Matcher的使用

    Java正則表達(dá)式之Pattern和Matcher的使用

    本文詳細(xì)介紹了Java中處理正則表達(dá)式的Pattern和Matcher類的使用方法和實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-09-09

最新評(píng)論