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

Spring實現(xiàn)三級緩存機制

 更新時間:2025年02月28日 09:22:25   作者:碼上生花  
三級緩存機制是Spring解決循環(huán)依賴問題的關鍵,本文主要介紹了Spring實現(xiàn)三級緩存機制,具有一定的參考價值,感興趣的可以了解一下

一、什么是三級緩存

就是在Bean生成流程中保存Bean對象三種形態(tài)的三個Map集合,如下:

// 一級緩存Map 存放完整的Bean(流程跑完的)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);

// 二級緩存Map 存放不完整的Bean(只實例化完,還沒屬性賦值、初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);

// 三級緩存Map 存放一個Bean的lambda表達式(也是剛實例化完)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);

用來解決什么問題?

這個大家應該熟知了,就是循環(huán)依賴

什么是循環(huán)依賴?

就像下面這樣,AService 中注入了BService ,而BService 中又注入了AService ,這就是循環(huán)依賴

@Service
public class AService {

    @Resource
    private BService bService;
}

@Service
public class BService {

    @Resource
    private AService aService;
}

二、Bean的加載源碼

我們先通過getBean()流程圖,來了解Spring的getBean()方法的工作流程,接著根據(jù)這個工作流程一步一步的閱讀源碼

在這里插入圖片描述

//在spring中我們平時用到的getbean()這個方法實際上是調(diào)用的AbstractBeanFactory這個抽象工廠中得getbean方法
public Object getBean(String name) throws BeansException {
//看源碼 我們首先看其返回值   如下返回得是doGetBean這個方法
        return this.doGetBean(name, null, null, false);
        }
