Spring IOC和AOP

1、IOC

概念:所謂控制反轉(zhuǎn),就是把原先我們代碼里面需要實(shí)現(xiàn)的對象創(chuàng)建、依賴的代碼,反轉(zhuǎn)給容器來幫忙實(shí)現(xiàn)。當(dāng)應(yīng)用了IoC,一個對象依賴的其它對象會通過被動的方式傳遞進(jìn)來,而不是這個對象自己創(chuàng)建或者查找依賴對象。

Spring 啟動時讀取應(yīng)用程序提供的Bean配置信息,并在Spring容器中生成一份相應(yīng)的Bean配置注冊表,然后根據(jù)這張注冊表實(shí)例化Bean,裝配好Bean之間的依賴關(guān)系,為上層應(yīng)用提供準(zhǔn)備就緒的運(yùn)行環(huán)境。

image.png

1.1 底層實(shí)現(xiàn):

image.png

如果是xml文件,那么需要解析xml文件;如果是注解,需要通過反射獲取注解,然后根據(jù)獲取到的bean信息通過反射實(shí)例化bean,實(shí)例化之后將bean放到spring容器的bean緩存池中(hashMap),當(dāng)要使用bean時,可以通過applicationContext獲取bean(getBean)。

1.2 spring ioc autowired如何實(shí)現(xiàn)

@Autowired表示被修飾的類需要注入對象,spring會掃描所有被@Autowired標(biāo)注的類,然后根據(jù) 類型type 在ioc容器中找到匹配的類注入

@Autowired VS @Resource
  • 提供方:@Autowired是由Spring提供;
    @Resource是由javax.annotation.Resource提供,即J2EE提供,需要JDK1.6及以上;
  • 注入方式:@Autowired只按照byType 注入;
    @Resource默認(rèn)按byName自動注入,也提供按照byType 注入;

1.3 @component

普通pojo實(shí)例化到spring容器中,相當(dāng)于配置文件中的<bean id="" class=""/>
雖然有了@Autowired,但是我們還是要寫一堆bean的配置文件,相當(dāng)麻煩,而@Component就是告訴spring,我是pojo類,把我注冊到容器中吧,spring會自動提取相關(guān)信息。那么我們就不用寫麻煩的xml配置文件了。

https://zhuanlan.zhihu.com/p/29344811

1.4 bean生命周期

