Spring核心原理之 IoC容器中那些鮮為人知的細節(3)

Tom彈架構發表於2021-12-25

本文節選自《Spring 5核心原理》

Spring IoC容器還有一些高階特性,如使用lazy-init屬性對Bean預初始化、使用FactoryBean產生或者修飾Bean物件的生成、IoC容器在初始化Bean過程中使用BeanPostProcessor後置處理器對Bean宣告週期事件進行管理等。

1 關於延時載入

我們已經知道,IoC容器的初始化過程就是對Bean定義資源的定位、載入和註冊,此時容器對Bean的依賴注入並沒有發生,依賴注入是在應用程式第一次向容器索取Bean時通過getBean()方法完成的。
當Bean定義資源的< bean>元素中配置了lazy-init=false屬性時,容器將會在初始化時對所配置的Bean進行預例項化,Bean的依賴注入在容器初始化時就已經完成。這樣,當應用程式第一次向容器索取被管理的Bean時,就不用再初始化和對Bean進行依賴注入了,而是直接從容器中獲取已經完成依賴注入的Bean,提高了應用程式第一次向容器獲取Bean的效能。

1.1. refresh()方法

IoC容器讀入已經定位的Bean定義資源是從refresh()方法開始的,我們從AbstractApplicationContext類的refresh()方法入手分析,回顧一下原始碼:

@Override
public void refresh() throws BeansException, IllegalStateException {
	  ...
      //子類的refreshBeanFactory()方法啟動
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
	  ...
}

在refresh()方法中ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();啟動了Bean定義資源的載入、註冊過程。finishBeanFactoryInitialization()方法是對註冊後的Bean定義中的預例項化(lazy-init=false,Spring預設進行預例項化,即為true)的Bean進行處理的地方。

1.2. 使用finishBeanFactoryInitialization()處理預例項化的Bean

當Bean定義資源被載入IoC容器之後,容器將Bean定義資源解析為容器內部的資料結構BeanDefinition,並註冊到容器中,AbstractApplicationContext類中的finishBeanFactoryInitialization()方法對配置了預例項化屬性的Bean進行預初始化,原始碼如下:

//對配置了lazy-init屬性的Bean進行預例項化處理
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
   //這是Spring 3新加的程式碼,為容器指定一個轉換服務(ConversionService)
   //在對某些Bean屬性進行轉換時使用
   if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
         beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
      beanFactory.setConversionService(
            beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
   }

   if (!beanFactory.hasEmbeddedValueResolver()) {
      beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders (strVal));
   }

   String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
   for (String weaverAwareName : weaverAwareNames) {
      getBean(weaverAwareName);
   }

   //為了使型別匹配,停止使用臨時的類載入器
   beanFactory.setTempClassLoader(null);

   //快取容器中所有註冊的BeanDefinition後設資料,以防被修改
   beanFactory.freezeConfiguration();

   //對配置了lazy-init屬性的單例模式的Bean進行預例項化處理
   beanFactory.preInstantiateSingletons();
}

其中ConfigurableListableBeanFactory是一個介面,preInstantiateSingletons()方法由其子類DefaultListableBeanFactory提供。

1.3. 對配置了lazy-init屬性的單例模式的Bean的預例項化

對配置了lazy-init屬性的單例模式的Bean的預例項化相關原始碼如下:

public void preInstantiateSingletons() throws BeansException {
   if (this.logger.isDebugEnabled()) {
      this.logger.debug("Pre-instantiating singletons in " + this);
   }

   List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

   for (String beanName : beanNames) {
      //獲取指定名稱的Bean定義
      RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
      //Bean不是抽象的,是單例模式的,且lazy-init屬性配置為false
      if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
         //如果指定名稱的Bean是建立容器的Bean
         if (isFactoryBean(beanName)) {
            //FACTORY_BEAN_PREFIX="&",當Bean名稱前面加"&"符號
            //時,獲取的是容器物件本身,而不是容器產生的Bean
            //呼叫getBean方法,觸發Bean例項化和依賴注入
            final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
            //標識是否需要預例項化
            boolean isEagerInit;
            if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
               //一個匿名內部類
               isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>) () ->
                     ((SmartFactoryBean<?>) factory).isEagerInit(),
                     getAccessControlContext());
            }
            else {
               isEagerInit = (factory instanceof SmartFactoryBean &&
                     ((SmartFactoryBean<?>) factory).isEagerInit());
            }
            if (isEagerInit) {
               //呼叫getBean()方法,觸發Bean例項化和依賴注入
               getBean(beanName);
            }
         }
         else {
            getBean(beanName);
         }
      }
   }

