最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java多線程父線程向子線程傳值問題及解決

 更新時間:2025年02月14日 11:29:25   作者:趙廣陸  
文章總結(jié)了5種解決父子之間數(shù)據(jù)傳遞困擾的解決方案,包括ThreadLocal+TaskDecorator、UserUtils、CustomTaskDecorator、ExecutorConfig、RequestContextHolder+TaskDecorator、MDC+TaskDecorator和InheritableThreadLocal

1 背景

在實際開發(fā)過程中我們需要父子之間傳遞一些數(shù)據(jù),比如用戶信息,日志異步生成數(shù)據(jù)傳遞等,該文章從5種解決方案解決父子之間數(shù)據(jù)傳遞困擾

2 ThreadLocal+TaskDecorator

用戶工具類 UserUtils

/**
 *使用ThreadLocal存儲共享的數(shù)據(jù)變量,如登錄的用戶信息
 */
public class UserUtils {
    private static  final  ThreadLocal<String> userLocal=new ThreadLocal<>();
 
    public static  String getUserId(){
        return userLocal.get();
    }
    public static void setUserId(String userId){
        userLocal.set(userId);
    }
 
    public static void clear(){
        userLocal.remove();
    }
 
}

自定義CustomTaskDecorator

/**
 * 線程池修飾類
 */
public class CustomTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        // 獲取主線程中的請求信息(我們的用戶信息也放在里面)
        String robotId = UserUtils.getUserId();
        System.out.println(robotId);
        return () -> {
            try {
                // 將主線程的請求信息,設(shè)置到子線程中
                UserUtils.setUserId(robotId);
                // 執(zhí)行子線程,這一步不要忘了
                runnable.run();
            } finally {
                // 線程結(jié)束,清空這些信息,否則可能造成內(nèi)存泄漏
                UserUtils.clear();
            }
        };
    }
}

ExecutorConfig

在原來的基礎(chǔ)上增加 executor.setTaskDecorator(new CustomTaskDecorator());

@Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
        log.info("start asyncServiceExecutor----------------");
        //ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //使用可視化運行狀態(tài)的線程池
        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //配置核心線程數(shù)
        executor.setCorePoolSize(corePoolSize);
        //配置最大線程數(shù)
        executor.setMaxPoolSize(maxPoolSize);
        //配置隊列大小
        executor.setQueueCapacity(queueCapacity);
        //配置線程池中的線程的名稱前綴
        executor.setThreadNamePrefix(namePrefix);
 
        // rejection-policy:當(dāng)pool已經(jīng)達(dá)到max size的時候,如何處理新任務(wù)
        // CALLER_RUNS:不在新線程中執(zhí)行任務(wù),而是有調(diào)用者所在的線程來執(zhí)行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
 
        //增加線程池修飾類
        executor.setTaskDecorator(new CustomTaskDecorator());
        //增加MDC的線程池修飾類
        //executor.setTaskDecorator(new MDCTaskDecorator());
        //執(zhí)行初始化
        executor.initialize();
        log.info("end asyncServiceExecutor------------");
        return executor;
    }

AsyncServiceImpl

    /**
     * 使用ThreadLocal方式傳遞
     * 帶有返回值
     * @throws InterruptedException
     */
    @Async("asyncServiceExecutor")
    public CompletableFuture<String> executeValueAsync2() throws InterruptedException {
        log.info("start executeValueAsync");
        System.out.println("異步線程執(zhí)行返回結(jié)果......+");
        log.info("end executeValueAsync");
        return CompletableFuture.completedFuture(UserUtils.getUserId());
    }
 

Test2Controller

    /**
     * 使用ThreadLocal+TaskDecorator的方式
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     */
    @GetMapping("/test2")
    public String test2() throws InterruptedException, ExecutionException {
        UserUtils.setUserId("123456");
        CompletableFuture<String> completableFuture = asyncService.executeValueAsync2();
        String s = completableFuture.get();
        return s;
    }

3 RequestContextHolder+TaskDecorator

自定義CustomTaskDecorator