image.png
  • 1.4.1.當(dāng)調(diào)用者通過 getBean(beanName)向容器請求某一個 Bean 時,如果容器注冊了org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor 接口,在實(shí)例化 Bean 之前,將調(diào)用接口的 postProcessBeforeInstantiation()方法;
  • 1.4.2.根據(jù)配置情況調(diào)用 Bean 構(gòu)造函數(shù)或工廠方法實(shí)例化 Bean;
  • 1.4.3.如果容器注冊了 InstantiationAwareBeanPostProcessor 接口,在實(shí)例化 Bean 之后,調(diào)用該接口的 postProcessAfterInstantiation()方法,可在這里對已經(jīng)實(shí)例化的對象進(jìn)行一些“梳妝打扮”;
  • 1.4.4.如果 Bean 配置了屬性信息,容器在這一步著手將配置值設(shè)置到 Bean 對應(yīng)的屬性中,不過在設(shè)置每個屬性之前將先調(diào)用InstantiationAwareBeanPostProcessor 接口的postProcessPropertyValues()方法;
  • 1.4.5.調(diào)用 Bean 的屬性設(shè)置方法設(shè)置屬性值;
  • 1.4.6.如果 Bean 實(shí)現(xiàn)了 org.springframework.beans.factory.BeanNameAware 接口,將調(diào)用setBeanName()接口方法,將配置文件中該 Bean 對應(yīng)的名稱設(shè)置到 Bean 中;
  • 1.4.7.如果 Bean 實(shí)現(xiàn)了 org.springframework.beans.factory.BeanFactoryAware 接口,將調(diào)用 setBeanFactory()接口方法,將 BeanFactory 容器實(shí)例設(shè)置到 Bean 中;
  • 1.4.8.如果 BeanFactory 裝配了 org.springframework.beans.factory.config.BeanPostProcessor后處理器,將調(diào)用 BeanPostProcessor 的 Object postProcessBeforeInitialization(Object bean, String beanName)接口方法對 Bean 進(jìn)行加工操作。其中入?yún)?bean 是當(dāng)前正在處理的 Bean,而 beanName 是當(dāng)前 Bean 的配置名,返回的對象為加工處理后的 Bean。用戶可以使用該方法對某些 Bean 進(jìn)行特殊的處理,甚至改變 Bean 的行為, BeanPostProcessor 在 Spring 框架中占有重要的地位,為容器提供對 Bean 進(jìn)行后續(xù)加工處理的切入點(diǎn), Spring 容器所提供的各種“神奇功能”(如 AOP,動態(tài)代理等)都通過 BeanPostProcessor 實(shí)施;
  • 1.4.9.如果 Bean 實(shí)現(xiàn)了 InitializingBean 的接口,將調(diào)用接口的 afterPropertiesSet()方法;
  • 1.4.10.如果在<bean>通過 init-method 屬性定義了初始化方法,將執(zhí)行這個方法;
  • 1.4.11.BeanPostProcessor 后處理器定義了兩個方法:其一是 postProcessBeforeInitialization() 在第 8 步調(diào)用;其二是 Object postProcessAfterInitialization(Object bean, String beanName)方法,這個方法在此時調(diào)用,容器再次獲得對 Bean 進(jìn)行加工處理的機(jī)會;
  • 1.4.12.如果在<bean>中指定 Bean 的作用范圍為 scope=“prototype”,將 Bean 返回給調(diào)用者,調(diào)用者負(fù)責(zé) Bean 后續(xù)生命的管理, Spring 不再管理這個 Bean 的生命周期。如果作用范圍設(shè)置為 scope=“singleton”,則將 Bean 放入到 Spring IoC 容器的緩存池中,并將 Bean引用返回給調(diào)用者, Spring 繼續(xù)對這些 Bean 進(jìn)行后續(xù)的生命管理;
  • 1.4.13.對于 scope=“singleton”的 Bean,當(dāng)容器關(guān)閉時,將觸發(fā) Spring 對 Bean 的后續(xù)生命周期的管理工作,首先如果 Bean 實(shí)現(xiàn)了 DisposableBean 接口,則將調(diào)用接口的afterPropertiesSet()方法,可以在此編寫釋放資源、記錄日志等操作;
  • 1.4.14.對于 scope=“singleton”的 Bean,如果通過<bean>的 destroy-method 屬性指定了 Bean 的銷毀方法, Spring 將執(zhí)行 Bean 的這個方法,完成 Bean 資源的釋放等操作。

可以將這些方法大致劃分為三類:

  • Bean 自身的方法:如調(diào)用 Bean 構(gòu)造函數(shù)實(shí)例化 Bean,調(diào)用 Setter 設(shè)置 Bean 的屬性值以及通過<bean>的 init-method 和 destroy-method 所指定的方法;

  • Bean 級生命周期接口方法:如 BeanNameAware、 BeanFactoryAware、 InitializingBean 和 DisposableBean,這些接口方法由 Bean 類直接實(shí)現(xiàn);

  • 容器級生命周期接口方法:在上圖中帶“★” 的步驟是由 InstantiationAwareBean PostProcessor 和 BeanPostProcessor 這兩個接口實(shí)現(xiàn),一般稱它們的實(shí)現(xiàn)類為“ 后處理器” 。 后處理器接口一般不由 Bean 本身實(shí)現(xiàn),它們獨(dú)立于 Bean,實(shí)現(xiàn)類以容器附加裝置的形式注冊到 Spring 容器中并通過接口反射為 Spring 容器預(yù)先識別。當(dāng)Spring 容器創(chuàng)建任何 Bean 的時候,這些后處理器都會發(fā)生作用,所以這些后處理器的影響是全局性的。當(dāng)然,用戶可以通過合理地編寫后處理器,讓其僅對感興趣Bean 進(jìn)行加工處理。