通過對lazy-init處理原始碼的分析可以看出,如果設定了lazy-init屬性,則容器在完成Bean定義的註冊之後,會通過getBean()方法觸發指定Bean的初始化和依賴注入。如前所述,這樣當應用程式第一次向容器索取所需的Bean時,容器不再需要對Bean進行初始化和依賴注入,可直接從已經完成例項化和依賴注入的Bean中取一個現成的Bean,提高了第一次獲取Bean的效能。

2 關於FactoryBean和BeanFactory

Spring中,有兩個很容易混淆的類:BeanFactory和FactoryBean。
BeanFactory:Bean工廠,是一個工廠(Factory),Spring IoC容器的最高層介面就是BeanFactory,它的作用是管理Bean,即例項化、定位、配置應用程式中的物件及建立這些物件之間的依賴。
FactoryBean:工廠Bean,是一個Bean,作用是產生其他Bean例項。這種Bean沒有什麼特別的要求,僅需要提供一個工廠方法,該方法用來返回其他Bean例項。在通常情況下,Bean無須自己實現工廠模式,Spring容器擔任工廠的角色;在少數情況下,容器中的Bean本身就是工廠,其作用是產生其他Bean例項。
當使用者使用容器時,可以使用轉義字元“&”來得到FactoryBean本身,以區別通過FactoryBean產生的例項物件和FactoryBean物件本身。在BeanFactory中通過如下程式碼定義了該轉義字元:
String FACTORY_BEAN_PREFIX = "&";

如果myJndiObject是一個FactoryBean,則使用&myJndiObject得到的是myJndiObject物件,而不是myJndiObject產生的物件。

2.1. FactoryBean原始碼

//工廠Bean,用於產生其他物件
public interface FactoryBean<T> {

   //獲取容器管理的物件例項
   @Nullable
   T getObject() throws Exception;

   //獲取Bean工廠建立的物件的型別
   @Nullable
   Class<?> getObjectType();

   //Bean工廠建立的物件是否是單例模式的,如果是,
   //則整個容器中只有一個例項物件,每次請求都返回同一個例項物件
   default boolean isSingleton() {
      return true;
   }

}

2.2. AbstractBeanFactory的getBean()方法

在分析Spring IoC容器例項化Bean並進行依賴注入的原始碼時,提到在getBean()方法觸發容器例項化Bean時會呼叫AbstractBeanFactory的doGetBean()方法,其重要原始碼如下:


protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
      @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
      ...
      BeanFactory parentBeanFactory = getParentBeanFactory();
      //當前容器的父容器存在,且當前容器中不存在指定名稱的Bean
      if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
         //解析指定Bean名稱的原始名稱
         String nameToLookup = originalBeanName(name);
         if (parentBeanFactory instanceof AbstractBeanFactory) {
            return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
                  nameToLookup, requiredType, args, typeCheckOnly);
         }
         else if (args != null) {
            //委派父容器根據指定名稱和顯式的引數查詢
            return (T) parentBeanFactory.getBean(nameToLookup, args);
         }
         else {
            //委派父容器根據指定名稱和型別查詢
            return parentBeanFactory.getBean(nameToLookup, requiredType);
         }
      }
   ...
   return (T) bean;
}

