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

spring?boot項(xiàng)目使用@Async注解的坑

 更新時(shí)間:2022年07月18日 11:44:16   作者:zzyang90  
這篇文章主要為大家介紹了spring?boot項(xiàng)目中使用@Async注解遇到的坑示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

背景

前段時(shí)間,一個(gè)同事小姐姐跟我說她的項(xiàng)目起不來了,讓我?guī)兔匆幌?,本著助人為樂的精神,這個(gè)忙肯定要去幫。

于是,我在她的控制臺(tái)發(fā)現(xiàn)了如下的異常信息:

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AService': Bean with name 'AService' has been injected into other beans [BService] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
 at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)

看到BeanCurrentlyInCreationException這個(gè)異常,我的第一反應(yīng)是出現(xiàn)了循環(huán)依賴的問題。但是仔細(xì)一想,Spring不是已經(jīng)解決了循環(huán)依賴的問題么,怎么還報(bào)這個(gè)錯(cuò)。于是,我就詢問小姐姐改了什么東西,她說在方法上加了@Async注解。

這里我模擬一下當(dāng)時(shí)的代碼,AService 和 BService 相互引用,AService的 save() 方法加了 @Async 注解。

@Component
public class AService {
    @Resource
    private BService bService;
    @Async
    public void save() {
    }
}
@Component
public class BService {
    @Resource
    private AService aService;
}

也就是這段代碼會(huì)報(bào)BeanCurrentlyInCreationException異常,難道是@Async注解遇上循環(huán)依賴的時(shí)候,Spring無法解決?為了驗(yàn)證這個(gè)猜想,我將@Async注解去掉之后,再次啟動(dòng)項(xiàng)目,項(xiàng)目成功起來了。于是基本可以得出結(jié)論,那就是@Async注解遇上循環(huán)依賴的時(shí)候,Spring的確無法解決。

雖然問題的原因已經(jīng)找到了,但是又引出以下幾個(gè)問題:

  • @Async注解是如何起作用的?
  • 為什么@Async注解遇上循環(huán)依賴,Spring無法解決?
  • 出現(xiàn)循環(huán)依賴異常之后如何解決?

@Async注解是如何起作用的?

@Async注解起作用是靠AsyncAnnotationBeanPostProcessor這個(gè)類實(shí)現(xiàn)的,這個(gè)類會(huì)處理@Async注解。AsyncAnnotationBeanPostProcessor這個(gè)類的對象是由@EnableAsync注解放入到Spring容器的,這也是為什么需要使用@EnableAsync注解來激活讓@Async注解起作用的根本原因。

AsyncAnnotationBeanPostProcessor

類體系

這個(gè)類實(shí)現(xiàn)了 BeanPostProcessor 接口,實(shí)現(xiàn)了 postProcessAfterInitialization 方法,是在其父類AbstractAdvisingBeanPostProcessor 中實(shí)現(xiàn)的,也就是說當(dāng)Bean的初始化階段完成之后會(huì)回調(diào) AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法。之所以會(huì)回調(diào),是因?yàn)樵贐ean的生命周期中,當(dāng)Bean初始化完成之后,會(huì)回調(diào)所有的 BeanPostProcessor 的 postProcessAfterInitialization 方法,代碼如下:

@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)throws BeansException {
    Object result = existingBean;
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
         Object current = processor.postProcessAfterInitialization(result, beanName);
         if (current == null) {
            return result;
         }
         result = current;
     }
    return result;
}

AsyncAnnotationBeanPostProcessor 對于 postProcessAfterInitialization 方法實(shí)現(xiàn):

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (this.advisor == null || bean instanceof AopInfrastructureBean) {
   // Ignore AOP infrastructure such as scoped proxies.
        return bean;
    }
    if (bean instanceof Advised) {
       Advised advised = (Advised) bean;
       if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
           // Add our local Advisor to the existing proxy's Advisor chain...
           if (this.beforeExistingAdvisors) {
              advised.addAdvisor(0, this.advisor);
           }
           else {
              advised.addAdvisor(this.advisor);
           }
           return bean;
        }
     }
     if (isEligible(bean, beanName)) {
        ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
        if (!proxyFactory.isProxyTargetClass()) {
           evaluateProxyInterfaces(bean.getClass(), proxyFactory);
        }
        proxyFactory.addAdvisor(this.advisor);
        customizeProxyFactory(proxyFactory);
        return proxyFactory.getProxy(getProxyClassLoader());
     }
     // No proxy needed.
     return bean;
}