1.5 ApplicationContext 與 BeanFactory 區(qū)別

  • BeanFactory:是Spring里面最低層的接口,提供了最簡單的容器的功能,只提供了實(shí)例化對象和拿對象的功能;

  • ApplicationContext:應(yīng)用上下文,繼承BeanFactory接口,它是Spring的一各更高級的容器,提供了更多的有用的功能;

    1. 國際化(MessageSource)
    2. 訪問資源,如URL和文件(ResourceLoader)
    3. 載入多個(有繼承關(guān)系)上下文 ,使得每一個上下文都專注于一個特定的層次,比如應(yīng)用的web層
    4. 消息發(fā)送、響應(yīng)機(jī)制(ApplicationEventPublisher)
    5. AOP(攔截器)

同時ApplicationContext會利用 Java 反射機(jī)制自動識別出配置文件中定義的 BeanPostProcessor、 InstantiationAwareBeanPostProcessor 和 BeanFactoryPostProcessor,并自動將它們注冊到應(yīng)用上下文中;
而BeanFactory 需要在代碼中通過手工調(diào)用 addBeanPostProcessor()方法進(jìn)行注冊。
這也是為什么在應(yīng)用開發(fā)時,我們普遍使用 ApplicationContext 而很少使用 BeanFactory 的原因之一。

1.6 Spring的bean的存儲和管理機(jī)制

1、讀取config.xml文件的bean標(biāo)簽放入數(shù)組,讀取內(nèi)容包含的id和class。
2、循環(huán)數(shù)組并根據(jù)class路徑利用反射機(jī)制實(shí)例化Bean(實(shí)例化bean的過程就是上面),并放入Map(spring容器中的Bean緩存池)。
3、根據(jù)傳入的BeanId獲取Map中對應(yīng)的bean實(shí)例。

1.7 Spring bean獲取方式

從上面我們知道bean是存儲在hashMap中的,其中key是beanId,vlaue是bean實(shí)例;

bean獲取方式其實(shí)就是從應(yīng)用上下文ApplicationContext的HashMap中根據(jù)key獲取value的過程。
方式一:Object getBean(String name)
方式二:<T> T getBean(String name, Class<T> requiredType)
方式一和方式二的區(qū)別只是方式二自動做了類型轉(zhuǎn)換。

1.8 ApplicationContext 獲取方式

方式一:讀取xml文件獲取ApplicationContext對象
ApplicationContext ac = new FileSystemXmlApplicationContext("applicationContext.xml");

方式二:通過Spring提供的工具類獲取ApplicationContext對象
ApplicationContext ac2 = WebApplicationContextUtils.getWebApplicationContext(ServletContext sc);

方式三:新建一個springUtils工具類,且實(shí)現(xiàn)ApplicationContextAware接口
上面bean實(shí)例化時的第五步,如果bean實(shí)現(xiàn)了ApplicationContextAware接口,它的setApplicationContext()方法將被調(diào)用,將應(yīng)用上下文的引用傳入到bean中;
通過這種方式可以把ApplicationContext傳入到springUtils中,然后再springUtils中就可以使用applicationContext.getBean()來獲取bean了

方式三是最推薦使用的

1.9 spring懶加載:

Spring默認(rèn)會在容器初始化的過程中,解析xml,并將單例的bean創(chuàng)建并保存到map中,這樣的機(jī)制在bean比較少時問題不大,但一旦bean非常多時,spring需要在啟動的過程中花費(fèi)大量的時間來創(chuàng)建bean 花費(fèi)大量的空間存儲bean,但這些bean可能很久都用不上,這種在啟動時在時間和空間上的浪費(fèi)顯得非常的不值得。
所以Spring提供了懶加載機(jī)制。所謂的懶加載機(jī)制就是可以規(guī)定指定的bean不在啟動時立即創(chuàng)建,而是在后續(xù)第一次用到時才創(chuàng)建,從而減輕在啟動過程中對時間和內(nèi)存的消耗。

1.10 spring bean 注冊到 IOC容器的過程

  • 1、讀取bean配置信息(通過xml中<bean> ,或者通過注解@Autowrite @Configuration),根據(jù)配置信息,在容器內(nèi)部建立Bean定義注冊表;
  • 2、根據(jù)bean注冊表實(shí)例化bean;
  • 3、將bean實(shí)例化并將其放入hashMap;
  • 4、獲取并使用bean