//接下來我們看這個doGetBean這個方法
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {

 //獲取name對應的真正beanName
//有這么幾種情況 傳入得參數(shù)有可能是某個參數(shù)得別名,也有可能是FactoryBean的name  //根據(jù)具體得實例去解析最終的得name
final String beanName = this.transformedBeanName(name);

        Object bean;

      //   在創(chuàng)建單例bean的時候會存在依賴注入的情況,而在創(chuàng)建依賴的時候為了避免循環(huán)依賴
       //  Spring創(chuàng)建bean的原則是不等bean創(chuàng)建完成就會將創(chuàng)建bean的ObjectFactory提前曝光(將對應的ObjectFactory加入到緩存)
      //   一旦下一個bean創(chuàng)建需要依賴上一個bean,則直接使用ObjectFactory對象
        // 獲取單例
        Object sharedInstance = this.getSingleton(beanName);
        if (sharedInstance != null && args == null) {
        // 實例已經(jīng)存在
        if (logger.isDebugEnabled()) {
	        if (this.isSingletonCurrentlyInCreation(beanName)) {
	        	logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
	        } else {
	        	logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
	        }
        }
        // 返回對應的實例
        bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, null);
        } else {
	        // 單例實例不存在
	        if (this.isPrototypeCurrentlyInCreation(beanName)) {
	         // 只有在單例模式下才會嘗試解決循環(huán)依賴問題
	         // 對于原型模式,如果存在循環(huán)依賴,也就是滿足this.isPrototypeCurrentlyInCreation(beanName),拋出異常
	        throw new BeanCurrentlyInCreationException(beanName);
        }

        // 獲取parentBeanFactory實例
        BeanFactory parentBeanFactory = this.getParentBeanFactory();
        // 如果在beanDefinitionMap中(即所有已經(jīng)加載的類中)不包含目標bean,則嘗試從parentBeanFactory中獲取
        if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) {
        	String nameToLookup = this.originalBeanName(name);  // 獲取name對應的真正beanName,如果是factoryBean,則加上“&”前綴
	        if (args != null) {
	        // 遞歸到BeanFactory中尋找
	        	return (T) parentBeanFactory.getBean(nameToLookup, args);
	        } else {
	        	return parentBeanFactory.getBean(nameToLookup, requiredType);
	        }
        }

        // 如果不僅僅是做類型檢查,標記bean的狀態(tài)已經(jīng)創(chuàng)建,即將beanName加入alreadyCreated集合中
        if (!typeCheckOnly) {
        	this.markBeanAsCreated(beanName);
        }

        try {
		 //將存儲XML配置的GenericBeanDefinition實例轉換成RootBeanDefinition實例,方便后續(xù)處理
		 // 如果存在父bean,則同時合并父bean的相關屬性
		final RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
        // 檢查bean是否是抽象的,如果是則拋出異常
        this.checkMergedBeanDefinition(mbd, beanName, args);

        // 加載當前bean依賴的bean
        String[] dependsOn = mbd.getDependsOn();
        if (dependsOn != null) {
        // 存在依賴,遞歸實例化依賴的bean
        for (String dep : dependsOn) {
        if (this.isDependent(beanName, dep)) {
        // 檢查dep是否依賴beanName,從而導致循環(huán)依賴
        throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
        }
        // 緩存依賴調(diào)用
        this.registerDependentBean(dep, beanName);
        this.getBean(dep);
        }
        }

        // 完成加載依賴的bean后,實例化mbd自身
        if (mbd.isSingleton()) {
        // scope == singleton
        sharedInstance = this.getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
        try {
        return createBean(beanName, mbd, args);
        } catch (BeansException ex) {
        // 清理工作,從單例緩存中移除
        destroySingleton(beanName);
        throw ex;
        }
        }
        });
        bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        } else if (mbd.isPrototype()) {
        // scope == prototype
        Object prototypeInstance;
        try {
        // 設置正在創(chuàng)建的狀態(tài)
        this.beforePrototypeCreation(beanName);
        // 創(chuàng)建bean
        prototypeInstance = this.createBean(beanName, mbd, args);
        } finally {
        this.afterPrototypeCreation(beanName);
        }
        // 返回對應的實例
        bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
        } else {
        // 其它范圍得實例
        String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
        if (scope == null) {
        throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
        }
        try {
        Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
        @Override
        public Object getObject() throws BeansException {
        beforePrototypeCreation(beanName);
        try {
        return createBean(beanName, mbd, args);
        } finally {
        afterPrototypeCreation(beanName);
        }
        }
        });
        // 返回對應的實例
        bean = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
        } catch (IllegalStateException ex) {
        throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex);
        }
        }
        } catch (BeansException ex) {
        cleanupAfterBeanCreationFailure(beanName);
        throw ex;
        }
        }

        // 檢查需要的類型是否符合bean的實際類型,對應getBean時指定的需要類型
        if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
        try {
        // 執(zhí)行類型轉換,轉換成對應的類型
        return this.getTypeConverter().convertIfNecessary(bean, requiredType);
        } catch (TypeMismatchException ex) {
        if (logger.isDebugEnabled()) {
        logger.debug("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", ex);
        }
        throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
        }
        }
        到這里才是真正意義上返回一個bean而已
        return (T) bean;
        }

三、三級緩存詳解

不管你了不了解源碼,我們先看一下Bean的生成流程,看看三級緩存是在什么地方有調(diào)用,就三個地方:

  • Bean實例化前會先查詢緩存,判斷Bean是否已經(jīng)存在
  • Bean屬性賦值前會先向三級緩存中放入一個lambda表達式,該表達式執(zhí)行則會生成一個半成品Bean放入二級緩存
  • Bean初始化完成后將完整的Bean放入一級緩存,同時清空二、三級緩存

接下來我們一個一個看!

在這里插入圖片描述

3.1 Bean實例化前

AbstractBeanFactory.doGetBean

Bean實例化前會從緩存里面獲取Bean,防止重復實例化

在這里插入圖片描述

DefaultSingletonBeanRegistry.getSingleton(String beanName, boolean allowEarlyReference)

我們看看這個獲取的方法邏輯:

  • 從一級緩存獲取,獲取到了,則返回
  • 從二級緩存獲取,獲取到了,則返回
  • 從三級緩存獲取,獲取到了,則執(zhí)行三級緩存中的lambda表達式,將結果放入二級緩存,清除三級緩存