//獲取給定Bean的例項物件,主要完成FactoryBean的相關處理
protected Object getObjectForBeanInstance(
      Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

   //容器已經得到了Bean例項物件,這個例項物件可能是一個普通的Bean,
   //也可能是一個工廠Bean,如果是一個工廠Bean,則使用它建立一個Bean例項物件,
   //如果呼叫本身就想獲得一個容器的引用,則返回這個工廠Bean例項物件
   //如果指定的名稱是容器的解引用(dereference,即物件本身而非記憶體地址)
   //且Bean例項也不是建立Bean例項物件的工廠Bean
   if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
      throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
   }

   //如果Bean例項不是工廠Bean,或者指定名稱是容器的解引用
   //呼叫者獲取對容器的引用時,直接返回當前的Bean例項
   if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
      return beanInstance;
   }

   //處理指定名稱不是容器的解引用,或者根據名稱獲取的Bean例項物件是一個工廠Bean
   //使用工廠Bean建立一個Bean的例項物件
   Object object = null;
   if (mbd == null) {
      //從Bean工廠快取中獲取指定名稱的Bean例項物件
      object = getCachedObjectForFactoryBean(beanName);
   }
   //讓Bean工廠生產指定名稱的Bean例項物件
   if (object == null) {
      FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
      //如果從Bean工廠生產的Bean是單例模式的,則快取
      if (mbd == null && containsBeanDefinition(beanName)) {
         //從容器中獲取指定名稱的Bean定義,如果繼承了基類,則合併基類的相關屬性
         mbd = getMergedLocalBeanDefinition(beanName);
      }
      //如果從容器得到了Bean定義資訊,並且Bean定義資訊不是虛構的,
      //則讓工廠Bean生產Bean例項物件
      boolean synthetic = (mbd != null && mbd.isSynthetic());
      //呼叫FactoryBeanRegistrySupport類的getObjectFromFactoryBean()方法
      //實現工廠Bean生產Bean例項物件的過程
      object = getObjectFromFactoryBean(factory, beanName, !synthetic);
   }
   return object;
}

在上面獲取給定Bean的例項物件的getObjectForBeanInstance()方法中,會呼叫FactoryBean- RegistrySupport類的getObjectFromFactoryBean()方法,該方法實現了Bean工廠生產Bean例項物件。

2.3. AbstractBeanFactory生產Bean例項物件

AbstractBeanFactory類中生產Bean例項物件的主要原始碼如下:

//Bean工廠生產Bean例項物件
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
   //Bean工廠是單例模式,並且Bean工廠快取中存在指定名稱的Bean例項物件
   if (factory.isSingleton() && containsSingleton(beanName)) {
      //多執行緒同步,以防止資料不一致
      synchronized (getSingletonMutex()) {
         //直接從Bean工廠的快取中獲取指定名稱的Bean例項物件
         Object object = this.factoryBeanObjectCache.get(beanName);
         //如果Bean工廠快取中沒有指定名稱的例項物件,則生產該例項物件
         if (object == null) {
            //呼叫Bean工廠的獲取物件的方法生產指定Bean的例項物件
            object = doGetObjectFromFactoryBean(factory, beanName);
            Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
            if (alreadyThere != null) {
               object = alreadyThere;
            }
            else {
               if (shouldPostProcess) {
                  try {
                     object = postProcessObjectFromFactoryBean(object, beanName);
                  }
                  catch (Throwable ex) {
                     throw new BeanCreationException(beanName,
                           "Post-processing of FactoryBean's singleton object failed", ex);
                  }
               }
               //將生產的例項物件新增到Bean工廠的快取中
               this.factoryBeanObjectCache.put(beanName, object);
            }
         }
         return object;
      }
   }
   //呼叫Bean工廠的獲取物件的方法生產指定Bean的例項物件
   else {
      Object object = doGetObjectFromFactoryBean(factory, beanName);
      if (shouldPostProcess) {
         try {
            object = postProcessObjectFromFactoryBean(object, beanName);
         }
         catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
         }
      }
      return object;
   }
}

//呼叫Bean工廠的方法生產指定Bean的例項物件
private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)
      throws BeanCreationException {

   Object object;
   try {
      if (System.getSecurityManager() != null) {
         AccessControlContext acc = getAccessControlContext();
         try {
            //實現PrivilegedExceptionAction介面的匿名內部類
            object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () ->
                  factory.getObject(), acc);
         }
         catch (PrivilegedActionException pae) {
            throw pae.getException();
         }
      }
      else {
         //呼叫BeanFactory介面實現類的建立物件方法
         object = factory.getObject();
      }
   }
   catch (FactoryBeanNotInitializedException ex) {
      throw new BeanCurrentlyInCreationException(beanName, ex.toString());
   }
   catch (Throwable ex) {
      throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
   }

   //建立出來的例項物件為null,或者因為單例物件正在建立而返回null
   if (object == null) {
      if (isSingletonCurrentlyInCreation(beanName)) {
         throw new BeanCurrentlyInCreationException(
               beanName, "FactoryBean which is currently in creation returned null from getObject");
      }
      object = new NullBean();
   }
   return object;
}

從上面的原始碼分析中可以看出,BeanFactory介面呼叫其實現類的獲取物件的方法來實現建立Bean例項物件的功能。

2.4. FactoryBean實現類的獲取物件的方法

