AOP從靜態(tài)代理到動態(tài)代理的示例分析-創(chuàng)新互聯(lián)

這篇文章主要為大家展示了“AOP從靜態(tài)代理到動態(tài)代理的示例分析”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“AOP從靜態(tài)代理到動態(tài)代理的示例分析”這篇文章吧。

創(chuàng)新互聯(lián)公司從2013年創(chuàng)立,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項目成都網(wǎng)站設(shè)計、做網(wǎng)站網(wǎng)站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元肇州做網(wǎng)站,已為上家服務(wù),為肇州各地企業(yè)和個人服務(wù),聯(lián)系電話:028-86922220

【前言】

AOP(Aspect Orient Programming),我們一般稱為面向方面(切面)編程,作為面向?qū)ο蟮囊环N補充,用于處理系統(tǒng)中分布于各個模塊的橫切關(guān)注點,比如事務(wù)管理、日志、緩存等等。AOP實現(xiàn)的關(guān)鍵在于AOP框架自動創(chuàng)建的AOP代理,AOP代理主要分為靜態(tài)代理和動態(tài)代理,靜態(tài)代理的代表為AspectJ;而動態(tài)代理則以Spring AOP為代表。

何為切面?

一個和業(yè)務(wù)沒有任何耦合相關(guān)的代碼段,諸如:調(diào)用日志,發(fā)送郵件,甚至路由分發(fā)。一切能為代碼所有且能和代碼充分解耦的代碼都可以作為一個業(yè)務(wù)代碼的切面。

我們?yōu)槭裁匆狝OP?

那我們從一個場景舉例說起:

如果想要采集用戶操作行為,我們需要掌握用戶調(diào)用的每一個接口的信息。這時候的我們要怎么做?

如果不采用AOP技術(shù),也是最簡單的,所有方法體第一句話先調(diào)用一個日志接口將方法信息傳遞記錄。

有何問題?

實現(xiàn)業(yè)務(wù)沒有任何問題,但是隨之而來的是代碼臃腫不堪,難以調(diào)整維護的諸多問題(可自行腦補)。

如果我們采用了AOP技術(shù),我們就可以在系統(tǒng)啟動的地方將所有將要采集日志的類注入,每一次調(diào)用方法前,AOP框架會自動調(diào)用我們的日志代碼。

是不是省去了很多重復(fù)無用的勞動?代碼也將變得非常好維護(有朝一日不需要了,只需將切面代碼注釋掉即可)

接下來我們看看AOP框架的工作原理以及實過程。

【實現(xiàn)思路】

AOP框架呢,一般通過靜態(tài)代理和動態(tài)代理兩種實現(xiàn)方式。

  AOP從靜態(tài)代理到動態(tài)代理的示例分析

何為靜態(tài)代理?

靜態(tài)代理,又叫編譯時代理,就是在編譯的時候,已經(jīng)存在代理類,運行時直接調(diào)用的方式。說的通俗一點,就是自己手動寫代碼實現(xiàn)代理類的方式。

我們通過一個例子來展現(xiàn)一下靜態(tài)代理的實現(xiàn)過程:

我們這里有一個業(yè)務(wù)類,里面有方法Test(),我們要在Test調(diào)用前和調(diào)用后分別輸出日志。

AOP從靜態(tài)代理到動態(tài)代理的示例分析

我們既然要將Log當(dāng)作一個切面,我們肯定不能去動原有的業(yè)務(wù)代碼,那樣也違反了面向?qū)ο笤O(shè)計之開閉原則。

那么我們要怎么做呢?我們定義一個新類 BusinessProxy 去包裝一下這個類。為了便于在多個方法的時候區(qū)分和辨認,方法也叫 Test()

AOP從靜態(tài)代理到動態(tài)代理的示例分析

這樣,我們?nèi)绻谒械腂usiness類中的方法都添加Log,我們就在BusinessProxy代理類中添加對應(yīng)的方法去包裝。既不破壞原有邏輯,又可以實現(xiàn)前后日志的功能。