/**
 * 線程池修飾類
 */
public class CustomTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        // 獲取主線程中的請求信息(我們的用戶信息也放在里面)
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        return () -> {
            try {
                // 將主線程的請求信息,設(shè)置到子線程中
                RequestContextHolder.setRequestAttributes(attributes);
                // 執(zhí)行子線程,這一步不要忘了
                runnable.run();
            } finally {
                // 線程結(jié)束,清空這些信息,否則可能造成內(nèi)存泄漏
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }
}

ExecutorConfig

在原來的基礎(chǔ)上增加 executor.setTaskDecorator(new CustomTaskDecorator());

@Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
        log.info("start asyncServiceExecutor----------------");
        //ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //使用可視化運行狀態(tài)的線程池
        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //配置核心線程數(shù)
        executor.setCorePoolSize(corePoolSize);
        //配置最大線程數(shù)
        executor.setMaxPoolSize(maxPoolSize);
        //配置隊列大小
        executor.setQueueCapacity(queueCapacity);
        //配置線程池中的線程的名稱前綴
        executor.setThreadNamePrefix(namePrefix);
 
        // rejection-policy:當(dāng)pool已經(jīng)達(dá)到max size的時候,如何處理新任務(wù)
        // CALLER_RUNS:不在新線程中執(zhí)行任務(wù),而是有調(diào)用者所在的線程來執(zhí)行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
 
        //增加線程池修飾類
        executor.setTaskDecorator(new CustomTaskDecorator());
        //增加MDC的線程池修飾類
        //executor.setTaskDecorator(new MDCTaskDecorator());
        //執(zhí)行初始化
        executor.initialize();
        log.info("end asyncServiceExecutor------------");
        return executor;
    }

AsyncServiceImpl

     /**
     * 使用RequestAttributes獲取主線程傳遞的數(shù)據(jù)
     * @return
     * @throws InterruptedException
     */
    @Async("asyncServiceExecutor")
    public CompletableFuture<String> executeValueAsync3() throws InterruptedException {
        log.info("start executeValueAsync");
        System.out.println("異步線程執(zhí)行返回結(jié)果......+");
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        Object userId = attributes.getAttribute("userId", 0);
        log.info("end executeValueAsync");
        return CompletableFuture.completedFuture(userId.toString());
    }

Test2Controller

    /**
     * RequestContextHolder+TaskDecorator的方式
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     */
    @GetMapping("/test3")
    public String test3() throws InterruptedException, ExecutionException {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        attributes.setAttribute("userId","123456",0);
        CompletableFuture<String> completableFuture = asyncService.executeValueAsync3();
        String s = completableFuture.get();
        return s;
    }

4 MDC+TaskDecorator

自定義MDCTaskDecorator

/**
 * 線程池修飾類
 */
public class MDCTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        // 獲取主線程中的請求信息(我們的用戶信息也放在里面)
        String userId = MDC.get("userId");
        Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();
        System.out.println(copyOfContextMap);
        return () -> {
            try {
                // 將主線程的請求信息,設(shè)置到子線程中
                MDC.put("userId",userId);
                // 執(zhí)行子線程,這一步不要忘了
                runnable.run();
            } finally {
                // 線程結(jié)束,清空這些信息,否則可能造成內(nèi)存泄漏
                MDC.clear();
            }
        };
    }
}

ExecutorConfig

在原來的基礎(chǔ)上增加 executor.setTaskDecorator(new MDCTaskDecorator());

@Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
        log.info("start asyncServiceExecutor----------------");
        //ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //使用可視化運行狀態(tài)的線程池
        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //配置核心線程數(shù)
        executor.setCorePoolSize(corePoolSize);
        //配置最大線程數(shù)
        executor.setMaxPoolSize(maxPoolSize);
        //配置隊列大小
        executor.setQueueCapacity(queueCapacity);
        //配置線程池中的線程的名稱前綴
        executor.setThreadNamePrefix(namePrefix);
 
        // rejection-policy:當(dāng)pool已經(jīng)達(dá)到max size的時候,如何處理新任務(wù)
        // CALLER_RUNS:不在新線程中執(zhí)行任務(wù),而是有調(diào)用者所在的線程來執(zhí)行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
 
        //增加MDC的線程池修飾類
        executor.setTaskDecorator(new MDCTaskDecorator());
        //執(zhí)行初始化
        executor.initialize();
        log.info("end asyncServiceExecutor------------");
        return executor;
    }

