真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

如何理解Spring單例

本篇內(nèi)容介紹了“如何理解Spring單例”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

創(chuàng)新互聯(lián)公司成都企業(yè)網(wǎng)站建設(shè)服務(wù),提供網(wǎng)站建設(shè)、網(wǎng)站制作網(wǎng)站開(kāi)發(fā),網(wǎng)站定制,建網(wǎng)站,網(wǎng)站搭建,網(wǎng)站設(shè)計(jì),響應(yīng)式網(wǎng)站開(kāi)發(fā),網(wǎng)頁(yè)設(shè)計(jì)師打造企業(yè)風(fēng)格網(wǎng)站,提供周到的售前咨詢(xún)和貼心的售后服務(wù)。歡迎咨詢(xún)做網(wǎng)站需要多少錢(qián):028-86922220

講點(diǎn)兒武德

這是由一個(gè)真實(shí)的 bug 引起的,bug 產(chǎn)生的原因就是忽略了 Spring Bean 的單例模式。來(lái),先看一段簡(jiǎn)單的代碼。

public class TestService {
    
    private String callback = "https://ip.com/token={token}";

    public String getCallback() {
        Random random = new Random();
        int number = random.nextInt(100);
        System.out.println("本次隨機(jī)數(shù)為:" + number);
        callback = callback.replace("{token}", String.valueOf(number));
        return callback;
    }


    public static void main(String[] args) {
        TestService testService = new TestService();
        while (true) {
            Scanner reader = new Scanner(System.in);
            int number = reader.nextInt();
            if (number > 0) {
                String url = testService.getCallback();
                System.out.println(url);
            }
        }
    }
}

callback是一個(gè)帶有一個(gè)回調(diào)地址,參數(shù) token是不確定的。

getCallback方法每次調(diào)用,會(huì)隨機(jī)生成一個(gè)100以?xún)?nèi)的數(shù)字,然后將 callback中的{token}替換為這個(gè)隨機(jī)數(shù)字,最后的格式就像這樣的:

https://ip.com/token=88

然后在 main方法中接收控制臺(tái)輸入,每次輸入的數(shù)字大于0,調(diào)用 getCallback方法,然后輸出 url。

相信各位都能輕易的看出這段程序的輸出。

如何理解Spring單例

執(zhí)行程序之后,不管你輸入多少次數(shù)字,最后輸出的 callback都是第一次的那個(gè)。

如何理解Spring單例

雖然每次生成的隨機(jī)數(shù)都變了,但是 callback沒(méi)變。

其實(shí)就是單例

有同學(xué)說(shuō),你過(guò)分了啊,這我能不知道為啥嗎?

main方法只創(chuàng)建了一個(gè)TestService實(shí)例,在第一次調(diào)用 getCallback方法的時(shí)候,callback這個(gè)字符串就被修改成 https://ip.com/token=89了,所以,之后不管你再調(diào)用多少次,都不會(huì)執(zhí)行 replace動(dòng)作了,因?yàn)?callback中已經(jīng)沒(méi)有 {token}這一段了。

TestService 在整個(gè)程序執(zhí)行過(guò)程中就是一個(gè)單例,所以,在 callback第一次被修改后,后面再執(zhí)行

callback.replace("{token}", String.valueOf(number));

的動(dòng)作,拿到的 callback中就已經(jīng)沒(méi)有 {token}了,所以說(shuō),不會(huì)有替換的動(dòng)作。

當(dāng)然,這只是用最簡(jiǎn)單的程序說(shuō)明單例中的這個(gè)問(wèn)題,真正的項(xiàng)目中想用單例的話(huà),還要借助于單例設(shè)計(jì)模式實(shí)現(xiàn)。

回到那個(gè) bug

有個(gè)弟弟在做微信服務(wù)號(hào)的開(kāi)發(fā),微信服務(wù)號(hào)或者訂閱號(hào)中有個(gè) access_token的概念,這是所有請(qǐng)求的憑證,有效期 2 個(gè)小時(shí),到期之前要進(jìn)行刷新。

他是這樣設(shè)計(jì)的,在項(xiàng)目啟動(dòng)的時(shí)候立即調(diào)用微信接口獲取 access_token,然后寫(xiě)了一個(gè)定時(shí)任務(wù)每1個(gè)小時(shí)刷新一次,獲取來(lái)的 access_token放到 redis 和 數(shù)據(jù)庫(kù)中,當(dāng)調(diào)用微信服務(wù)號(hào)其他接口的時(shí)候,在 redis 中獲取 access_token并拼接到接口地址中。

開(kāi)發(fā)調(diào)試的時(shí)候一起順利,看上去非常完美。

問(wèn)題出現(xiàn)了

當(dāng)項(xiàng)目部署到測(cè)試環(huán)境測(cè)試的時(shí)候,問(wèn)題出現(xiàn)了。項(xiàng)目剛發(fā)版的時(shí)候,測(cè)試都正常,但是過(guò)一段時(shí)間,就會(huì)出現(xiàn)錯(cuò)誤,查看日志的時(shí)候,發(fā)現(xiàn)是微信服務(wù)號(hào)的接口返回了錯(cuò)誤碼,意思就是 access_token已過(guò)期,需要重新獲取。

弟弟第一時(shí)間懷疑是定時(shí)任務(wù)出現(xiàn)了問(wèn)題,但是通過(guò)日志和數(shù)據(jù)庫(kù)中的更新時(shí)間,發(fā)現(xiàn)定時(shí)任務(wù)是完全沒(méi)有問(wèn)題的,刷新 access_token的時(shí)間和定時(shí)任務(wù)是完全吻合的,說(shuō)明已經(jīng)及時(shí)刷新了。