當(dāng)然,我們可以有更優(yōu)雅的實現(xiàn)方式:

AOP從靜態(tài)代理到動態(tài)代理的示例分析

我們可以定義代理類,繼承自業(yè)務(wù)類。將業(yè)務(wù)類中的方法定義為虛方法。那么我們可以重寫父類的方法并且在加入日志以后再調(diào)用父類的原方法。

當(dāng)然,我們還有更加優(yōu)雅的實現(xiàn)方式:

AOP從靜態(tài)代理到動態(tài)代理的示例分析

我們可以使用發(fā)射的技術(shù),寫一個通用的Invoke方法,所有的方法都可以通過該方法調(diào)用。

我們這樣便實現(xiàn)了一個靜態(tài)代理。

那我們既然有了靜態(tài)代理,為什么又要有動態(tài)代理呢?

我們仔細回顧靜態(tài)代理的實現(xiàn)過程。我們要在所有的方法中添加切面,我們就要在代理類中重寫所有的業(yè)務(wù)方法。更有甚者,我們有N個業(yè)務(wù)類,就要定義N個代理類。這是很龐大的工作量。

AOP從靜態(tài)代理到動態(tài)代理的示例分析

這就是動態(tài)代理出現(xiàn)的背景,相比都可以猜得到,動態(tài)代理就是將這一系列繁瑣的步驟自動化,讓程序自動為我們生成代理類。

何為動態(tài)代理?

動態(tài)代理,又成為運行時代理。在程序運行的過程中,調(diào)用了生成代理類的代碼,將自動生成業(yè)務(wù)類的代理類。不需要我們手共編寫,極高的提高了工作效率和調(diào)整了程序員的心態(tài)。

原理不必多說,就是動態(tài)生成靜態(tài)代理的代碼。我們要做的,就是選用一種生成代碼的方式去生成。

今天我分享一個簡單的AOP框架,代碼使用Emit生成。當(dāng)然,Emit 代碼的寫法不是今天要講的主要內(nèi)容,需要提前去學(xué)習(xí)。

先說效果:

定義一個Action特性類 ActionAttribute繼承自 ActionBaseAttribute,里面在Before和After方法中輸出兩條日志;

AOP從靜態(tài)代理到動態(tài)代理的示例分析

定義一個Action特性類InterceptorAttribute 繼承自InterceptorBaseAttribute,里面捕獲了方法調(diào)用異常,以及執(zhí)行前后分別輸出日志;

AOP從靜態(tài)代理到動態(tài)代理的示例分析

然后定義一個業(yè)務(wù)類BusinessClass 實現(xiàn)了IBusinessClass 接口,定義了各種類型的方法

AOP從靜態(tài)代理到動態(tài)代理的示例分析

AOP從靜態(tài)代理到動態(tài)代理的示例分析

多余的方法不貼圖了。

我們把上面定義的方法調(diào)用切面標(biāo)簽放在業(yè)務(wù)類上,表示該類下所有的方法都執(zhí)行異常過濾;

我們把Action特性放在Test方法上,表明要在 Test() 方法的 Before 和 After 調(diào)用時記錄日志;

我們定義測試類:

AOP從靜態(tài)代理到動態(tài)代理的示例分析

調(diào)用一下試試:

AOP從靜態(tài)代理到動態(tài)代理的示例分析

可見,全類方法標(biāo)簽 Interceptor 在 Test 和 GetInt 方法調(diào)用前后都打出了對應(yīng)的日志;

Action方法標(biāo)簽只在 Test 方法上做了標(biāo)記,那么Test方法 Before 和 After 執(zhí)行時打出了日志;

【實現(xiàn)過程】

實現(xiàn)的思路在上面已經(jīng)有詳細的講解,可以參考靜態(tài)代理的實現(xiàn)思路。