該方法的主要作用是用來對方法入?yún)⒌膶ο筮M(jìn)行動(dòng)態(tài)代理的,當(dāng)入?yún)⒌膶ο蟮念惣恿薂Async注解,那么這個(gè)方法就會(huì)對這個(gè)對象進(jìn)行動(dòng)態(tài)代理,最后會(huì)返回入?yún)ο蟮拇韺ο蟪鋈?。至于如何判斷方法有沒有加@Async注解,是靠 isEligible(bean, beanName) 來判斷的。由于這段代碼牽扯到動(dòng)態(tài)代理底層的知識(shí),這里就不詳細(xì)展開了。

AsyncAnnotationBeanPostProcessor作用

綜上所述,可以得出一個(gè)結(jié)論,那就是當(dāng)Bean創(chuàng)建過程中初始化階段完成之后,會(huì)調(diào)用 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 的方法,對加了@Async注解的類的對象進(jìn)行動(dòng)態(tài)代理,然后返回一個(gè)代理對象回去。

雖然這里我們得出@Async注解的作用是依靠動(dòng)態(tài)代理實(shí)現(xiàn)的,但是這里其實(shí)又引發(fā)了另一個(gè)問題,那就是事務(wù)注解@Transactional又或者是自定義的AOP切面,他們也都是通過動(dòng)態(tài)代理實(shí)現(xiàn)的,為什么使用這些的時(shí)候,沒見拋出循環(huán)依賴的異常?難道他們的實(shí)現(xiàn)跟@Async注解的實(shí)現(xiàn)不一樣?不錯(cuò),還真的不太一樣,請繼續(xù)往下看。

AOP是如何實(shí)現(xiàn)的?

我們都知道AOP是依靠動(dòng)態(tài)代理實(shí)現(xiàn)的,而且是在Bean的生命周期中起作用,具體是靠 AnnotationAwareAspectJAutoProxyCreator 這個(gè)類實(shí)現(xiàn)的,這個(gè)類會(huì)在Bean的生命周期中去處理切面,事務(wù)注解,然后生成動(dòng)態(tài)代理。這個(gè)類的對象在容器啟動(dòng)的時(shí)候,就會(huì)被自動(dòng)注入到Spring容器中。

AnnotationAwareAspectJAutoProxyCreator 也實(shí)現(xiàn)了BeanPostProcessor,也實(shí)現(xiàn)了 postProcessAfterInitialization 方法。

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
    if (bean != null) {
       Object cacheKey = getCacheKey(bean.getClass(), beanName);
       if (!this.earlyProxyReferences.contains(cacheKey)) {
           //生成動(dòng)態(tài)代理,如果需要被代理的話
           return wrapIfNecessary(bean, beanName, cacheKey);
       }
     }
    return bean;
}

通過 wrapIfNecessary 方法就會(huì)對Bean進(jìn)行動(dòng)態(tài)代理,如果你的Bean需要被動(dòng)態(tài)代理的話。

AnnotationAwareAspectJAutoProxyCreator作用

也就說,AOP和@Async注解雖然底層都是動(dòng)態(tài)代理,但是具體實(shí)現(xiàn)的類是不一樣的。一般的AOP或者事務(wù)的動(dòng)態(tài)代理是依靠 AnnotationAwareAspectJAutoProxyCreator 實(shí)現(xiàn)的,而@Async是依靠 AsyncAnnotationBeanPostProcessor 實(shí)現(xiàn)的,并且都是在初始化完成之后起作用,這也就是@Async注解和AOP之間的主要區(qū)別,也就是處理的類不一樣。

Spring是如何解決循環(huán)依賴的

Spring在解決循環(huán)依賴的時(shí)候,是依靠三級(jí)緩存來實(shí)現(xiàn)的。我曾經(jīng)寫過一篇關(guān)于三級(jí)緩存的文章,如果有不清楚的小伙伴可以 關(guān)注微信公眾號(hào) 三友的java日記,回復(fù) 循環(huán)依賴 即可獲取原文鏈接,本文也算是這篇三級(jí)緩存文章的續(xù)作。

簡單來說,通過緩存正在創(chuàng)建的對象對應(yīng)的ObjectFactory對象,可以獲取到正在創(chuàng)建的對象的早期引用的對象,當(dāng)出現(xiàn)循環(huán)依賴的時(shí)候,由于對象沒創(chuàng)建完,就可以通過獲取早期引用的對象注入就行了。