1.11 spring 循環(huán)依賴

spring 常用的注入方式有三種:構(gòu)造方法注入,setter注入,基于注解的注入。

所謂循環(huán)依賴,當(dāng)我們注入一個對象A時,需要注入對象A中標(biāo)記了某些注解的屬性,這些屬性也就是對象A的依賴,把對象A中的依賴都初始化完成,對象A才算是創(chuàng)建成功。那么,如果對象A中有個屬性是對象B,而且對象B中有個屬性是對象A,那么對象A和對象B就算是循環(huán)依賴,如果不加處理,就會出現(xiàn):創(chuàng)建對象A-->處理A的依賴B-->創(chuàng)建對象B-->處理B的對象A-->創(chuàng)建對象A,這樣無限的循環(huán)下去,我們開發(fā)過程中有時也會遇到這種問題,那么spring框架本身是怎么解決這個問題的呢?

從上面bean的生命周期中,我們發(fā)現(xiàn)bean實(shí)例化(構(gòu)造器)和setter設(shè)置屬性并不是同時發(fā)生的,這個其實(shí)就是解決循環(huán)依賴的依據(jù)。

當(dāng)我們初始化一個Bean時,先調(diào)用Bean的構(gòu)造方法,這個對象就在內(nèi)存中存在了(對象里面的依賴還沒有被注入),然后把這個對象保存下來,當(dāng)循環(huán)依賴產(chǎn)生時,直接拿到之前保存的對象,于是循環(huán)依賴就被終止了,依賴注入也就順利完成了。

image.png
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//先存singletonObjects中獲取bean,
        Object singletonObject = this.singletonObjects.get(beanName);
//如果bean不存在,并且bean正在創(chuàng)建中
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
//從earlySingletonObjects中獲取
                singletonObject = this.earlySingletonObjects.get(beanName);
