一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - C# - AOP從靜態代理到動態代理(Emit實現)詳解

AOP從靜態代理到動態代理(Emit實現)詳解

2022-03-01 14:14柒小 C#

AOP為Aspect Oriented Programming的縮寫,意思是面向切面編程的技術。下面這篇文章主要給大家介紹了關于AOP從靜態代理到動態代理(Emit實現)的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下

【前言】

aop(aspect orient programming),我們一般稱為面向方面(切面)編程,作為面向對象的一種補充,用于處理系統中分布于各個模塊的橫切關注點,比如事務管理、日志、緩存等等。aop實現的關鍵在于aop框架自動創建的aop代理,aop代理主要分為靜態代理和動態代理,靜態代理的代表為aspectj;而動態代理則以spring aop為代表。

何為切面?

一個和業務沒有任何耦合相關的代碼段,諸如:調用日志,發送郵件,甚至路由分發。一切能為代碼所有且能和代碼充分解耦的代碼都可以作為一個業務代碼的切面。

我們為什么要aop?

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

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

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

有何問題?

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

如果我們采用了aop技術,我們就可以在系統啟動的地方將所有將要采集日志的類注入,每一次調用方法前,aop框架會自動調用我們的日志代碼。

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

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

【實現思路】

aop框架呢,一般通過靜態代理和動態代理兩種實現方式。

  AOP從靜態代理到動態代理(Emit實現)詳解

何為靜態代理?

靜態代理,又叫編譯時代理,就是在編譯的時候,已經存在代理類,運行時直接調用的方式。說的通俗一點,就是自己手動寫代碼實現代理類的方式。

我們通過一個例子來展現一下靜態代理的實現過程:

我們這里有一個業務類,里面有方法test(),我們要在test調用前和調用后分別輸出日志。

AOP從靜態代理到動態代理(Emit實現)詳解

我們既然要將log當作一個切面,我們肯定不能去動原有的業務代碼,那樣也違反了面向對象設計之開閉原則。

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

AOP從靜態代理到動態代理(Emit實現)詳解

這樣,我們如果要在所有的business類中的方法都添加log,我們就在businessproxy代理類中添加對應的方法去包裝。既不破壞原有邏輯,又可以實現前后日志的功能。

當然,我們可以有更優雅的實現方式:

AOP從靜態代理到動態代理(Emit實現)詳解

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

當然,我們還有更加優雅的實現方式:

AOP從靜態代理到動態代理(Emit實現)詳解

我們可以使用發射的技術,寫一個通用的invoke方法,所有的方法都可以通過該方法調用。

我們這樣便實現了一個靜態代理。

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

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

AOP從靜態代理到動態代理(Emit實現)詳解

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

何為動態代理?

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

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

今天我分享一個簡單的aop框架,代碼使用emit生成。當然,emit 代碼的寫法不是今天要講的主要內容,需要提前去學習。

先說效果:

定義一個action特性類 actionattribute繼承自 actionbaseattribute,里面在before和after方法中輸出兩條日志;

AOP從靜態代理到動態代理(Emit實現)詳解

定義一個action特性類interceptorattribute 繼承自interceptorbaseattribute,里面捕獲了方法調用異常,以及執行前后分別輸出日志;

AOP從靜態代理到動態代理(Emit實現)詳解

然后定義一個業務類businessclass 實現了ibusinessclass 接口,定義了各種類型的方法

AOP從靜態代理到動態代理(Emit實現)詳解

AOP從靜態代理到動態代理(Emit實現)詳解

多余的方法不貼圖了。

我們把上面定義的方法調用切面標簽放在業務類上,表示該類下所有的方法都執行異常過濾;

我們把action特性放在test方法上,表明要在 test() 方法的 before 和 after 調用時記錄日志;

我們定義測試類:

AOP從靜態代理到動態代理(Emit實現)詳解

調用一下試試:

AOP從靜態代理到動態代理(Emit實現)詳解

可見,全類方法標簽 interceptor 在 test 和 getint 方法調用前后都打出了對應的日志;

action方法標簽只在 test 方法上做了標記,那么test方法 before 和 after 執行時打出了日志;