我們定義一個動態(tài)代理生成類 DynamicProxy,用于原業(yè)務(wù)代碼的掃描和代理類代碼的生成;

定義兩個過濾器標(biāo)簽,ActionBaseAttribute,提供Before和After切面方法;InterceptorBaseAttribute,提供 Invoke “全調(diào)用”包裝的切面方法;

Before可以獲取到當(dāng)前調(diào)用的方法和參數(shù)列表,After可以獲取到當(dāng)前方法調(diào)用以后的結(jié)果。

Invoke 可以拿到當(dāng)前調(diào)用的對象和方法名,參數(shù)列表。在這里進行反射動態(tài)調(diào)用。

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
 public class ActionBaseAttribute : Attribute
 {
 public virtual void Before(string @method, object[] parameters) { }

 public virtual object After(string @method, object result) { return result; }
 }
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
 public class InterceptorBaseAttribute : Attribute
 {
 public virtual object Invoke(object @object, string @method, object[] parameters)
 {
  return @object.GetType().GetMethod(@method).Invoke(@object, parameters);
 }
 }

代理生成類采用Emit的方式生成運行時IL代碼。

先把代碼放在這里:

public class DynamicProxy
 {
 public static TInterface CreateProxyOfRealize<TInterface, TImp>() where TImp : class, new() where TInterface : class
 {
  return Invoke<TInterface, TImp>();
 }

 public static TProxyClass CreateProxyOfInherit<TProxyClass>() where TProxyClass : class, new()
 {
  return Invoke<TProxyClass, TProxyClass>(true);
 }

 private static TInterface Invoke<TInterface, TImp>(bool inheritMode = false) where TImp : class, new() where TInterface : class
 {
  var impType = typeof(TImp);

  string nameOfAssembly = impType.Name + "ProxyAssembly";
  string nameOfModule = impType.Name + "ProxyModule";
  string nameOfType = impType.Name + "Proxy";

  var assemblyName = new AssemblyName(nameOfAssembly);

  var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
  var moduleBuilder = assembly.DefineDynamicModule(nameOfModule);

  //var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
  //var moduleBuilder = assembly.DefineDynamicModule(nameOfModule, nameOfAssembly + ".dll");

  TypeBuilder typeBuilder;
  if (inheritMode)
  typeBuilder = moduleBuilder.DefineType(nameOfType, TypeAttributes.Public, impType);
  else
  typeBuilder = moduleBuilder.DefineType(nameOfType, TypeAttributes.Public, null, new[] { typeof(TInterface) });

  InjectInterceptor<TImp>(typeBuilder, impType.GetCustomAttribute(typeof(InterceptorBaseAttribute))?.GetType(), inheritMode);

  var t = typeBuilder.CreateType();

  //assembly.Save(nameOfAssembly + ".dll");

  return Activator.CreateInstance(t) as TInterface;
 }

 private static void InjectInterceptor<TImp>(TypeBuilder typeBuilder, Type interceptorAttributeType, bool inheritMode = false)
 {
  var impType = typeof(TImp);
  // ---- define fields ----
  FieldBuilder fieldInterceptor = null;
  if (interceptorAttributeType != null)
  {
  fieldInterceptor = typeBuilder.DefineField("_interceptor", interceptorAttributeType, FieldAttributes.Private);
  }
  // ---- define costructors ----
  if (interceptorAttributeType != null)
  {
  var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, null);
  var ilOfCtor = constructorBuilder.GetILGenerator();

  ilOfCtor.Emit(OpCodes.Ldarg_0);
  ilOfCtor.Emit(OpCodes.Newobj, interceptorAttributeType.GetConstructor(new Type[0]));
  ilOfCtor.Emit(OpCodes.Stfld, fieldInterceptor);
  ilOfCtor.Emit(OpCodes.Ret);
  }

  // ---- define methods ----

  var methodsOfType = impType.GetMethods(BindingFlags.Public | BindingFlags.Instance);

  string[] ignoreMethodName = new[] { "GetType", "ToString", "GetHashCode", "Equals" };

  foreach (var method in methodsOfType)
  {
  //ignore method
  if (ignoreMethodName.Contains(method.Name))
   return;

  var methodParameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();

  MethodAttributes methodAttributes;

  if (inheritMode)
   methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual;
  else
   methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final;

  var methodBuilder = typeBuilder.DefineMethod(method.Name, methodAttributes, CallingConventions.Standard, method.ReturnType, methodParameterTypes);

  var ilMethod = methodBuilder.GetILGenerator();

  // set local field
  var impObj = ilMethod.DeclareLocal(impType);  //instance of imp object
  var methodName = ilMethod.DeclareLocal(typeof(string)); //instance of method name
  var parameters = ilMethod.DeclareLocal(typeof(object[])); //instance of parameters
  var result = ilMethod.DeclareLocal(typeof(object));  //instance of result
  LocalBuilder actionAttributeObj = null;

  //attribute init
  Type actionAttributeType = null;
  if (method.GetCustomAttribute(typeof(ActionBaseAttribute)) != null || impType.GetCustomAttribute(typeof(ActionBaseAttribute)) != null)
  {
   //method can override class attrubute
   if (method.GetCustomAttribute(typeof(ActionBaseAttribute)) != null)
   {
   actionAttributeType = method.GetCustomAttribute(typeof(ActionBaseAttribute)).GetType();
   }
   else if (impType.GetCustomAttribute(typeof(ActionBaseAttribute)) != null)
   {
   actionAttributeType = impType.GetCustomAttribute(typeof(ActionBaseAttribute)).GetType();
   }

   actionAttributeObj = ilMethod.DeclareLocal(actionAttributeType);
   ilMethod.Emit(OpCodes.Newobj, actionAttributeType.GetConstructor(new Type[0]));
   ilMethod.Emit(OpCodes.Stloc, actionAttributeObj);
  }

  //instance imp
  ilMethod.Emit(OpCodes.Newobj, impType.GetConstructor(new Type[0]));
  ilMethod.Emit(OpCodes.Stloc, impObj);

  //if no attribute
  if (fieldInterceptor != null || actionAttributeObj != null)
  {
   ilMethod.Emit(OpCodes.Ldstr, method.Name);
   ilMethod.Emit(OpCodes.Stloc, methodName);

   ilMethod.Emit(OpCodes.Ldc_I4, methodParameterTypes.Length);
   ilMethod.Emit(OpCodes.Newarr, typeof(object));
   ilMethod.Emit(OpCodes.Stloc, parameters);

   // build the method parameters
   for (var j = 0; j < methodParameterTypes.Length; j++)
   {
   ilMethod.Emit(OpCodes.Ldloc, parameters);
   ilMethod.Emit(OpCodes.Ldc_I4, j);
   ilMethod.Emit(OpCodes.Ldarg, j + 1);
   //box
   ilMethod.Emit(OpCodes.Box, methodParameterTypes[j]);
   ilMethod.Emit(OpCodes.Stelem_Ref);
   }
  }

  //dynamic proxy action before
  if (actionAttributeType != null)
  {
   //load arguments
   ilMethod.Emit(OpCodes.Ldloc, actionAttributeObj);
   ilMethod.Emit(OpCodes.Ldloc, methodName);
   ilMethod.Emit(OpCodes.Ldloc, parameters);
   ilMethod.Emit(OpCodes.Call, actionAttributeType.GetMethod("Before"));
  }

  if (interceptorAttributeType != null)
  {
   //load arguments
   ilMethod.Emit(OpCodes.Ldarg_0);//this
   ilMethod.Emit(OpCodes.Ldfld, fieldInterceptor);
   ilMethod.Emit(OpCodes.Ldloc, impObj);
   ilMethod.Emit(OpCodes.Ldloc, methodName);
   ilMethod.Emit(OpCodes.Ldloc, parameters);
   // call Invoke() method of Interceptor
   ilMethod.Emit(OpCodes.Callvirt, interceptorAttributeType.GetMethod("Invoke"));
  }
  else
  {
   //direct call method
   if (method.ReturnType == typeof(void) && actionAttributeType == null)
   {
   ilMethod.Emit(OpCodes.Ldnull);
   }

   ilMethod.Emit(OpCodes.Ldloc, impObj);
   for (var j = 0; j < methodParameterTypes.Length; j++)
   {
   ilMethod.Emit(OpCodes.Ldarg, j + 1);
   }
   ilMethod.Emit(OpCodes.Callvirt, impType.GetMethod(method.Name));
   //box
   if (actionAttributeType != null)
   {
   if (method.ReturnType != typeof(void))
    ilMethod.Emit(OpCodes.Box, method.ReturnType);
   else
    ilMethod.Emit(OpCodes.Ldnull);
   }
  }

  //dynamic proxy action after
  if (actionAttributeType != null)
  {
   ilMethod.Emit(OpCodes.Stloc, result);
   //load arguments
   ilMethod.Emit(OpCodes.Ldloc, actionAttributeObj);
   ilMethod.Emit(OpCodes.Ldloc, methodName);
   ilMethod.Emit(OpCodes.Ldloc, result);
   ilMethod.Emit(OpCodes.Call, actionAttributeType.GetMethod("After"));
  }

  // pop the stack if return void
  if (method.ReturnType == typeof(void))
  {
   ilMethod.Emit(OpCodes.Pop);
  }
  else
  {
   //unbox,if direct invoke,no box
   if (fieldInterceptor != null || actionAttributeObj != null)
   {
   if (method.ReturnType.IsValueType)
    ilMethod.Emit(OpCodes.Unbox_Any, method.ReturnType);
   else
    ilMethod.Emit(OpCodes.Castclass, method.ReturnType);
   }
  }
  // complete
  ilMethod.Emit(OpCodes.Ret);
  }
 }
 }