public Object getSingleton(String beanName) {
    return this.getSingleton(beanName, true);
}

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 從一級緩存中獲取Bean 獲取到了則返回 沒獲取到繼續(xù)
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
        // 從二級緩存中獲取Bean  獲取到了則返回 沒獲取到則繼續(xù)
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            // 加一把鎖防止 線程安全 雙重獲取校驗
            synchronized(this.singletonObjects) {
                // 從一級緩存中獲取Bean 獲取到了則返回 沒獲取到繼續(xù)
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    // 從二級緩存中獲取Bean  獲取到了則返回 沒獲取到則繼續(xù)
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        // 從三級緩存中獲取 沒獲取到則返回
                        ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            // 獲取到了 執(zhí)行三級緩存中的lambda表達式
                            singletonObject = singletonFactory.getObject();
                            // 并將結果放入二級緩存
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            // 從三級緩存中移除
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }

    return singletonObject;
}

3.2 屬性賦值/注入前

AbstractAutowireCapableBeanFactory.doCreateBean

在這里插入圖片描述

DefaultSingletonBeanRegistry.addSingletonFactory

這里就是將一個lambda表達式放入了三級緩存,我們需要去看一下這個表達式是干什么的!!

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized(this.singletonObjects) {
        // 一級緩存中不存在的話 
        if (!this.singletonObjects.containsKey(beanName)) {
            // 將lambda表達式放入三級緩存
            this.singletonFactories.put(beanName, singletonFactory);
            // 清除二級緩存 
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

AbstractAutowireCapableBeanFactory.getEarlyBeanReference

該方法說白了就是會判斷該Bean是否需要被動態(tài)代理,兩種返回結果:

  • 不需要代理,返回未屬性注入、未初始化的半成品Bean
  • 需要代理,返回未屬性注入、未初始化的半成品Bean的代理對象
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) {
        Iterator var5 = this.getBeanPostProcessors().iterator();
        // 遍歷后置處理器
        while(var5.hasNext()) {
            BeanPostProcessor bp = (BeanPostProcessor)var5.next();
            // 找到實現(xiàn)SmartInstantiationAwareBeanPostProcessor接口的
            // 該接口getEarlyBeanReference方法什么時候會執(zhí)行?
            // AOP動態(tài)代理的時候 該方法執(zhí)行就是判斷該Bean是否需要被代理
            // 需要代理則會創(chuàng)建代理對象返回
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor)bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    // 這個Object有兩種情況,一是實例化后的半成品Bean,二是半成品Bean動態(tài)代理后的代理對象
    return exposedObject;
}

注意:這里只是把lambda表達式放入了三級緩存,如果不從三級緩存中獲取,這個表達式是不執(zhí)行的,一旦執(zhí)行了,就會把半成品Bean或者半成品Bean的代理對象放入二級緩存中了

3.3初始化后

AbstractBeanFactory.doGetBean

這里注意啊,這個getSingleton方法傳參傳了個lambda表達式,這個表達式內(nèi)部就是Bean的實例化過程,初始化完成后,是要需要執(zhí)行這個getSingleton方法的

在這里插入圖片描述

DefaultSingletonBeanRegistry.getSingleton(beanName, singletonFactory)

這個方法與上面那個不一樣,重載了

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
     
        synchronized(this.singletonObjects) {
            // 第一次進來這里獲取肯定為null
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               // 省略................
                try {
                    // 注意啊,這個就是執(zhí)行外面那個傳參的lambda表達式
                    // 所以這里才會跳到createBean方法那里去執(zhí)行
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                } 
                // 省略................
                finally {
                    if (recordSuppressedExceptions) {
                        this.suppressedExceptions = null;
                    }
                    this.afterSingletonCreation(beanName);
                }
                // 到了這說明Bean創(chuàng)建完了
                if (newSingleton) {
                    // 這里就會把Bean放入一級緩存中了 同時清除二、三級緩存
                    this.addSingleton(beanName, singletonObject);
                }
            }
            return singletonObject;
        }
    }

DefaultSingletonBeanRegistry.addSingleton

protected void addSingleton(String beanName, Object singletonObject) {
  synchronized(this.singletonObjects) {
     // 放入一級緩存  
      this.singletonObjects.put(beanName, singletonObject);
      // 清除二、三級緩存
      this.singletonFactories.remove(beanName);
      this.earlySingletonObjects.remove(beanName);
      this.registeredSingletons.add(beanName);
  }
}

總結