//如果earlySingletonObjects不存在(allowEarlyReference默認(rèn)為true)
                if (singletonObject == null && allowEarlyReference) {
//獲取singletonFactories
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
//從singletonFactories中獲取bean
                        singletonObject = singletonFactory.getObject();
//添加到earlySingletonObjects
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
public boolean isSingletonCurrentlyInCreation(String beanName) {
        return this.singletonsCurrentlyInCreation.contains(beanName);
    }
  • singletonObjects:緩存key = beanName, value = bean;這里的bean是已經(jīng)創(chuàng)建完成的,該bean經(jīng)歷過實(shí)例化->屬性填充->初始化以及各類的后置處理。因此,一旦需要獲取bean時,我們第一時間就會尋找一級緩存

  • earlySingletonObjects:緩存key = beanName, value = bean;這里跟一級緩存的區(qū)別在于,該緩存所獲取到的bean是提前曝光出來的,是還沒創(chuàng)建完成的。也就是說獲取到的bean只能確保已經(jīng)進(jìn)行了實(shí)例化,但是屬性填充跟初始化還沒有做完(AOP情況后續(xù)分析),因此該bean還沒創(chuàng)建完成,僅僅能作為指針提前曝光,被其他bean所引用

  • singletonFactories:該緩存key = beanName, value = beanFactory;在bean實(shí)例化完之后,屬性填充以及初始化之前,如果允許提前曝光,spring會將實(shí)例化后的bean提前曝光,也就是把該bean轉(zhuǎn)換成beanFactory并加入到三級緩存。在需要引用提前曝光對象時再通過singletonFactory.getObject()獲取。

在整個getbean過程中,singletonObjects、earlySingletonObjects、singletonFactories中對象變化如下

    1. 開始初始化對象A
      singletonFactories:
      earlySingletonObjects:
      singletonObjects:
    1. 調(diào)用A的構(gòu)造器實(shí)例化A,并把A放入singletonFactories
      singletonFactories:A
      earlySingletonObjects:
      singletonObjects:
    1. 開始注入A的依賴,發(fā)現(xiàn)A依賴對象B之后,初始化對象B(同樣是調(diào)用B的構(gòu)造器實(shí)例化B,并把B放入singletonFactories)
      singletonFactories:A,B
      earlySingletonObjects:
      singletonObjects:
    1. 開始注入B的依賴,發(fā)現(xiàn)B依賴對象A,開始初始化對象A,發(fā)現(xiàn)A在singletonFactories里有,則直接獲取A,把A放入earlySingletonObjects(提前曝光),把A從singletonFactories刪除
      singletonFactories:B
      earlySingletonObjects:A
      singletonObjects:
    1. 對象B的依賴注入完成之后,對象B創(chuàng)建完成,把B放入singletonObjects,并且把B從earlySingletonObjects和singletonFactories中刪除
      singletonFactories:
      earlySingletonObjects:A
      singletonObjects:B
    1. 繼續(xù)A的初始化,把對象B注入給A,繼續(xù)注入A的其他依賴,直到A注入完成
      singletonFactories:
      earlySingletonObjects:A
      singletonObjects:B
    1. 對象A創(chuàng)建完成,把A放入singletonObjects,并把A從earlySingletonObjects和singletonFactories中刪除
      singletonFactories:
      earlySingletonObjects:
      singletonObjects:A,B
    1. 循環(huán)依賴處理結(jié)束,A和B都初始化和注入完成
      singletonFactories:
      earlySingletonObjects:
      singletonObjects:A,B

解決辦法:如果項(xiàng)目中出現(xiàn)了循環(huán)依賴,則使用setter注入替代構(gòu)造器注入。

2、AOP

2.1 概念

AOP的全稱是Aspect Orient Programming,即面向切面編程,擴(kuò)展功能不通過修改源代碼實(shí)現(xiàn)。

2.2實(shí)現(xiàn)方式

  • 2.2.1 JDK 動態(tài)代理(必須有接口)
    通過java.lang.reflect.Proxy類實(shí)現(xiàn)。
    動態(tài)代理就是為了解決靜態(tài)代理不靈活的缺陷而產(chǎn)生的。靜態(tài)代理是固定的,一旦確定了代碼,如果委托類新增一個方法,而這個方法又需要增強(qiáng),那么就必須在代理類里重寫一個帶增強(qiáng)的方法。而動態(tài)代理可以靈活替換代理方法,動態(tài)就是體現(xiàn)在這里。同時,靜態(tài)代理每個方法都需要單獨(dú)寫一個代理類,而動態(tài)代理一個接口只實(shí)現(xiàn)一個動態(tài)代理類即可。

設(shè)計模式中,有一種

  • 2.2.2 實(shí)現(xiàn)
Moveable move = (Moveable) Proxy.newProxyInstance(Car.class.getClassLoader(), Car.class.getInterfaces(), new LogHandler(new Car()));

使用JDK的動態(tài)代理去生成代理只需要一行代碼,傳入的參數(shù)中其實(shí)就倆,一是被代理類的類對象,二是自定義的增強(qiáng)處理代碼。
從上面的例子中可以看出,動態(tài)代理除了接受Car類型的目標(biāo)對象,還可以接受任何其他類型的對象;也不管目標(biāo)對象實(shí)現(xiàn)的接口有多少方法,都可以被代理。


public class LogHandler implements InvocationHandler{ 
 
 private Object target; 
 
 public LogHandler(Object object){
   super();
   this.target = object;
 }
 
 //增強(qiáng)處理
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
   Object o = method.invoke(target,args);
   return o;
 }
}

image.png
  • 2.2.2 cglib 動態(tài)代理(不需要類繼承任何接口,字節(jié)碼技術(shù))
public class Plane {

    public void fly(long ms) {
        System.out.println("plane is flying!");
        try {
            Thread.sleep(ms);
        } catch (Exception e) {

        }
    }
}

public class CglibProxy implements MethodInterceptor {
    private Object target;