而緩存ObjectFactory代碼如下:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
       if (!this.singletonObjects.containsKey(beanName)) {
           this.singletonFactories.put(beanName, singletonFactory);
           this.earlySingletonObjects.remove(beanName);
           this.registeredSingletons.add(beanName);
       }
    }
}

所以緩存的ObjectFactory對象其實(shí)是一個(gè)lamda表達(dá)式,真正獲取早期暴露的引用對象其實(shí)就是通過 getEarlyBeanReference 方法來實(shí)現(xiàn)的。

getEarlyBeanReference 方法:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
               SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
               exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
       }
    }
    return exposedObject;
}

getEarlyBeanReference 實(shí)現(xiàn)是調(diào)用所有的 SmartInstantiationAwareBeanPostProcessor 的 getEarlyBeanReference 方法。

而前面提到的 AnnotationAwareAspectJAutoProxyCreator 這個(gè)類就實(shí)現(xiàn)了 SmartInstantiationAwareBeanPostProcessor 接口,是在父類中實(shí)現(xiàn)的:

@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    if (!this.earlyProxyReferences.contains(cacheKey)) {
        this.earlyProxyReferences.add(cacheKey);
    }
    return wrapIfNecessary(bean, beanName, cacheKey);
}

這個(gè)方法最后會(huì)調(diào)用 wrapIfNecessary 方法,前面也說過,這個(gè)方法是獲取動(dòng)態(tài)代理的方法,如果需要的話就會(huì)代理,比如事務(wù)注解又或者是自定義的AOP切面,在早期暴露的時(shí)候,就會(huì)完成動(dòng)態(tài)代理。

這下終于弄清楚了,早期暴露出去的原來可能是個(gè)代理對象,而且最終是通過AnnotationAwareAspectJAutoProxyCreator這個(gè)類的getEarlyBeanReference方法獲取的。

但是AsyncAnnotationBeanPostProcessor并沒有實(shí)現(xiàn)SmartInstantiationAwareBeanPostProcessor,也就是在獲取早期對象這一階段,并不會(huì)調(diào)AsyncAnnotationBeanPostProcessor處理@Async注解。

為什么@Async注解遇上循環(huán)依賴,Spring無法解決?

這里我們就拿前面的例子來說,AService加了@Async注解,AService先創(chuàng)建,發(fā)現(xiàn)引用了BService,那么BService就會(huì)去創(chuàng)建,當(dāng)Service創(chuàng)建的過程中發(fā)現(xiàn)引用了AService,那么就會(huì)通過AnnotationAwareAspectJAutoProxyCreator 這個(gè)類實(shí)現(xiàn)的 getEarlyBeanReference 方法獲取AService的早期引用對象,此時(shí)這個(gè)早期引用對象可能會(huì)被代理,取決于AService是否需要被代理,但是一定不是處理@Async注解的代理,原因前面也說過。

于是BService創(chuàng)建好之后,注入給了AService,那么AService會(huì)繼續(xù)往下處理,前面說過,當(dāng)初始化階段完成之后,會(huì)調(diào)用所有的BeanPostProcessor的實(shí)現(xiàn)的 postProcessAfterInitialization 方法。于是就會(huì)回調(diào)依次回調(diào) AnnotationAwareAspectJAutoProxyCreator 和 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法實(shí)現(xiàn)。

這段回調(diào)有兩個(gè)細(xì)節(jié):

  • AnnotationAwareAspectJAutoProxyCreator 先執(zhí)行,AsyncAnnotationBeanPostProcessor 后執(zhí)行,因?yàn)?AnnotationAwareAspectJAutoProxyCreator 在前面。

  • AnnotationAwareAspectJAutoProxyCreator處理的結(jié)果會(huì)當(dāng)入?yún)鬟f給 AsyncAnnotationBeanPostProcessor,applyBeanPostProcessorsAfterInitialization方法就是這么實(shí)現(xiàn)的

AnnotationAwareAspectJAutoProxyCreator回調(diào):會(huì)發(fā)現(xiàn)AService對象已經(jīng)被早期引用了,什么都不處理,直接把對象AService給返回

AsyncAnnotationBeanPostProcessor回調(diào):發(fā)現(xiàn)AService類中加了@Async注解,那么就會(huì)對AnnotationAwareAspectJAutoProxyCreator返回的對象進(jìn)行動(dòng)態(tài)代理,然后返回了動(dòng)態(tài)代理對象。

