激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

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

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

服務(wù)器之家 - 編程語言 - C# - AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

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

【前言】

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)代理(Emit實現(xiàn))詳解

何為靜態(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)代理(Emit實現(xiàn))詳解

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

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

我們可以使用發(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)代理(Emit實現(xiàn))詳解

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

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

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

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

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

先說效果:

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

多余的方法不貼圖了。

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

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

我們定義測試類:

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

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

【實現(xiàn)過程】

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

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

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

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

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

?
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);
  }
 }
 }

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

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

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

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

【系統(tǒng)測試】  

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

結(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)代理(Emit實現(xiàn))詳解

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

AOP從靜態(tài)代理到動態(tài)代理(Emit實現(xiàn))詳解

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

總結(jié)

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

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

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产免费网站视频 | 偷偷操偷偷操 | 日本爽快片100色毛片视频 | 调教小男生抽打尿孔嗯啊视频 | 久久精品视频日本 | 欧美一级毛片特黄黄 | 亚洲国产精品久久久久婷婷老年 | 羞羞视频| 欧洲成人免费 | 日本黄色网战 | 黄色片网页 | 涩涩屋av| 国产一区国产二区在线观看 | 久久精品视频国产 | 国产精品av久久久久久网址 | 一级黄色毛片播放 | 亚洲欧美在线视频免费 | 最污网站 | 久久精品免费国产 | 欧美性受ⅹ╳╳╳黑人a性爽 | 欧美一级片在线 | 中文字幕视频在线播放 | 欧美日本不卡 | 销魂美女一区二区 | 成人毛片在线免费观看 | 92看片淫黄大片欧美看国产片 | 成人福利在线观看 | 日本成年网 | 中文字幕www. | 一区二区久久精品66国产精品 | 综合精品一区 | 嗯哈~不行好大h双性 | 久久国产乱子伦精品 | 99亚洲| 国产视频软件在线 | 黄色免费av网站 | 亚洲国产色婷婷 | 欧美精品成人一区二区三区四区 | 久久99偷拍视频 | 亚洲性视频 | 成人毛片100免费观看 |