里面實現(xiàn)了兩種代理方式,一種是 面向接口實現(xiàn) 的方式,另一種是 繼承重寫 的方式。

但是繼承重寫的方式需要把業(yè)務(wù)類的所有方法寫成virtual虛方法,動態(tài)類會重寫該方法。

我們從上一節(jié)的Demo中獲取到運行時生成的代理類dll,用ILSpy反編譯查看源代碼:

AOP從靜態(tài)代理到動態(tài)代理的示例分析

可以看到,我們的代理類分別調(diào)用了我們特性標(biāo)簽中的各項方法。

核心代碼分析(源代碼在上面折疊部位已經(jīng)貼出):

AOP從靜態(tài)代理到動態(tài)代理的示例分析

解釋:如果該方法存在Action標(biāo)簽,那么加載 action 標(biāo)簽實例化對象,加載參數(shù),執(zhí)行Before方法;如果該方法存在Interceptor標(biāo)簽,那么使用類字段this._interceptor調(diào)用該標(biāo)簽的Invoke方法。

AOP從靜態(tài)代理到動態(tài)代理的示例分析

解釋:如果面的Interceptor特性標(biāo)簽不存在,那么會加載當(dāng)前掃描的方法對應(yīng)的參數(shù),直接調(diào)用方法;如果Action標(biāo)簽存在,則將剛才調(diào)用的結(jié)果包裝成object對象傳遞到After方法中。