這段回調(diào)完,是不是已經(jīng)發(fā)現(xiàn)了問題。早期暴露出去的對象,可能是AService本身或者是AService的代理對象,而且是通過AnnotationAwareAspectJAutoProxyCreator對象實(shí)現(xiàn)的,但是通過AsyncAnnotationBeanPostProcessor的回調(diào),會(huì)對AService對象進(jìn)行動(dòng)態(tài)代理,這就導(dǎo)致AService早期暴露出去的對象跟最后完全創(chuàng)造出來的對象不是同一個(gè),那么肯定就不對了。

同一個(gè)Bean在一個(gè)Spring中怎么能存在兩個(gè)不同的對象呢,于是就會(huì)拋出BeanCurrentlyInCreationException異常,這段判斷邏輯的代碼如下:

if (earlySingletonExposure) {
  // 獲取到早期暴露出去的對象
  Object earlySingletonReference = getSingleton(beanName, false);
  if (earlySingletonReference != null) {
      // 早期暴露的對象不為null,說明出現(xiàn)了循環(huán)依賴
      if (exposedObject == bean) {
          // 這個(gè)判斷的意思就是指 postProcessAfterInitialization 回調(diào)沒有進(jìn)行動(dòng)態(tài)代理,如果沒有那么就將早期暴露出去的對象賦值給最終暴露(生成)出去的對象,
          // 這樣就實(shí)現(xiàn)了早期暴露出去的對象和最終生成的對象是同一個(gè)了
          // 但是一旦 postProcessAfterInitialization 回調(diào)生成了動(dòng)態(tài)代理 ,那么就不會(huì)走這,也就是加了@Aysnc注解,是不會(huì)走這的
          exposedObject = earlySingletonReference;
      }
      else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
               // allowRawInjectionDespiteWrapping 默認(rèn)是false
               String[] dependentBeans = getDependentBeans(beanName);
               Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
               for (String dependentBean : dependentBeans) {
                   if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                       actualDependentBeans.add(dependentBean);
                  }
               }
               if (!actualDependentBeans.isEmpty()) {
                   //拋出異常
                   throw new BeanCurrentlyInCreationException(beanName,
                           "Bean with name '" + beanName + "' has been injected into other beans [" +
                           StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                           "] in its raw version as part of a circular reference, but has eventually been " +
                           "wrapped. This means that said other beans do not use the final version of the " +
                           "bean. This is often the result of over-eager type matching - consider using " +
                           "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
               }
      }
   }
}

所以,之所以@Async注解遇上循環(huán)依賴,Spring無法解決,是因?yàn)锧Aysnc注解會(huì)使得最終創(chuàng)建出來的Bean,跟早期暴露出去的Bean不是同一個(gè)對象,所以就會(huì)報(bào)錯(cuò)。

出現(xiàn)循環(huán)依賴異常之后如何解決?

解決這個(gè)問題的方法很多

1、調(diào)整對象間的依賴關(guān)系,從根本上杜絕循環(huán)依賴,沒有循環(huán)依賴,就沒有早期暴露這么一說,那么就不會(huì)出現(xiàn)問題

2、不使用@Async注解,可以自己通過線程池實(shí)現(xiàn)異步,這樣沒有@Async注解,就不會(huì)在最后生成代理對象,導(dǎo)致早期暴露的出去的對象不一樣

3、可以在循環(huán)依賴注入的字段上加@Lazy注解

@Component
public class AService {
    @Resource
    @Lazy
    private BService bService;
    @Async
    public void save() {
    }
}

4、從上面的那段判斷拋異常的源碼注釋可以看出,當(dāng)allowRawInjectionDespiteWrapping為true的時(shí)候,就不會(huì)走那個(gè)else if,也就不會(huì)拋出異常,所以可以通過將allowRawInjectionDespiteWrapping設(shè)置成true來解決報(bào)錯(cuò)的問題,代碼如下:

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        ((DefaultListableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
    }
}

雖然這樣設(shè)置能解決報(bào)錯(cuò)的問題,但是并不推薦,因?yàn)檫@樣設(shè)置就允許早期注入的對象和最終創(chuàng)建出來的對象是不一樣,并且可能會(huì)導(dǎo)致最終生成的對象沒有被動(dòng)態(tài)代理。