AsyncServiceImpl

         /**
     * 使用MDC獲取主線程傳遞的數(shù)據(jù)
     * @return
     * @throws InterruptedException
     */
    @Async("asyncServiceExecutor")
    public CompletableFuture<String> executeValueAsync5() throws InterruptedException {
        log.info("start executeValueAsync");
        System.out.println("異步線程執(zhí)行返回結(jié)果......+");
        log.info("end executeValueAsync");
        return CompletableFuture.completedFuture(MDC.get("userId"));
    }

Test2Controller

     /**
     * 使用MDC+TaskDecorator方式
     * 本質(zhì)也是ThreadLocal+TaskDecorator方式
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     */
    @GetMapping("/test5")
    public String test5() throws InterruptedException, ExecutionException {
        MDC.put("userId","123456");
        CompletableFuture<String> completableFuture = asyncService.executeValueAsync5();
        String s = completableFuture.get();
        return s;
    }

5 InheritableThreadLocal

測試代碼

public class TestThreadLocal {
	public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
	public static void main(String[] args) {
		 //設(shè)置線程變量
        threadLocal.set("hello world");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run( ) {
                //子線程輸出線程變量的值
                System.out.println("thread:"+threadLocal.get());
            }
        });
        thread.start();
        // 主線程輸出線程變量的值
        System.out.println("main:"+threadLocal.get());
	}
}

輸出結(jié)果:

main:hello world

thread:null

從上面結(jié)果可以看出:同一個ThreadLocal變量在父線程中被設(shè)置后,在子線程中是獲取不到的;

原因在子線程thread里面調(diào)用get方法時當(dāng)前線程為thread線程,而這里調(diào)用set方法設(shè)置線程變量的是main線程,兩者是不同的線程,自然子線程訪問時返回null

為了解決上面的問題,InheritableThreadLocal應(yīng)運而生,InheritableThreadLocal繼承ThreadLocal,其提供一個特性,就是讓子線程可以訪問在父線程中設(shè)置的本地變量

將上面測試代碼用InheritableThreadLocal修改

public class TestInheritableThreadLocal {
	
	public static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
	
	public static void main(String[] args) {
		 //設(shè)置線程變量
        threadLocal.set("hello world");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run( ) {
                //子線程輸出線程變量的值
                System.out.println("thread:"+threadLocal.get());
            }
        });
        thread.start();
        // 主線程輸出線程變量的值
        System.out.println("main:"+threadLocal.get());
	}
}

輸出結(jié)果:

main:hello world

thread:hello world

5.1 源碼分析

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

InheritableThreadLocal 重寫了childValue,getMap,createMap三個方法

在InheritableThreadLocal中,變量inheritableThreadLocals 替代了threadLocals;

那么如何讓子線程可以訪問父線程的本地變量。這要從創(chuàng)建Thread的代碼說起,打開Thread類的默認(rèn)構(gòu)造方法,代碼如下:

  public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name;
        //獲取當(dāng)前線程
        Thread parent = currentThread();
       //如果父線程的 inheritableThreadLocals變量不為null
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        //設(shè)置子線程inheritThreadLocals變量
            this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;
        /* Set thread ID */
        tid = nextThreadID();
    }

我們看下createInheritedMap代碼:

this.inheritableThreadLocals =            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

在createInheritedMap內(nèi)部使用父線程的inheritableThreadLocals變量作為構(gòu)造方法創(chuàng)建了一個新的ThreadLocalMap變量,然后賦值給子線程的inheritableThreadLocals變量。

下面看看ThreadLocalMap的構(gòu)造函數(shù)內(nèi)部做了什么事情;

private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];
            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