FactoryBean介面的實現類非常多,比如Proxy、RMI、JNDI、ServletContextFactoryBean等。FactoryBean介面為Spring容器提供了一個很好的封裝機制,具體的獲取物件的方法由不同的實現類根據不同的實現策略來提供,我們分析一下最簡單的AnnotationTestFactoryBean類的原始碼:

public class AnnotationTestBeanFactory implements FactoryBean<FactoryCreatedAnnotationTestBean> {
   private final FactoryCreatedAnnotationTestBean instance = new FactoryCreatedAnnotationTestBean();
   public AnnotationTestBeanFactory() {
      this.instance.setName("FACTORY");
   }
   @Override
   public FactoryCreatedAnnotationTestBean getObject() throws Exception {
      return this.instance;
   }
   //AnnotationTestBeanFactory產生Bean例項物件的實現
   @Override
   public Class<? extends IJmxTestBean> getObjectType() {
      return FactoryCreatedAnnotationTestBean.class;
   }
   @Override
   public boolean isSingleton() {
      return true;
   }
}

Proxy、RMI、JNDI等其他實現類都根據相應的策略提供方法,這裡不做一一分析,這已經不是Spring的核心功能,感興趣的“小夥伴”可以自行深入研究。

3 再述autowiring

Spring IoC容器提供了兩種管理Bean依賴關係的方式:
(1)顯式管理:通過BeanDefinition的屬性值和構造方法實現Bean依賴關係管理。
(2)autowiring:Spring IoC容器有依賴自動裝配功能,不需要對Bean屬性的依賴關係做顯式的宣告,只需要配置好autowiring屬性,IoC容器會自動使用反射查詢屬性的型別和名稱,然後基於屬性的型別或者名稱來自動匹配容器中的Bean,從而自動完成依賴注入。
容器對Bean的自動裝配發生在容器對Bean依賴注入的過程中。在對Spring IoC容器的依賴注入原始碼進行分析時,我們已經知道容器對Bean例項物件的依賴屬性注入發生在AbstractAutoWireCapableBeanFactory類的populateBean()方法中,下面通過程式流程分析autowiring的實現原理。

3.1. AbstractAutoWireCapableBeanFactory對Bean例項物件進行屬性依賴注入

應用程式第一次通過getBean()方法(配置了lazy-init預例項化屬性的除外)向IoC容器索取Bean時,容器建立Bean例項物件,並且對Bean例項物件進行屬性依賴注入,AbstractAutoWire- CapableBeanFactory的populateBean()方法就實現了屬性依賴注入的功能,其主要原始碼如下:

//將Bean屬性設定到生成的例項物件上
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
…
   //獲取容器在解析Bean定義時為BeanDefinition設定的屬性值
   PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

   //處理依賴注入,首先處理autowiring自動裝配的依賴注入
   if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
         mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
      MutablePropertyValues newPvs = new MutablePropertyValues(pvs);

      //根據Bean名稱進行autowiring自動裝配處理
      if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
         autowireByName(beanName, mbd, bw, newPvs);
      }

      //根據Bean型別進行autowiring自動裝配處理
      if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
         autowireByType(beanName, mbd, bw, newPvs);
      }

      pvs = newPvs;
   }

   //對非autowiring的屬性進行依賴注入處理
   ...
}

3.2. Spring IoC容器根據Bean名稱或者型別進行autowiring自動屬性依賴注入

Spring IoC容器根據Bean名稱或者型別進行autowiring自動屬性依賴注入的重要程式碼如下:

//根據型別對屬性進行自動依賴注入
protected void autowireByType(
      String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {

   //獲取使用者定義的型別轉換器
   TypeConverter converter = getCustomTypeConverter();
   if (converter == null) {
      converter = bw;
   }

   //存放解析的要注入的屬性
   Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
   //對Bean物件中非簡單屬性(不是簡單繼承的物件,如8種原始型別、字元、URL等都是簡單屬性)進行處理
   String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
   for (String propertyName : propertyNames) {
      try {
         //獲取指定屬性名稱的屬性描述器
         PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
         //不對Object型別的屬性進行autowiring自動依賴注入
         if (Object.class != pd.getPropertyType()) {
            //獲取屬性的賦值方法
            MethodParameter MethodParam = BeanUtils.getWriteMethodParameter(pd);
            //檢查指定型別是否可以被轉換為目標物件的型別
            boolean eager = !PriorityOrdered.class.isInstance(bw.getWrappedInstance());
            //建立一個要被注入的依賴描述
            DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(MethodParam, eager);
            //根據容器的Bean定義解析依賴關係,返回所有要被注入的Bean物件
            Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);
            if (autowiredArgument != null) {
               //將屬性賦值為所引用的物件
               pvs.add(propertyName, autowiredArgument);
            }
            for (String autowiredBeanName : autowiredBeanNames) {
               //為指定名稱屬性註冊依賴Bean名稱,進行屬性的依賴注入
               registerDependentBean(autowiredBeanName, beanName);
               if (logger.isDebugEnabled()) {
                  logger.debug("Autowiring by type from bean name '" + beanName + "' via property '" 
                        + propertyName + "' to bean named '" + autowiredBeanName + "'");
               }
            }
            //釋放已自動注入的屬性
            autowiredBeanNames.clear();
         }
      }
      catch (BeansException ex) {
         throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);
      }
   }
}