整個過程就三個地方跟緩存有關,我們假設現(xiàn)在要實例化A這個Bean,看看緩存是怎么變化的:

  • 實例化前,獲取緩存判斷(三個緩存中肯定沒有A,獲取為null,進入實例化流程)
  • 實例化完成,屬性注入前(往三級緩存中放入了一個lambda表達式,一、二級為null)
  • 初始化完成(將A這個Bean放入一級緩存,清除二、三級緩存)

以上則是單個Bean生成過程中緩存的變化??!

四、怎么解決的循環(huán)依賴

上面我們把Bean流程中利用緩存的三個重要的點都找出來了,也分析了會帶來什么變化,接下來看看是怎么解決的循環(huán)依賴,我們看個圖就懂了:

以A注入B,B注入A為例:

A屬性注入前就把lambda表達式放入了第三級緩存,所以B再注入A的時候會從第三級緩存中找到A的lambda表達式并執(zhí)行,然后將半成品Bean放入第二級緩存,所以此時B注入的只是半成品的A對象,B創(chuàng)建完成后返回給A注入,A繼續(xù)初始化,完成創(chuàng)建。

注意: B注入的半成品A對象只是一個引用,所以之后A初始化完成后,B這個注入的A就隨之變成了完整的A

在這里插入圖片描述

從上述看第三級緩存是用來提前暴露Bean對象引用的,所以解決了循環(huán)依賴,但是第二級緩存的這個半成品Bean對象干嘛的呢?

假設A同時注入了B和C,B和C又都注入了A,這時A注入B,實例化B的過程和上述是一樣的,但隨后還會注入C,那這個C在注入A的時候還會有第三級緩存用嗎?沒了吧,所以它就只能用第二級緩存的半成品Bean對象了,同樣也是引用而已

五、不用三級緩存不行嗎

可能很多小伙伴得到的答案就是不行,而且答案是因為不確定這個Bean是不是代理對象,所以搞了個lambda表達式?答案真的是這樣嗎??

我們分析一下:AOP動態(tài)代理在沒有循環(huán)依賴的時候是在哪里執(zhí)行的?Bean初始化后!有循環(huán)依賴的時候是在屬性賦值前,中間就間隔了一個屬性注入對吧,沒錯,在屬性注入的時候注入的是原始對象的引用還是代理對象的引用這個很重要,但是屬性注入會影響AOP的結果嗎?是否AOP創(chuàng)建代理對象和切面有關,和屬性注入無關,所以我們完全可以在屬性注入之前就知道這個Bean是代理對象還是非代理對象,就像下面這樣,我不將表達式放入第三級緩存了,而是直接執(zhí)行,將結果放入第二級緩存

在這里插入圖片描述

這樣可不可以?可以吧,這樣用二級緩存就解決了,但是在一個對象沒有屬性賦值、初始化前就創(chuàng)建代理對象是有風險的!像這么做不管有沒有產(chǎn)生循環(huán)依賴,只要有AOP動態(tài)代理對象的產(chǎn)生就有一分風險,這么做是得不償失的,所以有了三級緩存,三級緩存是只有在循環(huán)依賴以及AOP動態(tài)代理同時產(chǎn)生時才會有風險。可以說是因為存在循環(huán)依賴所以被迫的導致Bean對象提前的暴露了引用?。?! 所以這下懂了吧

至于為什么多例、構造器注入這兩種情況解決不了循環(huán)依賴就很簡單了:

循環(huán)依賴的解決原理是在對象實例化后提前暴露了引用,而這兩種情況都還沒實例化呢

六、總結

  • 一級緩存:用于存儲被完整創(chuàng)建了的bean。也就是完成了初始化之后,可以直接被其他對象使用的bean。
  • 二級緩存:用于存儲半成品的Bean。也就是剛實例化但是還沒有進行初始化的Bean
  • 三級緩存:三級緩存存儲的是工廠對象(lambda表達式)。工廠對象可以產(chǎn)生Bean對象提前暴露的引用(半成品的Bean或者半成品的代理Bean對象),執(zhí)行這個lambda表達式,就會將引用放入二級緩存中

經(jīng)過以上的分析,現(xiàn)在應該懂了吧:

循環(huán)依賴是否一定需要三級緩存來解決? 不一定,但三級緩存會更合適,風險更小