我讓他用 redis 或數(shù)據(jù)庫(kù)中的access_token去調(diào)一下服務(wù)號(hào)接口,看看是不是也有同樣的過(guò)期問(wèn)題。

結(jié)果一試,redis 中存的是沒(méi)問(wèn)題的,可以正常使用。

那徹底排除是定時(shí)任務(wù)的問(wèn)題了,問(wèn)題的癥結(jié)應(yīng)該就出在兩個(gè)地方:

1、在獲取 redis 中的access_token的過(guò)程;

2、將獲取到的 access_token拼接到請(qǐng)求接口 URL 上發(fā)生了錯(cuò)誤;

到這里就很好判斷了,他把從 redis 拿到的access_token和最后拼接好的 URL 都輸出到日志中一看,果然,兩個(gè)是不一致的。

從 redis 取出的確實(shí)是最新可用的 access_token ,但是拼接到接口 URL 上之后,發(fā)現(xiàn)是另外一個(gè)。那就確定是拿到的 access_token 是沒(méi)問(wèn)題的,但是最后拼接到 URL 卻有問(wèn)題。這時(shí),弟弟仔細(xì)檢查了代碼,然后徹底蒙了。

如何理解Spring單例

講點(diǎn)武德

既然問(wèn)題出在哪兒已經(jīng)確定了,那就分析那段代碼就好了。

項(xiàng)目整體采用的是 Spring Boot,代碼很簡(jiǎn)單,就是在一個(gè) Controller 中調(diào)用 Service 中的一個(gè)方法。大致 demo 是這樣的。

@RestController
@RequestMapping(value = "test")
public class TestController {

    @Autowired
    private TestService testService;

    @GetMapping(value = "call")
    public Object getCallback() {
        return testService.getCallback();
    }
}

@Service
public class TestService {

    private String callback = "https://ip.com/token={token}";

    public String getCallback() {
        Random random = new Random();
        int number = random.nextInt(100);
        System.out.println("本次隨機(jī)數(shù)為:" + number);
        callback = callback.replace("{token}", String.valueOf(number));
        return callback;
    }
}

看到這里,各位肯定已經(jīng)發(fā)現(xiàn)問(wèn)題原因了。雖然有多次請(qǐng)求,但因?yàn)?Spring Bean 默認(rèn)是單例模式,所以實(shí)際上和前面演示的那個(gè)控制臺(tái)程序是類(lèi)似的,從頭到尾都只有一個(gè) TestService 實(shí)例,所以只有第一次能將{token}替換成真正的access_token

對(duì)應(yīng)到實(shí)際的服務(wù)號(hào)場(chǎng)景中,在第一次調(diào)用這個(gè)接口時(shí),從 redis 拿到 access_token拼接到具體的 URL中是沒(méi)問(wèn)題的,但是一旦這個(gè)access_token過(guò)期(1小時(shí)后),再次請(qǐng)求這個(gè)接口就會(huì)出現(xiàn) access_token過(guò)期的問(wèn)題。

這里違反了 Spring 單例模式的一個(gè)點(diǎn),那就是 Spring 單例模式,不適合存儲(chǔ)有狀態(tài)的值,比如這里的 callback就是個(gè)有狀態(tài)的值,它應(yīng)該隨著定時(shí)任務(wù)的進(jìn)行,獲取到不同的值。

關(guān)于 Spring 或 Spring Boot 工作流程的介紹可以閱讀文末的兩篇文章,其中包括 Bean 實(shí)例化過(guò)程。

修改建議

如何解決這個(gè)問(wèn)題呢?

其實(shí)很簡(jiǎn)單,不讓callback每次調(diào)用發(fā)生變化就可以了,每次拼接 URL 的時(shí)候,先將 callback賦給一個(gè)局部變量,然后在這個(gè)變量上操作就好了。

public String getCallback() {
  Random random = new Random();
  int number = random.nextInt(100);
  System.out.println("本次隨機(jī)數(shù)為:" + number);
  String tempCallback = callback;
  tempCallback = tempCallback.replace("{token}", String.valueOf(number));
  return tempCallback;
}

另外,說(shuō)到 Spring 單例模式,Spring 本身還支持其他幾種模式,與單例模式對(duì)應(yīng)的就是 prototype模式,這種模式是每個(gè)請(qǐng)求都重新生成實(shí)例。所以,如果你確定這個(gè) Controller 和 Service 可以不用單例模式,可以加上 @Scope(value = "prototype")注解。

@RestController
@RequestMapping(value = "test")
@Scope(value = "prototype")
public class TestController {

    @Autowired
    private TestService testService;

    @GetMapping(value = "call")
    public Object getCallback() {
        return testService.getCallback();
    }
}

@Service
@Scope(value = "prototype")
public class TestService {

    private String callback = "https://ip.com/token={token}";

    public String getCallback() {
        Random random = new Random();
        int number = random.nextInt(100);
        System.out.println("本次隨機(jī)數(shù)為:" + number);
        callback = callback.replace("{token}", String.valueOf(number));
        return callback;
    }
}

這樣一來(lái),每次都是新的實(shí)例,自然就不存在那個(gè)問(wèn)題了。

“如何理解Spring單例”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!


名稱(chēng)欄目:如何理解Spring單例
文章起源:http://weahome.cn/article/gceood.html

其他資訊

在線(xiàn)咨詢(xún)

微信咨詢(xún)

電話(huà)咨詢(xún)

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部