    public CglibProxy(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        //1. 實(shí)例化工具類
        Enhancer en = new Enhancer();
        //2. 設(shè)置父類對象
        en.setSuperclass(this.target.getClass());
        //3. 設(shè)置回調(diào)函數(shù)
        en.setCallback(this);
        //4. 創(chuàng)建子類,也就是代理對象
        return en.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before invoke ");
        long begin = System.currentTimeMillis();

        //執(zhí)行目標(biāo)對象的方法
        Object returnValue = method.invoke(target, objects);

        long end = System.currentTimeMillis();
        System.out.println("after invoke elpased " + (end - begin));

        return returnValue;
    }

}

public static void main() {
    CglibProxy cglibProxy = new CglibProxy(new Plane()); 
    Plane plane = (Plane) cglibProxy.getProxyInstance();             
    plane.fly(150);
}

2.4使用場景:

    1. Logging 日志
    1. Authentication 權(quán)限
    1. Transactions 事務(wù)
    1. Context passing 內(nèi)容傳遞
    1. Error handling 錯誤處理
    1. Lazy loading 懶加載
    1. Debugging 調(diào)試
    1. logging,tracing,profiling and monitoring 記錄跟蹤 優(yōu)化 校準(zhǔn)
    1. Performance optimization 性能優(yōu)化
    1. Persistence 持久化
    1. Resource pooling 資源池
    1. Synchronization 同步
    1. Caching 緩存

2.5 在Spring的AOP編程中

如果加入容器的目標(biāo)對象有實(shí)現(xiàn)接口,就使用JDK代理
如果目標(biāo)對象沒有實(shí)現(xiàn)接口,就使用Cglib代理。

3、反射:

反射是Java的特征之一,是一種間接操作目標(biāo)對象的機(jī)制,核心是JVM在運(yùn)行的時候才動態(tài)加載類,并且對于任意一個類,都能夠知道這個類的所有屬性和方法,調(diào)用方法/訪問屬性,不需要提前在編譯期知道運(yùn)行的對象是誰,他允許運(yùn)行中的Java程序獲取類的信息,并且可以操作類或?qū)ο髢?nèi)部屬性。

程序中對象的類型一般都是在編譯期就確定下來的,而當(dāng)我們的程序在運(yùn)行時,可能需要動態(tài)的加載一些類,這些類因?yàn)橹坝貌坏?,所以沒有加載到j(luò)vm,這時,使用Java反射機(jī)制可以在運(yùn)行期動態(tài)的創(chuàng)建對象并調(diào)用其屬性,它是在運(yùn)行時根據(jù)需要才加載。

3.1 new和反射創(chuàng)建有什么區(qū)別

new:靜態(tài)編譯,在編譯期就將模塊編譯進(jìn)來,執(zhí)行該字節(jié)碼文件,所有的模塊都被加載;
反射:動態(tài)編譯,編譯期沒有加載,等到模塊被調(diào)用時才加載;

3.2 反射的作用

  • 在運(yùn)行時判斷任意一個對象所屬的類;
  • 在運(yùn)行時構(gòu)造任意一個類的對象;
  • 在運(yùn)行時判斷任意一個類所具有的成員變量和方法;
  • 在運(yùn)行時調(diào)用任意一個對象的方法;

3.3 反射的實(shí)現(xiàn)

要使用一個類,就要先把它加載到虛擬機(jī)中,生成一個Class對象。這個class對象就保存了這個類的一切信息。

反射機(jī)制的實(shí)現(xiàn),就是獲取這個Class對象,通過Class對象去訪問類、對象的元數(shù)據(jù)以及運(yùn)行時的數(shù)據(jù)。

//通過反射機(jī)制創(chuàng)建class對象
class1 = Class.forName(className);
//在運(yùn)行時,通過創(chuàng)建的class對象,獲取自己的父類信息
Class<?> parentClass = class1.getSuperclass();
//通過反射機(jī)制創(chuàng)建一個類的對象
Classname 對象=class1.newInstance(參數(shù));
//取得本類已聲明的所有字段,包括私有的、保護(hù)的
Field[] field = class1.getDeclaredFields();
//返回一個 Method 對象,它反映此 Class 對象所表示的類或接口的指定公共成員方法。
Method method = clazz.getMethod(方法名,參數(shù)類型);
//調(diào)用具體某個實(shí)例對象的這個公有方法
method.invoke(實(shí)例對象,參數(shù)值);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容