二級緩存能否解決循環(huán)依賴? 可以,但風險比三級緩存更大

第二級緩存用來干嘛的? 存放半成品的引用,可能產(chǎn)生多對象循環(huán)依賴,第三級緩存產(chǎn)生引用后,后續(xù)的就可以直接注入該引用

多例、構造器注入為什么不能解決循環(huán)依賴? 因為循環(huán)依賴的原理的實例化后提前暴露的引用,這兩種情況還沒實例化

到此這篇關于Spring實現(xiàn)三級緩存機制的文章就介紹到這了,更多相關Spring 三級緩存機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 2024版本IDEA創(chuàng)建Servlet模板的圖文教程

    2024版本IDEA創(chuàng)建Servlet模板的圖文教程

    新版IDEA?2024.1.4中,用戶需要自行創(chuàng)建Servlet模板以解決Web項目無法通過右鍵創(chuàng)建Servlet的問題,本文詳細介紹了添加ServletAnnotatedClass.java模板的步驟,幫助用戶快速配置并使用新的Servlet模板,需要的朋友可以參考下
    2024-10-10
  • Java之WeakHashMap源碼淺析

    Java之WeakHashMap源碼淺析

    這篇文章主要介紹了Java之WeakHashMap源碼淺析,WeakHashMap從名字可以得知主要和Map有關,不過還有一個Weak,我們就更能自然而然的想到這里面還牽扯到一種弱引用結構,因此想要徹底搞懂,我們還需要知道四種引用,需要的朋友可以參考下
    2023-09-09
  • Spring Boot 集成Mybatis實現(xiàn)主從(多數(shù)據(jù)源)分離方案示例

    Spring Boot 集成Mybatis實現(xiàn)主從(多數(shù)據(jù)源)分離方案示例

    本篇文章主要介紹了Spring Boot 集成Mybatis實現(xiàn)主從(多數(shù)據(jù)源)分離方案實例,具有一定的參考價值,有興趣的可以了解一下。
    2017-03-03
  • c#和java base64不一致的解決方法

    c#和java base64不一致的解決方法

    最近非常郁悶的處理這個base64的問題,同樣的一個圖片文件,在java和c#進行base64編碼后結果不一樣,苦惱了很久,下面這篇文章主要給大家介紹了關于c#和java base64不一致的解決方法,需要的朋友可以參考下
    2018-11-11
  • Jeecg-Boot異常處理'jeecg-boot.QRTZ_LOCKS'?doesn't?exist問題

    Jeecg-Boot異常處理'jeecg-boot.QRTZ_LOCKS'?doesn'

    這篇文章主要介紹了Jeecg-Boot異常處理'jeecg-boot.QRTZ_LOCKS'?doesn't?exist問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • JVM:晚期(運行期)優(yōu)化的深入理解

    JVM:晚期(運行期)優(yōu)化的深入理解

    今天小編就為大家分享一篇關于JVM:晚期(運行期)優(yōu)化的深入理解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-02-02
  • Java的數(shù)據(jù)類型和參數(shù)傳遞(詳解)

    Java的數(shù)據(jù)類型和參數(shù)傳遞(詳解)

    下面小編就為大家?guī)硪黄狫ava的數(shù)據(jù)類型和參數(shù)傳遞(詳解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • spring boot 集成dubbo的示例演示

    spring boot 集成dubbo的示例演示

    這篇文章主要介紹了spring boot 集成dubbo的示例演示,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-07-07
  • SpringCloud Alibaba 基本開發(fā)框架搭建過程

    SpringCloud Alibaba 基本開發(fā)框架搭建過程

    這篇文章主要介紹了SpringCloud Alibaba 基本開發(fā)框架搭建過程,開發(fā)工具選用的idea,本文通過圖文實例相結合給大家分享搭建全過程,需要的朋友可以參考下
    2021-06-06
  • 攔截Druid數(shù)據(jù)源自動注入帳密解密實現(xiàn)詳解

    攔截Druid數(shù)據(jù)源自動注入帳密解密實現(xiàn)詳解

    這篇文章主要為大家介紹了攔截Druid數(shù)據(jù)源自動注入帳密解密實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-11-11

最新評論