通過上面的原始碼分析可以看出,通過屬性名進行自動依賴注入相比通過屬性型別進行自動依賴注入要稍微簡單一些。但是真正實現屬性注入的是DefaultSingletonBeanRegistry類的registerDependentBean()方法。

3.3. DefaultSingletonBeanRegistry的registerDependentBean()方法實現屬性依賴注入

DefaultSingletonBeanRegistry的registerDependentBean()方法實現屬性依賴注入的重要程式碼如下:

//為指定的Bean注入依賴的Bean
public void registerDependentBean(String beanName, String dependentBeanName) {
   //處理Bean名稱,將別名轉換為規範的Bean名稱
   String canonicalName = canonicalName(beanName);
   Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
   if (dependentBeans != null && dependentBeans.contains(dependentBeanName)) {
      return;
   }

   //多執行緒同步,保證容器內資料的一致性
   //在容器中通過“Bean名稱→全部依賴Bean名稱集合”查詢指定名稱Bean的依賴Bean
   synchronized (this.dependentBeanMap) {
      //獲取指定名稱Bean的所有依賴Bean名稱
      dependentBeans = this.dependentBeanMap.get(canonicalName);
      if (dependentBeans == null) {
         //為Bean設定依賴Bean資訊
         dependentBeans = new LinkedHashSet<>(8);
         this.dependentBeanMap.put(canonicalName, dependentBeans);
      }
      //在向容器中通過“Bean名稱→全部依賴Bean名稱集合”新增Bean的依賴資訊
      //即,將Bean所依賴的Bean新增到容器的集合中
      dependentBeans.add(dependentBeanName);
   }
   //在容器中通過“Bean名稱→指定名稱Bean的依賴Bean集合”查詢指定名稱Bean的依賴Bean
   synchronized (this.dependenciesForBeanMap) {
      Set<String> dependenciesForBean = this.dependenciesForBeanMap.get(dependentBeanName);
      if (dependenciesForBean == null) {
         dependenciesForBean = new LinkedHashSet<>(8);
         this.dependenciesForBeanMap.put(dependentBeanName, dependenciesForBean);
      }
      //在容器中通過“Bean名稱→指定Bean的依賴Bean名稱集合”新增Bean的依賴資訊
      //即,將Bean所依賴的Bean新增到容器的集合中
      dependenciesForBean.add(canonicalName);
   }
}

可以看出,autowiring的實現過程如下:
(1)對Bean的屬性呼叫getBean()方法,完成依賴Bean的初始化和依賴注入。
(2)將依賴Bean的屬性引用設定到被依賴的Bean屬性上。
(3)將依賴Bean的名稱和被依賴Bean的名稱儲存在IoC容器的集合中。
Spring IoC容器的autowiring自動屬性依賴注入是一個很方便的特性,可以簡化開發配置,但是凡事都有兩面性,自動屬性依賴注入也有不足:首先,Bean的依賴關係在配置檔案中無法很清楚地看出來,會給維護造成一定的困難;其次,由於自動屬性依賴注入是Spring容器自動執行的,容器是不會智慧判斷的,如果配置不當,將會帶來無法預料的後果。所以在使用自動屬性依賴注入時需要綜合考慮。

本文為“Tom彈架構”原創,轉載請註明出處。技術在於分享,我分享我快樂!
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支援是我堅持創作的動力。

原創不易,堅持很酷,都看到這裡了,小夥伴記得點贊、收藏、在看,一鍵三連加關注!如果你覺得內容太乾,可以分享轉發給朋友滋潤滋潤!

相關文章