<strike id="wsaek"></strike>

              科技改變生活 · 科技引領未來

              • 首頁
              • 資訊
              • 技術
              • 百科
              • 問答
              • 學習
              • 看看
              • 站長
              • 生活
              • 快訊

              首頁 > 技術 > 技術快訊

              m1d0手表的價格(Spring)

              時間:2022-10-09 16:01 作者:劉原

              作者|GitChat責編|郭芮出品|CSDN博客AOP(AspectOrientedProgramming)面向切面編程是Spring框架最核心的組件之一,它通過對程序結構的另一種考慮,補充了OOP(Object-OrientedProgr

              m1d0手表的價格(Spring)

              作者 | GitChat

              責編 | 郭芮

              出品 | CSDN 博客

              AOP(Aspect Oriented Programming)面向切面編程是 Spring 框架最核心的組件之一,它通過對程序結構的另一種考慮,補充了 OOP(Object-Oriented Programming)面向對象編程。在 OOP 中模塊化的關鍵單元是類,而在 AOP 中,模塊化單元是切面。也就是說 AOP 關注的不再是類,而是一系列類里面需要共同能力的行為。

              本文內容主要包括:

              • 講解 OOP 與 AOP 的簡單對比,以及 AOP 的基礎名詞,比如面試中經常會被問到的 point cut、advice、join point 等。

              • 講解面試經常會被問到的 JDK 動態代理和 CGLIB 代理原理以及區別。

              • 講解 Spring 框架中基于 Schema 的 AOP 實現原理。

              • 講解 Spring 框架中如何基于 AOP 實現的事務管理。

              AOP 基礎概念

              我們知道在 OOP 中模塊化的關鍵單元是類,類封裝了一類對象的行為和狀態,當多個類有共同的屬性和行為時候我們把這些共同的東西封裝為一個基類,然后多個類可以通過繼承基類的方式來復用這些共同的東西,如果子類需要定制基類行為則可以使用多態。OOP 中使用類來提供封裝,繼承,多態三個特性。

              當我們需要在多個不相關的類的某些已有的行為里面添加一個共同的非業務邏輯時候,比如我們需要統計一些業務方法的執行耗時時候,以往做法需要在統計耗時的行為里面寫入計算耗時的代碼,在 OOP 里面這種不涉及業務的散落在多個類的行為里面的代碼叫做橫切(Cross-cutting)代碼,OOP 中這種方式缺點一是業務邏輯行為受到了計算耗時代碼干擾(業務邏輯行為應該只專注業務),缺點二是計算耗時的代碼不能被復用。

              而在 AOP 中模塊化單元是切面(Aspect),它將那些影響多個類的共同行為封裝到可重用的模塊中,然后你就可以決定在什么時候對哪些類的哪些行為執行進行攔截(切點),并使用封裝好的可重用模塊里面的行為(通知)對其攔截的業務行為進行功能增強,而不需要修改業務模塊的代碼,切面就是對此的一個抽象描述。

              AOP 中有以下基礎概念:

              • Join point(連接點):程序執行期間的某一個點,例如執行方法或處理異常時候的點。在 Spring AOP 中,連接點總是表示方法的執行。

              • Advice(通知):通知是指一個切面在特定的連接點要做的事情。通知分為方法執行前通知,方法執行后通知,環繞通知等。許多 AOP 框架(包括 Spring)都將通知建模為攔截器,在連接點周圍維護一系列攔截器(形成攔截器鏈),對連接點的方法進行增強。

              • Pointcut(切點):一個匹配連接點(Join point)的謂詞表達式。通知(Advice)與切點表達式關聯,并在切點匹配的任何連接點(Join point)(例如,執行具有特定名稱的方法)上運行。切點是匹配連接點(Join point)的表達式的概念,是AOP的核心,并且 Spring 默認使用 AspectJ 作為切入點表達式語言。

              • Aspect(切面):它是一個跨越多個類的模塊化的關注點,它是通知(Advice)和切點(Pointcut)合起來的抽象,它定義了一個切點(Pointcut)用來匹配連接點(Join point),也就是需要對需要攔截的那些方法進行定義;它定義了一系列的通知(Advice)用來對攔截到的方法進行增強;

              • Target object(目標對象):被一個或者多個切面(Aspect)通知的對象,也就是需要被 AOP 進行攔截對方法進行增強(使用通知)的對象,也稱為被通知的對象。由于在 AOP 里面使用運行時代理,所以目標對象一直是被代理的對象。

              • AOP proxy(AOP 代理):為了實現切面(Aspect)功能使用 AOP 框架創建一個對象,在 Spring 框架里面一個 AOP 代理要么指 JDK 動態代理,要么指 CgLIB 代理。

              • Weaving(織入):是將切面應用到目標對象的過程,這個過程可以是在編譯時(例如使用 AspectJ 編譯器),類加載時,運行時完成。Spring AOP 和其它純 Java AOP 框架一樣,是在運行時執行植入。

              • Advisor:這個概念是從 Spring 1.2的 AOP 支持中提出的,一個 Advisor 相當于一個小型的切面,不同的是它只有一個通知(Advice),Advisor 在事務管理里面會經常遇到,這個后面會講到。

              相比 OOP,AOP 有以下優點:

              • 業務代碼更加簡潔,例如當需要在業務行為前后做一些事情時候,只需要在該行為前后配置切面進行處理,無須修改業務行為代碼。

              • 切面邏輯封裝性好,并且可以被復用,例如我們可以把打日志的邏輯封裝為一個切面,那么我們就可以在多個相關或者不相關的類的多個方法上配置該切面。

              JDK 動態代理和 CGLIB 代理原理、區別

              在 Spring 中 AOP 代理使用 JDK 動態代理和 CGLIB 代理來實現,默認如果目標對象是接口,則使用 JDK 動態代理,否則使用 CGLIB 來生成代理類,本節就簡單來介紹這兩種代理的原理和區別。

              JDK 動態代理

              由于 JDK 代理是對接口進行代理,所以首先寫一個接口類:

              public interface UserServiceBo {

              public int add;

              }

              然后實現該接口如下:

              public class UserServiceImpl implements UserServiceBo {

              @Override

              public int add {

              System.out.println("--------------------add----------------------");

              return 0;

              }

              }

              JDK 動態代理是需要實現 InvocationHandler 接口,這里創建一個 InvocationHandler 的實現類:

              public class MyInvocationHandler implements InvocationHandler {

              private Object target;

              public MyInvocationHandler(Object target) {

              super;

              this.target = target;

              }

              @Override

              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

              //(1)

              System.out.println("-----------------begin "+method.getName+"-----------------");

              //(2)

              Object result = method.invoke(target, args);

              //(3)

              System.out.println("-----------------end "+method.getName+"-----------------");

              return result;

              }

              public Object getProxy{

              //(4)

              return Proxy.newProxyInstance(Thread.currentThread.getContextClassLoader, target.getClass.getInterfaces, this);

              }

              }

              建立一個測試類:

              public static void main(String[] args) {

              //(5)打開這個開關,可以把生成的代理類保存到磁盤

              System.getProperties.put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

              //(6)創建目標對象(被代理對象)

              UserServiceBo service = new UserServiceImpl;

              //(7)創建一個InvocationHandler實例,并傳遞被代理對象

              MyInvocationHandler handler = new MyInvocationHandler(service);

              //(8)生成代理類

              UserServiceBo proxy = (UserServiceBo) handler.getProxy;

              proxy.add;

              }

              其中代碼(6)創建了一個 UserServiceImpl 的實例對象,這個對象就是要被代理的目標對象。

              代碼(7)創建了一個 InvocationHandler 實例,并傳遞被代理目標對象 service 給內部變量 target。

              代碼(8)調用 MyInvocationHandler 的 getProxy 方法使用 JDK 生成一個代理對象。

              代碼(5)設置系統屬性變量 sun.misc.ProxyGenerator.saveGeneratedFiles 為 true,這是為了讓代碼(8)生成的代理對象的字節碼文件保存到磁盤。

              運行上面代碼會在項目的 com.sun.proxy 下面會生成 $Proxy0.class 類,經反編譯后核心 Java 代碼如下:

              package com.sun.proxy;

              import java.lang.reflect.InvocationHandler;

              import java.lang.reflect.Method;

              import java.lang.reflect.Proxy;

              import java.lang.reflect.UndeclaredThrowableException;

              import proxy.JDK.UserServiceBo;

              public final class $Proxy0

              extends Proxy

              implements UserServiceBo

              {

              private static Method m1;

              private static Method m3;

              private static Method m0;

              private static Method m2;

              public $Proxy0(InvocationHandler paramInvocationHandler)

              {

              super(paramInvocationHandler);

              }

              public final int add

              {

              try

              {

              //(9)第一個參數是代理類本身,第二個是實現類的方法,第三個是參數

              return h.invoke(this, m3, );

              }

              catch (Error|RuntimeException localError)

              {

              throw localError;

              }

              catch (Throwable localThrowable)

              {

              throw new UndeclaredThrowableException(localThrowable);

              }

              }

              ...

              static

              {

              try

              {

              m3 = Class.forName("proxy.JDK.UserServiceBo").getMethod("add", new Class[0]);

              ...

              return;

              }

              catch (NoSuchMethodException localNoSuchMethodException)

              ...

              }

              }

              代理類 $Proxy0中代碼(9)中的 h 就是 main 函數里面創建的 MyInvocationHandler 的實例,h.invoke(this, m3, ) 實際就是調用的 MyInvocationHandler 的 invoke 方法,而后者則是委托給被代理對象進行執行,這里可以對目標對象方法進行攔截,然后對其功能進行增強。

              另外代碼(8)生成的 proxy 對象實際就是 $Proxy0的一個實例,當調用 proxy.add 時候,實際是調用的代理類$Proxy0的 add 方法,后者內部則委托給 MyInvocationHandler 的 invoke 方法,invoke 方法內部有調用了目標對象 service 的 add 方法。

              那么接口(UserServiceBo)、目標對象(被代理對象 UserServiceImpl)、代理對象($Proxy0)三者具體關系可以使用下圖表示:

              enter image description here

              可知 JDK 動態代理是對接口進行的代理;代理類實現了接口,并繼承了 Proxy 類;目標對象與代理對象沒有什么直接關系,只是它們都實現了接口,并且代理對象執行方法時候內部最終是委托目標對象執行具體的方法。

              CGLIB 動態代理

              相比 JDK 動態代理對接口進行代理,CGLIB 則是對實現類進行代理,這意味著無論目標對象是否有接口,都可以使用 CGLIB 進行代理。

              下面結合一個使用 CGLIB 對實現類進行動態代理的簡單代碼來講解。

              使用 CGLIB 進行代理需要實現 MethodInterceptor,創建一個方法攔截器 CglibProxy 類:

              public class CglibProxy implements MethodInterceptor {

              //(10)

              private Enhancer enhancer = new Enhancer;

              //(11)

              public Object getProxy(Class clazz) {

              //(12)設置被代理類的Class對象

              enhancer.setSuperclass(clazz);

              //(13)設置攔截器回調

              enhancer.setCallback( this);

              return enhancer.create;

              }

              @Override

              public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

              System.out.println(obj.getClass.getName+"."+method.getName);

              Object result = proxy.invokeSuper(obj, args);

              return result;

              }

              }

              public void testCglibProxy {

              //(14)生成代理類到本地

              System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zhuizhumengxiang/Downloads");

              //(15)生成目標對象

              UserServiceImpl service = new UserServiceImpl;

              //(16)創建CglibProxy對象

              CglibProxy cp = new CglibProxy;

              //(17)生成代理類

              UserServiceBo proxy = (UserServiceBo) cp.getProxy(service.getClass);

              proxy.add;

              }

              執行上面代碼會在 /Users/zhuizhumengxiang/Downloads目錄生成代理類UserServiceImpl$EnhancerByCGLIB$d0bce05a.class文件,反編譯后部分代碼如下:

              public class UserServiceImpl$EnhancerByCGLIB$d0bce05a extends UserServiceImpl

              implements Factory

              {

              static void CGLIB$STATICHOOK1

              {

              //(18)空參數

              CGLIB$emptyArgs = new Object[0];

              //(19)獲取UserServiceImpl的add方法列表

              Method tmp191_188 = ReflectUtils.findMethods(new String { "add", "I" }, (localClass2 = Class.forName("zlx.cglib.zlx.UserServiceImpl")).getDeclaredMethods);

              CGLIB$add$0$Method = tmp191_188[0];

              //(20)創建CGLIB$add$0,根據創建一個MethodProxy

              CGLIB$add$0$Proxy = MethodProxy.create(localClass2, localClass1, "I", "add", "CGLIB$add$0");

              }

              static

              {

              CGLIB$STATICHOOK1;

              }

              //(21)

              final int CGLIB$add$0

              {

              return super.add;

              }

              //(22)

              public final int add

              {

              ...

              //(23)獲取攔截器,這里為CglibProxy的實例

              MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;

              if (tmp17_14 != )

              { //(24)調用攔截器的intercept方法

              Object tmp36_31 = tmp17_14.intercept(this, CGLIB$add$0$Method, CGLIB$emptyArgs, CGLIB$add$0$Proxy);

              tmp36_31;

              return tmp36_31 == ? 0 : ((Number)tmp36_31).intValue;

              }

              return super.add;

              }

              }

              代碼(18)創建了一個 CGLIB$emptyArgs,因為 add 方法是無入參的,所以這里創建的 Object 對象個數為0,這對象是 CglibProxy 攔截器的 intercept 的第三個參數。

              代碼(19)獲取 UserServiceImpl 的 add 方法列表,并把唯一方法賦值給 CGLIB$add$0$Method,這個對象是 CglibProxy 攔截器的 intercept 第二個參數。

              代碼(20)創建了一個 MethodProxy 對象,當調用 MethodProxy 對象的 invokeSuper 方法時候會直接調用代理對象的 CGLIB$add$0方法,也就是直接調用父類 UserServiceImpl 的 add 方法,這避免了一次反射調用,創建的 MethodProxy 對象是 CglibProxy 攔截器的 intercept 的第四個參數。

              代碼(22)是代理類的 add 方法,代碼(17)生成的代理對象 proxy 就是 UserServiceImpl$EnhancerByCGLIB$d0bce05a的一個實例,當調用 proxy.add 方法時候就是調用的代碼(22),從代碼(22)可知內部調用了攔截器 CglibProxy 的 intercept 方法,可知 intercept 的第一個參數就是代理對象本身。

              那么接口(UserServiceBo)、目標對象(被代理對象 UserServiceImpl),代理對象(UserServiceImpl$EnhancerByCGLIB$d0bce05a)三者具體關系可以使用下圖表示:

              enter image description here

              可知接口和代理對象沒有啥關系,代理對象是繼承了目標對象和實現了 Factory 接口。

              總結

              JDK 動態代理機制只能對接口進行代理,其原理是動態生成一個代理類,這個代理類實現了目標對象的接口,目標對象和代理類都實現了接口,但是目標對象和代理類的 Class 對象是不一樣的,所以兩者是沒法相互賦值的。

              CGLIB 是對目標對象本身進行代理,所以無論目標對象是否有接口,都可以對目標對象進行代理,其原理是使用字節碼生成工具在內存生成一個繼承目標對象的代理類,然后創建代理對象實例。由于代理類的父類是目標對象,所以代理類是可以賦值給目標對象的,自然如果目標對象有接口,代理對象也是可以賦值給接口的。

              CGLIB 動態代理中生成的代理類的字節碼相比 JDK 來說更加復雜。

              JDK 使用反射機制調用目標類的方法,CGLIB 則使用類似索引的方式直接調用目標類方法,所以 JDK 動態代理生成代理類的速度相比 CGLIB 要快一些,但是運行速度比 CGLIB 低一些,并且 JDK 代理只能對有接口的目標對象進行代理。

              Spring 框架中基于 Schema 的 AOP 實現原理

              Spring 提供了兩種方式對 AOP 進行支持:基于 Schema 的 AOP,基于注解的 AOP。

              基于 Schema 的 AOP 允許您基于 XML 的格式配置切面功能,Spring 2.0 提供了新的“aop”命名空間標記來定義切面的支持,基于注解的 AOP 則允許您使用 @Aspect 風格來配置切面。

              本文就來先講講基于 Schema 的 AOP 的實現原理。

              AOP 簡單使用

              一個切面實際上是一個被定義在 Spring application context 里面的一個正常的 Java 對象,配置切面對目標對象進行增強時候,一般使用下面配置格式:

              expression="execution(* *..*BoImpl.sayHello(..))"/>

              method="afterAdvice" />

              pointcut="execution(* *..*BoImpl.sayHelloAfterReturn(..))" method="afterReturningAdvice"

              arg-names="content" returning="content" />

              method="afterThrowingAdvice" arg-names="e" throwing="e" />

              method="aroundAdvice" />

              pression="execution(* *..*BoImpl.sayHelloAdvisor(..))" />

              isor pointcut-ref="pointcutForadVisor" advice-ref="tracingInterceptor" />

              代碼(1)創建一個要被 AOP 進行功能增強的目標對象(Target object),HelloServiceBoImpl 的代碼如下:

              public class HelloServiceBoImpl implements HelloServiceBo{

              @Override

              public void sayHello(String content) {

              System.out.println("sayHello:" + content);

              }

              @Override

              public String sayHelloAround(String content) {

              System.out.println(" sayHelloAround:" + content);

              return content;

              }

              @Override

              public String sayHelloAfterReturn(String content) {

              System.out.println("sayHelloAround:" + content);

              return content;

              }

              @Override

              public void sayHelloThrowExecption {

              System.out.println("sayHelloThrowExecption");

              throw new RuntimeException("hello Im an exception");

              }

              }

              public interface HelloServiceBo {

              public void sayHello(String content);

              public String sayHelloAround(String content);

              public String sayHelloAfterReturn(String content);

              public void sayHelloThrowExecption;

              }

              代碼(2)實質是定義了切面要使用的一系列的通知方法(Advice),用來對目標對象(Target object)的方法進行增強,MyAspect 的代碼如下:

              public class MyAspect {

              public void beforeAdvice(String content) {

              System.out.println("---before advice "+ "---");

              }

              public void afterAdvice(JoinPoint jp) {

              System.out.println("---after advice " + jp.getArgs[0].toString+"---");

              }

              public Object afterReturningAdvice(Object value) {

              System.out.println("---afterReturning advice " + value+"---");

              return value + " ha";

              }

              public void afterThrowingAdvice(Exception e) {

              System.out.println("---after throwing advice exception:" + e+"---");

              }

              public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {

              Object obj = pjp.getArgs;

              String content = (String) obj[0];

              System.out.println("---before sayHelloAround execute---");

              String retVal = (String) pjp.proceed;

              System.out.println("---after sayHelloAround execute---");

              return retVal+ " suffix";

              }

              }

              代碼(3)定義了一個切點(pointcut),這里是對滿足* *..*BoImpl表達式的類里面的方法名稱為 sayHello 的方法進行攔截。

              代碼(4)定義一個切面,一個切面中可以定義多個攔擊器,其中(4.1)定義了一個前置攔截器,這個攔截器對滿足代碼(3)中定義的切點的連接點(方法)進行攔截,并使用 MyAspect 中定義的通知方法 beforeAdvice 進行功能增強。其中(4.2)定義了一個后置攔截器(finally),對滿足 execution(* *..*BoImpl.sayHello(..))條件的連接點方法使用 MyAspect 中的通知方法 afterAdvice 進行功能增強。其中(4.3)定義了一個后置攔截器,對滿足execution(* *..*BoImpl.sayHelloAfterReturn(..))條件的連接點方法使用 MyAspect 中的通知方法 afterReturningAdvice 進行功能增強,這個后置連接器與(4.2)不同在于如果被攔截方法拋出了異常,則這個攔截器不會被執行,而(4.2)的攔截器一直會被執行。其中(4.4)定義了一個當被攔截方法拋出異常后對異常進行攔截的攔截器,具體攔截哪些方法由execution(* *..*BoImpl.sayHelloThrowExecption(..))來決定,具體的通知方法是 MyAspect 中的 afterThrowingAdvice 方法。其中(4.5)對滿足execution(* *..*BoImpl.sayHelloAround(..))條件的連接點方法使用 MyAspect 中的通知方法 aroundAdvice 進行增強。

              代碼(5)創建一個方法攔截器,它是一個通知,代碼如下:

              class TracingInterceptor implements MethodInterceptor {

              public Object invoke(MethodInvocation i) throws Throwable {

              System.out

              .println("---method " + i.getMethod + " is called on " + i.getThis + " with args " + i.getArguments+"---");

              Object ret = i.proceed;

              System.out.println("---method " + i.getMethod + " returns " + ret+"---");

              return ret;

              }

              }

              代碼(6)創建了一個新的 aop:config 標簽,內部首先創建了一個切點,然后創建了一個 advisor(一個小型切面),它對應的通知方法是 tracingInterceptor,對應的切點是 pointcutForadVisor。

              需要注意的是為了能夠使用 AOP 命名空間下的 aop 標簽,您需要在 XML 引入下面的 spring-aop schema:

              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

              xmlns:aop="http://www.springframework.org/schema/aop"

              xsi:schemaLocation="

              http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd

              http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

              把上面配置收集起來,放入到 beanaop.xml 配置文件后,寫下下面代碼,就可以進行測試:

              public class TestAOP {

              public static final String xmlpath = "beanaop.xml";

              public static void main(String[] args) {

              ClassPathXmlApplicationContext cpxa = new ClassPathXmlApplicationContext(xmlpath);

              HelloServiceBo serviceBo = cpxa.getBean("helloService",HelloServiceBo.class);

              serviceBo.sayHello(" I love you");

              String result = serviceBo.sayHelloAround("I love you");

              System.out.println(result);

              result = serviceBo.sayHelloAfterReturn("I love you");

              System.out.println(result);

              serviceBo.sayHelloAdvisor("I love you");

              serviceBo.sayHelloThrowExecption;

              }

              }

              原理剖析

              aop:config 標簽的解析

              既然本文講解基于 XML 配置的 AOP 的原理,那么我們就先從解析 XML 里面配置的 aop:config 講起。首先看看 Spring 框架的 ConfigBeanDefinitionParser 類是如何解析aop:config 標簽,主要時序圖如下:

              enter image description here

              代碼(3)注冊了一個 AspectJAwareAdvisorAutoProxyCreator 類到 Spring IOC 容器,該類的作用是自動創建代理類,這個后面會講。這里需要注意的是在 registerAspectJAutoProxyCreatorIfNecessary的useClassProxyingIfNecessary 方法里面解析了 aop:config 標簽里面的 proxy-target-class 和 expose-proxy 屬性值,代碼如下:

              private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @able Element sourceElement) {

              //解析proxy-target-class屬性

              if (sourceElement != ) {

              boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE)); //設置proxy-target-class屬性值到AspectJAwareAdvisorAutoProxyCreator里面

              if (proxyTargetClass) {

              AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);

              }

              //解析expose-proxy屬性值

              boolean exposeProxy = Boolean.valueOf(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));

              //設置expose-proxy屬性屬性值到AspectJAwareAdvisorAutoProxyCreator

              if (exposeProxy) {

              AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);

              }

              }

              }

              針對 proxy-target-class 屬性(默認 false),如果設置為 true,則會強制使用 CGLIB 生成代理類;對于 expose-proxy 屬性(默認 false)如果設置為 true,則對一個類里面的嵌套方法調用的方法也進行代理,具體說是如果一個 ServiceImpl 類里面有 a 和 b 方法,如果 a 里面調用了 b:

              public void a{

              this.b;

              }

              那么默認情況下在對方法 a 進行功能增強時候,里面的 b 方法是不會被增強的,如果也需要對 b 方法進行增強則可以設置 expose-proxy 為 true,并且在調用 b 方法時候使用下面方式:

              public void a{

              ((ServiceBo)AopContext.currentProxy).b;

              }

              需要注意的是 AopContext.currentProxy 返回的是代理后的類,如果使用 JDK 代理則這里類型轉換時候必須要用接口類,如果是 CGLIB 代理則這里類型轉換為實現類 ServiceImpl。

              代碼(4)是對 aop:config 標簽里面的 aop:pointcut 元素進行解析,每個 pointcut 元素會創建一個 AspectJexpressionPointcut 的 bean 定義,并注冊到Spring IOC,parsePointcut 的主干代碼如下:

              private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {

              //獲取aop:pointcut標簽的id屬性值

              String id = pointcutElement.getAttribute(ID);

              //獲取aop:pointcut標簽的expression屬性值

              String expression = pointcutElement.getAttribute(expression);

              AbstractBeanDefinition pointcutDefinition = ;

              try {

              //創建AspectJexpressionPointcut的bean定義,并設置屬性

              pointcutDefinition = createPointcutDefinition(expression);

              pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));

              //如果aop:pointcut標簽的id屬性值不為空,則id作為該bean在Spring容器里面的bean名字,并注冊該bean到Spring容器。

              String pointcutBeanName = id;

              if (StringUtils.hasText(pointcutBeanName)) {

              parserContext.getRegistry.registerBeanDefinition(pointcutBeanName, pointcutDefinition);

              }

              else {//否者系統自動生成一個名字,并注入該bean到Spring容器

              pointcutBeanName = parserContext.getReaderContext.registerWithGeneratedName(pointcutDefinition);

              }

              ...

              }

              ...

              return pointcutDefinition;

              }

              注:AspectJexpressionPointcut 的成員變量 expression 保存了 aop:pointcut 標簽的 expression 屬性值,這個在自動代理時候會用到。

              代碼(5)是對 aop:config 標簽里面的 aop:advisor 元素進行解析,每個 advisor 元素會創建一個 DefaultBeanFactoryPointcutAdvisor 的 bean 定義,并注冊到 Spring IOC,parseAdvisor代碼如下:

              private void parseAdvisor(Element advisorElement, ParserContext parserContext) {

              //創建DefaultBeanFactoryPointcutAdvisor的定義,并設置對advice的引用

              AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);

              String id = advisorElement.getAttribute(ID);

              try {

              ...

              //注冊DefaultBeanFactoryPointcutAdvisor到Spring容器

              String advisorBeanName = id;

              if (StringUtils.hasText(advisorBeanName)) {

              parserContext.getRegistry.registerBeanDefinition(advisorBeanName, advisorDef);

              }

              else {

              advisorBeanName = parserContext.getReaderContext.registerWithGeneratedName(advisorDef);

              }

              //解析aop:advisor標簽中引用的切點,并設置到DefaultBeanFactoryPointcutAdvisor的定義

              Object pointcut = parsePointcutProperty(advisorElement, parserContext);

              if (pointcut instanceof String) {

              advisorDef.getPropertyValues.add(POINTCUT, new RuntimeBeanReference((String) pointcut));

              ...

              }

              ...

              }

              ...

              }

              注:每個 DefaultBeanFactoryPointcutAdvisor 對象里面通過成員變量 adviceBeanName 保存引用通知在 BeanFactory 里面的 Bean 名稱,通過成員變量 pointcut 保存切點。

              DefaultBeanFactoryPointcutAdvisor 繼承自 Advisor 接口,這里需要注意的是 adviceBeanName 保存的只是通知的字符串名稱,那么如何獲取真正的通知對象呢,其實 DefaultBeanFactoryPointcutAdvisor 實現了 BeanFactoryAware,其內部保證著 BeanFactory 的引用,既然有了 BeanFactory,那么就可以根據 Bean 名稱拿到想要的 Bean,這個可以參考 Chat:Spring 框架常用擴展接口揭秘(https://gitbook.cn/gitchat/activity/5a84589a1f42d45a333f2a8e)。

              代碼(6)是對 aop:config 標簽里面的 aop:aspect 元素進行解析,會解析 aop:aspect 元素內的子元素,每個子元素會對應創建一個 AspectJPointcutAdvisor 的 bean 定義,并注冊到 Spring IOC,parseAspect的代碼如下:

              private void parseAspect(Element aspectElement, ParserContext parserContext) {

              //獲取 aop:aspect 元素的id屬性

              String aspectId = aspectElement.getAttribute(ID);

              //獲取 aop:aspect 元素的ref屬性

              String aspectName = aspectElement.getAttribute(REF);

              try {

              this.parseState.push(new AspectEntry(aspectId, aspectName));

              List beanDefinitions = new ArrayList<>;

              List beanReferences = new ArrayList<>;

              ...

              //循環解析```aop:aspect```元素內所有通知

              NodeList nodeList = aspectElement.getChildNodes;

              boolean adviceFoundAlready = false;

              for (int i = 0; i < nodeList.getLength; i++) {

              Node node = nodeList.item(i);

              //判斷是否為通知節點,比如前置,后者通知等

              if (isAdviceNode(node, parserContext)) {

              if (!adviceFoundAlready) {

              adviceFoundAlready = true;

              ...

              beanReferences.add(new RuntimeBeanReference(aspectName));

              }

              //根據通知類型的不同,創建不同的通知對象,最后封裝為AspectJPointcutAdvisor的bean定義,并注冊到Spring容器

              AbstractBeanDefinition advisorDefinition = parseAdvice(

              aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);

              beanDefinitions.add(advisorDefinition);

              }

              }

              //循環解析```aop:aspect```元素內所有切點,并注冊到Spring容器

              List pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);

              for (Element pointcutElement : pointcuts) {

              parsePointcut(pointcutElement, parserContext);

              }

              ...

              }

              finally {

              ...

              }

              }

              需要注意的是 parseAdvice 函數內部會根據通知類型的不同創建不同的 advice 對象,對應 before 通知會創建通知對象為 AspectJMethodBeforeAdvice,對應 after 通知創建的通知對象為 AspectJAfterAdvice,對應 after-returning 通知創建的通知對象為 AspectJAfterReturningAdvice,對應 after-throwing 通知創建的通知對象為 AspectJAfterThrowingAdvice,對應 around 通知創建的通知對象為 AspectJAroundAdvice,一個共同點是這些通知對象都實現了 MethodInterceptor 接口。

              注:每個 AspectJPointcutAdvisor 對象里面通過 advice 維護著一個通知,通過 pointcut 維護這么一個切點,AspectJPointcutAdvisor 繼承自 Advisor 接口。

              至此 Spring 框架把 aop:config 標簽里面的配置全部轉換為了 Bean 定義的形式并注入到 Spring 容器了,需要注意的是對應一個 Spring 應用程序上下文的 XML 配置里面可以配置多個 aop:config 標簽,每次解析一個 aop:config 標簽的時候都會重新走這個流程。

              代理類的生成

              (1)代理類生成的主干流程

              上節在解析 aop:config 標簽時候注入了一個 AspectJAwareAdvisorAutoProxyCreator 類到 Spring 容器,其實這個類就是實現動態生成代理類的,AspectJAwareAdvisorAutoProxyCreator 實現了 BeanPostProcessor 接口(這個接口是 Spring 框架在 bean 初始化前后做事情的擴展接口,具體可以參考:http://gitbook.cn/gitchat/activity/5a84589a1f42d45a333f2a8e),所以會有 Object postProcessAfterInitialization(@able Object bean, String beanName) 方法,下面看下這個方法代碼執行時序圖:

              enter image description here

              代碼(3)在 Spring 容器中查找可以對當前 bean 進行增強的通知 bean,內部首先執行代碼(4)在 Spring 容器查找所有實現了 Advisor 接口的 Bean,也就是上節講解的從 aop:config 標簽里面解析的所有 DefaultBeanFactoryPointcutAdvisor 和 AspectJPointcutAdvisor 的實例都會被找到。代碼(5)則看找到的 Advisor 里面哪些可以應用到當前 bean 上,這個是通過切點表達式來匹配的。代碼(6)則對匹配的 Advisor 進行排序,根據每個 Advisor 對象的 getOrder 方法的值進行排序。

              代碼(13)這里根據一些條件決定是使用 JDK 動態代理,還是使用 CGLIB 進行代理,屬于設計模式里面的策略模式。

              public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {

              //如果optimize =true或者proxyTargetClass=ture 或者沒有指定代理接口,則使用CGLIB進行代理

              if (config.isOptimize || config.isProxyTargetClass || hasNoUserSuppliedProxyInterfaces(config)) {

              Class targetClass = config.getTargetClass;

              //如果目標類為接口或者目標類是使用JDK動態代理生成的類,則是要使用JDK對其進行代理

              if (targetClass.isInterface || Proxy.isProxyClass(targetClass)) {

              return new JdkDynamicAopProxy(config);

              }

              //否者使用CGLIB進行代理

              return new ObjenesisCglibAopProxy(config);

              }

              //使用JDK進行代理

              else {

              return new JdkDynamicAopProxy(config);

              }

              對于 proxyTargetClass 前面已經講過了可以通過在 aop:config 這個標簽里面配置,那么 optimize 是在哪里配置的呢?其實除了本文講的基于標簽的 AOP 模式,Spring 還提供了比如下面的方式進行動態代理:

              BeanNameAutoProxyCreator 可以針對指定規則的 beanName 的 bean 使用 interceptorNames 進行增強,由于這時候不能確定匹配的 bean 是否有接口,所以這里 optimize 設置為 true,然后在創建代理工廠時候具體看被代理的類是否是接口決定是使用 JDK 代理還是 CGLIB 代理。

              代碼(14)則是具體調用 JdkDynamicAopProxy 或者 ObjenesisCglibAopProxy 的 getProxy 方法獲取代理類,下面兩節具體介紹如何生成。

              (2)JDK 動態代理生成代理對象

              首先看下 JdkDynamicAopProxy 的 getProxy 方法:

              public Object getProxy(@able ClassLoader classLoader) {

              if (logger.isDebugEnabled) {

              logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource);

              }

              Class proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);

              findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);

              return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);

              }

              由于 JdkDynamicAopProxy 類實現了 InvocationHandler 接口,所以這里使用 Proxy.newProxyInstance 創建代理對象時候第三個參數傳遞的為 this。下面看下 JdkDynamicAopProxy 的 invoke 方法:

              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

              ...

              try {

              ...

              //獲取可以運用到該方法上的攔截器列表

              List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

              //創建一個invocation方法,這個里面是一個interceptor鏈,是一個責任鏈模式

              invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);

              //使用攔截器鏈處理這個連接點方法

              retVal = invocation.proceed;

              ...

              return retVal;

              }

              finally {

              ...

              }

              }

              下面看下上面小節“AOP 簡單使用”例子中調用 serviceBo.sayHello 時候的時序圖從而加深理解:

              • “AOP 簡單使用”這一小節例子中我們在 sayHello 方法執行前加了一個前置攔截器,在 sayHello 方法執行后加了個后置攔截器;

              • 當執行 serviceBo.sayHello 時候實際上執行的代理類的 sayHello 方法,所以會被 JdkDynamicAopProxy 的 invoke 方法攔截,invoke 方法內首先調用 getInterceptorsAndDynamicInterceptionAdvice 方法獲取配置到 sayHello 方法上的攔截器列表,然后創建一個 ReflectiveMethodInvocation 對象(內部是一個基于數數組的責任鏈),然后調用該對象的 procced 方法激活攔截器鏈對 sayHello 方法進行增強,這里是首先調用了前置連接器對 sayHello 進行增強,然后調用實際業務方法 sayHello,最后調用了后置攔截器對 sayHello 進行增強。

              (3)CGLIB 動態代理生成代理對象

              首先看下 ObjenesisCglibAopProxy 的 getProxy 方法:

              public Object getProxy(@able ClassLoader classLoader) {

              try {

              Class rootClass = this.advised.getTargetClass;

              //創建CGLIB Enhancer

              Enhancer enhancer = createEnhancer;

              ...

              enhancer.setSuperclass(proxySuperClass);

              enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));

              enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);

              ...

              //獲取callback,主要是DynamicAdvisedInterceptor

              Callback callbacks = getCallbacks(rootClass);

              Class types = new Class[callbacks.length];

              for (int x = 0; x < types.length; x++) {

              types[x] = callbacks[x].getClass;

              }

              //設置callback

              enhancer.setCallbackTypes(types);

              //產生代理類并創建一個代理實例

              return createProxyClassAndInstance(enhancer, callbacks);

              }

              catch (CodeGenerationException | IllegalArgumentException ex) {

              ...

              }

              ...

              }

              下面看下攔截器 DynamicAdvisedInterceptor 的 intercept 方法,代碼如下:

              public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

              ...

              TargetSource targetSource = this.advised.getTargetSource;

              try {

              ...

              //獲取可以運用到該方法上的攔截器列表

              List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

              Object retVal;

              //如果攔截器列表為空,則直接反射調用業務方法

              if (chain.isEmpty && Modifier.isPublic(method.getModifiers)) {

              Object argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);

              retVal = methodProxy.invoke(target, argsToUse);

              }

              else {

              //創建一個方法invocation,其實這個是個攔截器鏈,這里調用proceed激活攔截器鏈對當前方法進行功能增強

              retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed;

              }

              //處理返回值

              retVal = processReturnType(proxy, target, method, retVal);

              return retVal;

              }

              finally {

              ...

              }

              }

              下面看“AOP 簡單使用”這一節例子中調用 serviceBo.sayHello 時候的調用鏈路時序圖從而加深理解:

              enter image description here

              • 由于默認使用的 JDK 動態代理,要使用 CGLIB 進行代理,需要在 aop:config 標簽添加屬性如下:

              nfig proxy-target-class="true">

              • 執行流程類似于 JDK 動態代理,這里不再累述。

              Spring 框架中如何基于 AOP 實現的事務管理

              事務的簡單配置

              XML 使用標簽配置事務,一般是按照下面方式進行配置:

              expression="execution(* com.xyz.myapp.service.*.*(..))"/>

              isor

              pointcut-ref="businessService"

              advice-ref="tx-advice"/>

              • 如上配置(1),配置了一個 id 為 businessService 的切點用來匹配要對哪些方法進行事務增強;

              • 配置(2)配置了一個 advisor ,使用 businessService 作為切點,tx-advice 作為通知方法;

              • 配置(3)則使用 tx:advice 標簽配置了一個通知,這個是重點,下節專門講解。

              注:這個配置作用是對滿足 id 為 businessService 的切點條件的方法進行事務增強,并且設置事務傳播性級別為 REQUIRED。

              原理剖析

              tx:advice 標簽的解析

              tx:advice 標簽是使用 TxAdviceBeanDefinitionParser 進行解析的,下面看看解析時序圖:

              enter image description here

              • 如上時序圖 BeanDefinitionBuilder 是一個建造者模式,用來構造一個 bean 定義,這個 bean 定義最終會生成一個 TransactionInterceptor 的實例;

              • 步驟(5)通過循環解析 tx:attributes 標簽里面的所有 tx:method 標簽,每個 tx:method 對應一個 RulebasedTransactionAttribute 對象,其中 tx:method 標簽中除了可以配置事務傳播性,還可以配置事務隔離級別,超時時間,是否只讀,和回滾策略。

              • 步驟(12)把所有的 method 標簽的 RulebasedTransactionAttribute 對象存在到了一個 map 數據結構,步驟(13)設置解析的所有屬性到建造者模式的對象里面,步驟(14)使用建造者對象創建一個 bean 定義,步驟(15)則注冊該 bean 到 Spring 容器。

              注:tx:advice 作用是創建一個 TransactionInterceptor 攔截器,內部維護事務配置信息。

              事務攔截器原理簡單剖析

              下面看下 TransactionInterceptor 的 invoke 方法:

              public Object invoke(final MethodInvocation invocation) throws Throwable {

              ...

              return invokeWithinTransaction(invocation.getMethod, targetClass, invocation::proceed);

              }

              protected Object invokeWithinTransaction(Method method, @able Class targetClass,

              final InvocationCallback invocation) throws Throwable {

              // If the transaction attribute is , the method is non-transactional.

              TransactionAttributeSource tas = getTransactionAttributeSource;

              final TransactionAttribute txAttr = (tas != ? tas.getTransactionAttribute(method, targetClass) : );

              final PlatformTransactionManager tm = determineTransactionManager(txAttr);

              final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

              if (txAttr == || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {

              // 標準事務,內部有getTransaction(開啟事務) 和commit(提交)/rollback(回滾)事務被調用.

              TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

              Object retVal = ;

              try {

              //這是一個環繞通知,調用proceedWithInvocation激活攔截器鏈里面的下一個攔擊器

              retVal = invocation.proceedWithInvocation;

              }

              catch (Throwable ex) {

              // target invocation exception

              completeTransactionAfterThrowing(txInfo, ex);

              throw ex;

              }

              finally {

              cleanupTransactionInfo(txInfo);

              }

              commitTransactionAfterReturning(txInfo);

              return retVal;

              }

              ...

              }

              其中 createTransactionIfNecessary 是重要方法,其內部邏輯處理流程如下圖:

              enter image description here

              注:Spring 事務管理通過配置一個 AOP 切面來實現,其中定義了一個切點用來決定對哪些方法進行方法攔截,定義了一個 TransactionInterceptor 通知,來對攔截到的方法進行事務增強,具體事務攔擊器里面是怎么做的,讀者可以結合上面的 TransactionInterceptor 方法的流程圖結合源碼來研究下,如果必要后面會再開一個 Chat 專門講解 Spring 事務的實現,以及事務隔離性與傳播性。

              總結

              本文以大綱的形式剖析了 AOP 原理,以及事務攔擊器如何使用 AOP 來實現,由于篇幅限制并沒有深入到每個實現細節進行講解,希望讀者能夠依靠本文作為大綱,對照源碼深入到大綱里面的細節進行研究,以便加深對 AOP 原理的理解和掌握。

              相關話題

              • Google強迫自己使傳入的智能手機更受歡迎
              • 7月23日螞蟻莊園每日答案小雞寶寶問題 為什么酒喝多了走路容易東倒西歪
              • 支付寶小雞寶寶考考你今日問答 一般吃火鍋時涮的蟹棒它的主要原材料是什么
              • 7月23日螞蟻莊園小雞寶寶問題 炎熱的夏天為了解暑喝什么比較好
              • 一般吃火鍋時涮的蟹棒它的主要原材料是什么
              • 互聯網醫療服務納入醫保
              • 螞蟻莊園今天小雞考你問答 一般吃火鍋時涮的蟹棒它的主要原材料是什么
              • 為什么酒喝多了走路容易東倒西歪 7月23日支付寶螞蟻莊園答案答題
              • 螞蟻莊園今日課堂答題 為什么貓咪能像液體一樣縮在小盒子里
              • 螞蟻莊園小課堂今日答案 一般吃火鍋時涮的蟹棒它的主要原材料是什么
              • 中國AI公司Megvii申請狗臉識別專利
              • 炎熱的夏天為了解暑喝什么比較好 A、熱飲 B、冷飲
              • 一名抗洪戰士被洪水卷走負傷
              • 支付寶螞蟻莊園小雞問答匯總 一般吃火鍋時涮的蟹棒它的主要原材料是什么
              • 警方通報杭州女子失蹤案 目前杭州失蹤女子尸體在小區化糞池找到
              • 炎熱的夏天為了解暑喝什么比較好 7月23日螞蟻莊園今日問答
              • 互聯網醫療服務納入醫保 制定公布全國統一的互聯網醫療審批標準
              • 歐盟擬明年1月開征塑料包裝稅 每公斤0.8歐元約合6.4元人民幣
              • 7月23日螞蟻莊園每日答案小雞寶寶問題 為什么貓咪能像液體一樣縮在小盒子里
              • 螞蟻莊園今日問答 為什么貓咪能像液體一樣縮在小盒子里

              熱門推薦

              • “純血鴻蒙”要來了!華為正式官宣,與盤古大模型5.0一同亮相!
              • 小米手環 9 即將上市發布!
              • 華為P70發售“蓄勢待發”,旗艦店:能留下電話等通知!
              • 真我GT Neo6 SE首發6000nit無雙屏,綜合功耗更低,首銷1699元起!
              • 京東支付與銀聯國際達成合作,實現跨境便利支付!
              • iOS 17.5測試版上線:iPhone用戶可從網站側載App,與安卓相似!
              • vivo X Fold3 系列折疊屏或將支持5.5G,4月或將開啟OTA 推送!
              • 京東汽車和小米汽車或聯手深度合作!
              • 微軟發布首批AI電腦,配專用Copilot按鈕!
              • 美團成立平臺產品部,整合本地商業多項任務!
              • 代號“橄欖石”,小米 Redmi Note 13 Turbo 手機新曝光!
              • 華為P70系列發布延期?爆料芯片有變化!
              • 農業銀行申請云計算任務調度專利,極大提高云計算任務處理效率!
              • 榮耀將推出首款小折疊手機:給三星和蘋果一點點震撼!
              • 生成式人工智能技術走進高校專業課,極大提高備課效率!
              • 騰訊公司申請區塊鏈數據處理專利,實現快速完成相同業務數據的數據交換功能!
              • 消息稱谷歌將把Bard 更名為 Gemini,并退出獨立的應用!
              • 小米 14 Ultra 手機跑分成績單曝光!
              • 谷歌瀏覽器將基于AI功能進行升級,計劃2月上線寫作助手!
              • 亞馬遜AWS將投資150億美元擴大日本云計算業務,滿足客戶需求!

              劉原

              關注
              免責聲明:本文章由會員“劉原”發布,如果文章侵權,請聯系我們處理,本站僅提供信息存儲空間服務 如因作品內容、版權和其他問題請于本站聯系

              關注排行榜

              1. 1“純血鴻蒙”要來了!華為正式官宣,與盤古大模型5.0一同亮相!
              2. 2小米手環 9 即將上市發布!
              3. 3華為P70發售“蓄勢待發”,旗艦店:能留下電話等通知!
              4. 4真我GT Neo6 SE首發6000nit無雙屏,綜合功耗更低,首銷1699元起!
              5. 5京東支付與銀聯國際達成合作,實現跨境便利支付!
              6. 6iOS 17.5測試版上線:iPhone用戶可從網站側載App,與安卓相似!
              7. 7vivo X Fold3 系列折疊屏或將支持5.5G,4月或將開啟OTA 推送!
              8. 8京東汽車和小米汽車或聯手深度合作!
              9. 9微軟發布首批AI電腦,配專用Copilot按鈕!
              10. 10美團成立平臺產品部,整合本地商業多項任務!

              編輯精選

              Copyright ?2009-2022 KeJiTian.Com, All Rights Reserved

              版權所有 未經許可不得轉載

              增值電信業務經營許可證備案號:遼ICP備14006349號

              網站介紹 商務合作 免責聲明 - html - txt - xml

              感谢您访问我们的网站,您可能还对以下资源感兴趣:

              亚洲精品一区二三区不卡
              亚洲精品蜜夜内射| 亚洲精品福利网泷泽萝拉| 无码国内精品久久综合88| 精品一久久香蕉国产线看播放| 国产精品2019| 国产午夜精品一区理论片| 国产精品亚洲精品日韩电影| 99国产精品免费观看视频| 亚洲精品国产综合久久一线| 精品一久久香蕉国产二月| 精品久久久久久综合日本| 国产精品亚洲天堂| 国产精品高清一区二区人妖| 久久99精品国产99久久6男男| 国产精品久久久精品三级| 亚洲国产精品成人综合久久久 | 久久91精品久久91综合| 国产伦精品一区二区三区在线观看 | 久久国产精品免费视频| 亚洲国产精品第一区二区三区| 亚洲精品国产av成拍色拍| 久久国产精品视频一区| 久久久精品波多野结衣| 精品伊人久久大线蕉地址| 91精品全国免费观看含羞草| 国产精品无码一区二区三区电影| 精品国自产拍天天拍2021| 蜜臀AV无码精品人妻色欲| 67194老司机精品午夜| 国产日韩精品中文字无码| 国产精品极品美女自在线观看免费 | 精品午夜国产人人福利| 日本精品视频在线播放| 国产精品视频你懂的| 91精品国产综合久久久久| 久久国产成人精品麻豆| 成人精品视频99在线观看免费| 国产av一区二区精品久久凹凸| 国产精品视频在线观看| 国产精品大bbwbbwbbw| 老子午夜精品无码|
              <cite id="uwigo"></cite>
              <center id="uwigo"></center>
            • <cite id="uwigo"></cite>
              <abbr id="uwigo"></abbr>
            • <li id="uwigo"><source id="uwigo"></source></li>
                • <rt id="uwigo"></rt>
                  <strike id="uwigo"></strike>
                    <rt id="uwigo"><tr id="uwigo"></tr></rt>
                      <cite id="uwigo"></cite>