InheritableThreadLocal 類通過重寫下面代碼

 ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

讓本地變量保存到了具體的線程的inheritableThreadLocals變量里面,那么線程在通過InheritableThreadLocal類實例的set或者get方法設(shè)置變量時,就會創(chuàng)建當(dāng)前線程的inheritableThreadLocals變量。

當(dāng)父線程創(chuàng)建子線程時,構(gòu)造方法會把父線程中的inheritableThreadLocals變量里面的本地變量賦值一份保存到子線程的inheritableThreadLocals變量里面

5.2 InheritableThreadLocal存在的問題

雖然InheritableThreadLocal可以解決在子線程中獲取父線程的值的問題,但是在使用線程池的情況下,由于不同的任務(wù)有可能是同一個線程處理,因此這些任務(wù)取到的值有可能并不是父線程設(shè)置的值

測試目標(biāo):任務(wù)1和任務(wù)2 獲取父線程值一樣,為測試代碼中的hello world

測試代碼:

public class TestInheritableThreadLocaIssue {
	
	public static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
	public static ExecutorService executorService = Executors.newSingleThreadExecutor();
	
	public static void main(String[] args) throws Exception {
		 //設(shè)置線程變量
        threadLocal.set("hello world");
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run( ) {
                //子線程輸出線程變量的值
                System.out.println("thread:"+threadLocal.get());
                threadLocal.set("hello world 2");
            }
        },"task1");
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run( ) {
                //子線程輸出線程變量的值
                System.out.println("thread:"+threadLocal.get());
                threadLocal.set("hello world 2");
            }
        },"task2");
        executorService.submit(thread1).get();
        executorService.submit(thread2).get();
        
        // 主線程輸出線程變量的值
        System.out.println("main:"+threadLocal.get());
	}
}

輸出結(jié)果:

thread:hello world

thread:hello world 2

main:hello world

結(jié)果分析:

很明顯,任務(wù)2獲取的不是父線程設(shè)置的hello world ,而是線程1修改后的值。如果在線程池中使用,需要注意這種情況(可以備份備份父線程的值)

6 TransmittableThreadLocal

解決線程池化值傳遞

阿里封裝了一個工具,實現(xiàn)了在使用線程池等會池化復(fù)用線程的組件情況下,提供ThreadLocal值的傳遞功能,解決異步執(zhí)行時上下文傳遞的問題

JDK的InheritableThreadLocal類可以完成父線程到子線程的值傳遞。但對于使用線程池等會池化復(fù)用線程的執(zhí)行組件的情況,線程由線程池創(chuàng)建好,并且線程是池化起來反復(fù)使用的;

這時父子線程關(guān)系的ThreadLocal值傳遞已經(jīng)沒有意義,應(yīng)用需要的實際上是把 任務(wù)提交給線程池時的ThreadLocal值傳遞到 任務(wù)執(zhí)行時

https://github.com/alibaba/transmittable-thread-local

引入:

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

需求場景:

  • 1.分布式跟蹤系統(tǒng) 或 全鏈路壓測(即鏈路打標(biāo))
  • 2.日志收集記錄系統(tǒng)上下文
  • 3.Session級Cache
  • 4.應(yīng)用容器或上層框架跨應(yīng)用代碼給下層SDK傳遞信息

測試代碼:

1)父子線程信息傳遞

public static TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
	
	public static void main(String[] args) {
		 //設(shè)置線程變量
        threadLocal.set("hello world");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run( ) {
                //子線程輸出線程變量的值
                System.out.println("thread:"+threadLocal.get());
            }
        });
        thread.start();
        // 主線程輸出線程變量的值
        System.out.println("main:"+threadLocal.get());
	}
}

輸出結(jié)果:

main:hello world

thread:hello world