【實現過程】

實現的思路在上面已經有詳細的講解,可以參考靜態代理的實現思路。

我們定義一個動態代理生成類 dynamicproxy,用于原業務代碼的掃描和代理類代碼的生成;

定義兩個過濾器標簽,actionbaseattribute,提供before和after切面方法;interceptorbaseattribute,提供 invoke “全調用”包裝的切面方法;

before可以獲取到當前調用的方法和參數列表,after可以獲取到當前方法調用以后的結果。

invoke 可以拿到當前調用的對象和方法名,參數列表。在這里進行反射動態調用。

?
1
2
3
4
5
6
7
[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; }
 }
?
1
2
3
4
5
6
7
8
[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代碼。

先把代碼放在這里:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
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);
  }
 }
 }

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

但是繼承重寫的方式需要把業務類的所有方法寫成virtual虛方法,動態類會重寫該方法。

我們從上一節的demo中獲取到運行時生成的代理類dll,用ilspy反編譯查看源代碼:

AOP從靜態代理到動態代理(Emit實現)詳解

可以看到,我們的代理類分別調用了我們特性標簽中的各項方法。

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

AOP從靜態代理到動態代理(Emit實現)詳解

解釋:如果該方法存在action標簽,那么加載 action 標簽實例化對象,加載參數,執行before方法;如果該方法存在interceptor標簽,那么使用類字段this._interceptor調用該標簽的invoke方法。

AOP從靜態代理到動態代理(Emit實現)詳解

解釋:如果面的interceptor特性標簽不存在,那么會加載當前掃描的方法對應的參數,直接調用方法;如果action標簽存在,則將剛才調用的結果包裝成object對象傳遞到after方法中。

這里如果目標參數是object類型,而實際參數是直接調用返回的明確的值類型,需要進行裝箱操作,否則運行時報調用內存錯誤異常。

AOP從靜態代理到動態代理(Emit實現)詳解

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

il實現的細節,這里不做重點討論。

【系統測試】  

1.接口實現方式,api測試(各種標簽使用方式對應的不同類型的方法調用):

AOP從靜態代理到動態代理(Emit實現)詳解

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

2.繼承方式,api測試(各種標簽使用方式對應的不同類型的方法調用):

AOP從靜態代理到動態代理(Emit實現)詳解

結論:繼承方式和接口實現方式的效果是一樣的,只是方法上需要不同的實現調整;

3.直接調用三個方法百萬次性能結果:

AOP從靜態代理到動態代理(Emit實現)詳解

結論:直接調用三個方法百萬次調用耗時 58ms

4.使用實現接口方式三個方法百萬次調用結果

AOP從靜態代理到動態代理(Emit實現)詳解

結論:結果見上圖,需要注意是三個方法百萬次調用,也就是300w次的方法調用

5.使用繼承方式三個方法百萬次調用結果

AOP從靜態代理到動態代理(Emit實現)詳解

結論:結果見上圖,需要注意是三個方法百萬次調用,也就是300w次的方法調用

事實證明,il emit的實現方式性能還是很高的。

綜合分析:

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

直接添加action代理類實現 before和after的方式和原生差距不大,主要損耗在after觸發時的拆裝箱上。

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

【總結】

通過自己實現一個aop的動態注入框架,對emit有了更加深入的了解,最重要的是,對clr il代碼的執行過程有了一定的認知,受益匪淺。

該方法在使用的過程中也發現了問題,比如有ref和out類型的參數時,會出現問題,需要后續繼續改進

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

該代碼的位置在 codearts.csharp 分區下

AOP從靜態代理到動態代理(Emit實現)詳解

vs打開后,可以在 emitdynamicproxy 分區下找到;本博客所有的測試項目都在項目中可以找到。

AOP從靜態代理到動態代理(Emit實現)詳解

再次放上源代碼地址,供一起學習的朋友參考,希望能幫助到你:https://github.com/seventiny/codearts

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。

原文鏈接:https://www.cnblogs.com/7tiny/p/9657451.html

延伸 · 閱讀