以上就是spring boot項(xiàng)目使用@Async注解的坑的詳細(xì)內(nèi)容,更多關(guān)于spring boot項(xiàng)目@Async注解的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java實(shí)現(xiàn)本地日期時(shí)間處理

    java實(shí)現(xiàn)本地日期時(shí)間處理

    這篇文章主要介紹了本地日期時(shí)間處理的程序,實(shí)現(xiàn)了下面的功能,大家參考使用吧
    2014-01-01
  • MyBatis-Plus 分頁查詢以及自定義sql分頁的實(shí)現(xiàn)

    MyBatis-Plus 分頁查詢以及自定義sql分頁的實(shí)現(xiàn)

    這篇文章主要介紹了MyBatis-Plus 分頁查詢以及自定義sql分頁的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • 一文詳解Java如何優(yōu)雅地判斷對象是否為空

    一文詳解Java如何優(yōu)雅地判斷對象是否為空

    這篇文章主要給大家介紹了關(guān)于Java如何優(yōu)雅地判斷對象是否為空的相關(guān)資料,在Java中可以使用以下方法優(yōu)雅地判斷一個(gè)對象是否為空,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-04-04
  • SpringCloud及Nacos服務(wù)注冊IP選擇問題解決方法

    SpringCloud及Nacos服務(wù)注冊IP選擇問題解決方法

    這篇文章主要介紹了SpringCloud及Nacos服務(wù)注冊IP選擇問題,為什么注冊的IP和真實(shí)IP不符合呢,原因是Nacos客戶端在注冊服務(wù)時(shí)會(huì)從機(jī)器網(wǎng)卡中選擇一個(gè)IP來注冊,所以,當(dāng)注冊了的是非真實(shí)IP后,另一臺(tái)機(jī)器調(diào)用時(shí)是不可能調(diào)通的,知道問題原因就是解決方法,一起看看吧
    2024-01-01
  • Java如何使用遞歸查詢多級(jí)樹形結(jié)構(gòu)數(shù)據(jù)(多級(jí)菜單)

    Java如何使用遞歸查詢多級(jí)樹形結(jié)構(gòu)數(shù)據(jù)(多級(jí)菜單)

    這篇文章主要介紹了Java如何使用遞歸查詢多級(jí)樹形結(jié)構(gòu)數(shù)據(jù)(多級(jí)菜單),具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • Java多線程之Future設(shè)計(jì)模式

    Java多線程之Future設(shè)計(jì)模式

    這篇文章主要介紹了Java多線程之Future設(shè)計(jì)模式,F(xiàn)uture 代表的是未來的一個(gè)憑據(jù),文章主要附上Future具體實(shí)現(xiàn)類、橋接Future和FutureTask的代碼,需要的朋友可以參考一下
    2021-10-10
  • java反射原理制作對象打印工具

    java反射原理制作對象打印工具

    本文主要給大家介紹了java反射原理制作對象打印工具的方法和代碼,以及一個(gè)熱心網(wǎng)友給出的更加簡潔方便的代碼,小伙伴們需要的話可以參考下。
    2015-12-12
  • Spring利用@Validated注解實(shí)現(xiàn)參數(shù)校驗(yàn)詳解

    Spring利用@Validated注解實(shí)現(xiàn)參數(shù)校驗(yàn)詳解

    這篇文章主要為大家詳細(xì)介紹了在?Spring?項(xiàng)目中使用?@Validated?進(jìn)行參數(shù)校驗(yàn)的方法和常見應(yīng)用場景,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-05-05
  • Java Map.entry案例詳解

    Java Map.entry案例詳解

    這篇文章主要介紹了Java Map.entry案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • Java利用MessageFormat實(shí)現(xiàn)短信模板的匹配

    Java利用MessageFormat實(shí)現(xiàn)短信模板的匹配

    這篇文章主要介紹了Java利用MessageFormat實(shí)現(xiàn)短信模板的匹配,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-06-06

最新評(píng)論

保德县| 昌都县| 稻城县| 资溪县| 聂荣县| 涿州市| 乌兰浩特市| 大丰市| 垣曲县| 木里| 侯马市| 松原市| 芦溪县| 蕲春县| 安吉县| 长泰县| 郸城县| 始兴县| 三门县| 怀安县| 赤城县| 武胜县| 滁州市| 娄烦县| 息烽县| 遂平县| 沿河| 小金县| 临江市| 邛崃市| 盱眙县| 沾益县| 东丰县| 黔西县| 嘉兴市| 台江县| 娱乐| 遵义市| 河间市| 蓬莱市| 潼南县|