2)線程池中傳遞值,參考github:修飾線程池

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java利用TCP協(xié)議實現(xiàn)客戶端與服務(wù)器通信(附通信源碼)

    Java利用TCP協(xié)議實現(xiàn)客戶端與服務(wù)器通信(附通信源碼)

    這篇文章主要介紹了Java利用TCP協(xié)議實現(xiàn)客戶端與服務(wù)器通信(附通信源碼),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • 詳解備忘錄模式及其在Java設(shè)計模式編程中的實現(xiàn)

    詳解備忘錄模式及其在Java設(shè)計模式編程中的實現(xiàn)

    這篇文章主要介紹了詳解備忘錄模式及其在Java設(shè)計模式編程中的實現(xiàn),備忘錄模式數(shù)據(jù)的存儲過程中應(yīng)當(dāng)注意淺拷貝和深拷貝的問題,需要的朋友可以參考下
    2016-04-04
  • IDEA中為SpringBoot項目接入MySQL數(shù)據(jù)庫的詳細(xì)指南

    IDEA中為SpringBoot項目接入MySQL數(shù)據(jù)庫的詳細(xì)指南

    MySQL作為最流行的開源關(guān)系型數(shù)據(jù)庫,與Spring Boot的整合是企業(yè)級開發(fā)的標(biāo)配,本文將手把手教你?在IntelliJ IDEA中為Spring Boot項目接入MySQL數(shù)據(jù)庫?,有需要的可以了解下
    2025-05-05
  • 快速入手IntelliJ IDEA基本配置

    快速入手IntelliJ IDEA基本配置

    IntelliJ IDEA是java編程語言開發(fā)的集成環(huán)境,本篇主要介紹了對它的安裝、配置maven倉庫、調(diào)試方法、常用的插件推薦、快捷鍵大全與常用快捷鍵說明,感興趣的朋友一起看看吧
    2021-10-10
  • 使用@Value注解從配置文件中讀取數(shù)組

    使用@Value注解從配置文件中讀取數(shù)組

    這篇文章主要介紹了使用@Value注解從配置文件中讀取數(shù)組的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • JavaSwing BorderLayout 邊界布局的實現(xiàn)代碼

    JavaSwing BorderLayout 邊界布局的實現(xiàn)代碼

    這篇文章主要介紹了JavaSwing BorderLayout 邊界布局的實現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • java中l(wèi)ist.forEach()和list.stream().forEach()區(qū)別

    java中l(wèi)ist.forEach()和list.stream().forEach()區(qū)別

    這篇文章主要介紹了java中l(wèi)ist.forEach()和list.stream().forEach()區(qū)別,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Java JTable 實現(xiàn)日歷的示例

    Java JTable 實現(xiàn)日歷的示例

    這篇文章主要介紹了Java JTable 實現(xiàn)日歷的示例,幫助大家更好的理解和學(xué)習(xí)Java jtable的使用方法,感興趣的朋友可以了解下
    2020-10-10
  • java數(shù)據(jù)結(jié)構(gòu)循環(huán)隊列的空滿判斷及長度計算

    java數(shù)據(jù)結(jié)構(gòu)循環(huán)隊列的空滿判斷及長度計算

    這篇文章主要為大家介紹了java數(shù)據(jù)結(jié)構(gòu)循環(huán)隊列的空滿判斷及長度計算,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • Springboot項目對數(shù)據(jù)庫用戶名密碼實現(xiàn)加密過程解析

    Springboot項目對數(shù)據(jù)庫用戶名密碼實現(xiàn)加密過程解析

    這篇文章主要介紹了Springboot項目對數(shù)據(jù)庫用戶名密碼實現(xiàn)加密過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-06-06

最新評論

临沧市| 余江县| 林甸县| 鄂尔多斯市| 莎车县| 开远市| 静宁县| 监利县| 新和县| 东明县| 临海市| 阳新县| 澜沧| 安平县| 东辽县| 沛县| 界首市| 宁武县| 麦盖提县| 新宁县| 乐清市| 家居| 手游| 突泉县| 呼和浩特市| 荆门市| 韩城市| 集安市| 桦甸市| 潍坊市| 宁陵县| 泰和县| 遂平县| 玉溪市| 武宣县| 夏河县| 天镇县| 张家口市| 巴林左旗| 西乌珠穆沁旗| 广丰县|