精彩推薦
  • C#C#設計模式之Visitor訪問者模式解決長隆歡樂世界問題實例

    C#設計模式之Visitor訪問者模式解決長隆歡樂世界問題實例

    這篇文章主要介紹了C#設計模式之Visitor訪問者模式解決長隆歡樂世界問題,簡單描述了訪問者模式的定義并結合具體實例形式分析了C#使用訪問者模式解決長...

    GhostRider9502022-01-21
  • C#C# 實現對PPT文檔加密、解密及重置密碼的操作方法

    C# 實現對PPT文檔加密、解密及重置密碼的操作方法

    這篇文章主要介紹了C# 實現對PPT文檔加密、解密及重置密碼的操作方法,非常不錯,具有參考借鑒價值,需要的朋友可以參考下...

    E-iceblue5012022-02-12
  • C#C#通過KD樹進行距離最近點的查找

    C#通過KD樹進行距離最近點的查找

    這篇文章主要為大家詳細介紹了C#通過KD樹進行距離最近點的查找,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    帆帆帆6112022-01-22
  • C#C#實現XML文件讀取

    C#實現XML文件讀取

    這篇文章主要為大家詳細介紹了C#實現XML文件讀取的相關代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    Just_for_Myself6702022-02-22
  • C#深入解析C#中的交錯數組與隱式類型的數組

    深入解析C#中的交錯數組與隱式類型的數組

    這篇文章主要介紹了深入解析C#中的交錯數組與隱式類型的數組,隱式類型的數組通常與匿名類型以及對象初始值設定項和集合初始值設定項一起使用,需要的...

    C#教程網6172021-11-09
  • C#Unity3D實現虛擬按鈕控制人物移動效果

    Unity3D實現虛擬按鈕控制人物移動效果

    這篇文章主要為大家詳細介紹了Unity3D實現虛擬按鈕控制人物移動效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一...

    shenqingyu060520232410972022-03-11
  • C#WPF 自定義雷達圖開發實例教程

    WPF 自定義雷達圖開發實例教程

    這篇文章主要介紹了WPF 自定義雷達圖開發實例教程,本文介紹的非常詳細,具有參考借鑒價值,需要的朋友可以參考下...

    WinterFish13112021-12-06
  • C#C#裁剪,縮放,清晰度,水印處理操作示例

    C#裁剪,縮放,清晰度,水印處理操作示例

    這篇文章主要為大家詳細介紹了C#裁剪,縮放,清晰度,水印處理操作示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    吳 劍8332021-12-08
主站蜘蛛池模板: 国产成人精品777 | 亚洲天堂网站在线 | 天色综合 | 欧美日韩高清观看一区二区 | tkvk视频| 国产女主播在线播放一区二区 | 9久热久爱免费精品视频在线观看 | 国产精品麻豆99久久 | 特级淫片欧美高清视频蜜桃 | 亚洲 欧美 在线观看 | 国产一区二区在线观看视频 | 5g影院天天5g天天爽大陆 | 日韩一区二区三区精品 | 第一福利在线观看永久视频 | 免费一区在线观看 | 楚乔传第二部免费观看全集完整版 | 欧美三级免费观看 | yjsp妖精视频在线观看免费 | 国产免费一区二区 | 亚洲欧美日韩精品高清 | 国产尤物视频 | 皇上好大好硬好涨好深好爽 | 思思玖玖玖在线精品视频 | zozzozozozo大| 国产精品久久久久久久久免费观看 | 九九热这里只有精品视频免费 | 俄罗斯美女毛茸茸bbwbbw | 暖暖免费高清完整版观看日本 | 给我免费的视频在线观看 | 国内精品视频一区二区三区八戒 | 大桥未久midd—962在线 | 日本高清无吗 | 精品久久久久久无码人妻国产馆 | 超h 超重口 高h 污肉1v1 | 亚洲丰满女人ass硕大 | 污到湿的爽文免费阅读 | 精品久久香蕉国产线看观看亚洲 | 国产欧美一区二区三区免费 | 情欲综合网 | 国语自产拍在线播放不卡 | 欧美精品国产一区二区 |