這里如果目標(biāo)參數(shù)是object類型,而實際參數(shù)是直接調(diào)用返回的明確的值類型,需要進行裝箱操作,否則運行時報調(diào)用內(nèi)存錯誤異常。

AOP從靜態(tài)代理到動態(tài)代理的示例分析

解釋:如果返回值是void類型,則直接結(jié)束并返回結(jié)果;如果返回值是值類型,則需要手動拆箱操作,如果是引用類型,那么需要類型轉(zhuǎn)換操作。

IL實現(xiàn)的細節(jié),這里不做重點討論。

【系統(tǒng)測試】  

1.接口實現(xiàn)方式,Api測試(各種標(biāo)簽使用方式對應(yīng)的不同類型的方法調(diào)用):

AOP從靜態(tài)代理到動態(tài)代理的示例分析

結(jié)論:對于上述窮舉的類型,各種標(biāo)簽使用方式皆成功打出了日志;

2.繼承方式,Api測試(各種標(biāo)簽使用方式對應(yīng)的不同類型的方法調(diào)用):

AOP從靜態(tài)代理到動態(tài)代理的示例分析

結(jié)論:繼承方式和接口實現(xiàn)方式的效果是一樣的,只是方法上需要不同的實現(xiàn)調(diào)整;

3.直接調(diào)用三個方法百萬次性能結(jié)果:

AOP從靜態(tài)代理到動態(tài)代理的示例分析

結(jié)論:直接調(diào)用三個方法百萬次調(diào)用耗時 58ms

4.使用實現(xiàn)接口方式三個方法百萬次調(diào)用結(jié)果

AOP從靜態(tài)代理到動態(tài)代理的示例分析

結(jié)論:結(jié)果見上圖,需要注意是三個方法百萬次調(diào)用,也就是300w次的方法調(diào)用

5.使用繼承方式三個方法百萬次調(diào)用結(jié)果

AOP從靜態(tài)代理到動態(tài)代理的示例分析

結(jié)論:結(jié)果見上圖,需要注意是三個方法百萬次調(diào)用,也就是300w次的方法調(diào)用

事實證明,IL Emit的實現(xiàn)方式性能還是很高的。

綜合分析:

通過各種的調(diào)用分析,可以看出使用代理以后和原生方法調(diào)用相比性能損耗在哪里。性能差距大的,也是耗時最多的實現(xiàn)方式就是添加了全類方法代理而且是使用Invoke進行全方法切面方式。該方式耗時的原因是使用了反射Invoke的方法。

直接添加Action代理類實現(xiàn) Before和After的方式和原生差距不大,主要損耗在After觸發(fā)時的拆裝箱上。

綜上分析,我們使用的時候,盡量針對性地對某一個方法進行AOP注入,而盡量不要全類方法進行AOP注入。

【總結(jié)】

通過自己實現(xiàn)一個AOP的動態(tài)注入框架,對Emit有了更加深入的了解,最重要的是,對CLR IL代碼的執(zhí)行過程有了一定的認知,受益匪淺。

該方法在使用的過程中也發(fā)現(xiàn)了問題,比如有ref和out類型的參數(shù)時,會出現(xiàn)問題,需要后續(xù)繼續(xù)改進

本文的源代碼已托管在GitHub上,又需要可以自行拿取(順手Star哦~):https://github.com/sevenTiny/CodeArts (本地下載)

該代碼的位置在 CodeArts.CSharp 分區(qū)下

AOP從靜態(tài)代理到動態(tài)代理的示例分析

VS打開后,可以在 EmitDynamicProxy 分區(qū)下找到;本博客所有的測試項目都在項目中可以找到。

AOP從靜態(tài)代理到動態(tài)代理的示例分析

以上是“AOP從靜態(tài)代理到動態(tài)代理的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)成都網(wǎng)站設(shè)計公司行業(yè)資訊頻道!

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。

本文標(biāo)題:AOP從靜態(tài)代理到動態(tài)代理的示例分析-創(chuàng)新互聯(lián)
網(wǎng)站地址:http://bm7419.com/article12/hsjgc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供外貿(mào)網(wǎng)站建設(shè)、網(wǎng)站排名建站公司、響應(yīng)式網(wǎng)站搜索引擎優(yōu)化、App開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

營銷型網(wǎng)站建設(shè)