一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

<aop:config>(通知advice)

 宇宙之窗 2013-08-19

6.3. Schema-based AOP support

如果你無法使用Java 5,或者你比較喜歡使用XML格式,Spring2.0也提供了使用新的"aop"命名空間來定義一個(gè)切面。 和使用@AspectJ風(fēng)格完全一樣,切入點(diǎn)表達(dá)式和通知類型同樣得到了支持,因此在這一節(jié)中我們將著重介紹新的 語法 和回顧前面我們所討論的如何寫一個(gè)切入點(diǎn)表達(dá)式和通知參數(shù)的綁定(Section 6.2, “@AspectJ支持”)。

使用本章所介紹的aop命名空間標(biāo)簽(aop namespace tag),你需要引入Appendix A, XML Schema-based configuration中提及的spring-aop schema。 參見Section A.2.6, “The aop schema”。

在Spring的配置文件中,所有的切面和通知器都必須定義在 <aop:config> 元素內(nèi)部。 一個(gè)application context可以包含多個(gè) <aop:config>。 一個(gè) <aop:config> 可以包含pointcut,advisor和aspect元素(注意它們必須按照這樣的順序進(jìn)行聲明)。

[Warning] Warning

<aop:config>風(fēng)格的配置使得對(duì)Spring auto-proxying 機(jī)制的使用變得很笨重。如果你已經(jīng)通過BeanNameAutoProxyCreator或類似的東西使用顯式的auto-proxying將會(huì)引發(fā)問題 (例如通知沒有被織入)。推薦的使用模式是只使用<aop:config>風(fēng)格或只使用 AutoProxyCreator風(fēng)格

6.3.1. 聲明一個(gè)切面

有了schema的支持,切面就和常規(guī)的Java對(duì)象一樣被定義成application context中的一個(gè)bean。 對(duì)象的字段和方法提供了狀態(tài)和行為信息,XML文件則提供了切入點(diǎn)和通知信息。

切面使用<aop:aspect>來聲明,backing bean(支持bean)通過 ref 屬性來引用:

<aop:config>  <aop:aspect id="myAspect" ref="aBean">...  </aop:aspect></aop:config><bean id="aBean" class="...">  ...</bean>

切面的支持bean(上例中的"aBean")可以象其他Spring bean一樣被容器管理配置以及依賴注入。

6.3.2. 聲明一個(gè)切入點(diǎn)

切入點(diǎn)可以在切面里面聲明,這種情況下切入點(diǎn)只在切面內(nèi)部可見。切入點(diǎn)也可以直接在<aop:config>下定義,這樣就可以使多個(gè)切面和通知器共享該切入點(diǎn)。

一個(gè)描述service層中表示所有service執(zhí)行的切入點(diǎn)可以如下定義:

<aop:config>  <aop:pointcut id="businessService"expression="execution(* com.xyz.myapp.service.*.*(..))"/></aop:config>

注意切入點(diǎn)表達(dá)式本身使用了 Section 6.2, “@AspectJ支持” 中描述的AspectJ 切入點(diǎn)表達(dá)式語言。 如果你在Java 5環(huán)境下使用基于schema的聲明風(fēng)格,可參考切入點(diǎn)表達(dá)式類型中定義的命名式切入點(diǎn),不過這在JDK1.4及以下版本中是不被支持的(因?yàn)橐蕾囉贘ava 5中的AspectJ反射API)。 所以在JDK 1.5中,上面的切入點(diǎn)的另外一種定義形式如下:

<aop:config>  <aop:pointcut id="businessService"expression="com.xyz.myapp.SystemArchitecture.businessService()"/></aop:config>

假定你有 Section 6.2.3.3, “共享常見的切入點(diǎn)(pointcut)定義”中說描述的 SystemArchitecture 切面。

在切面里面聲明一個(gè)切入點(diǎn)和聲明一個(gè)頂級(jí)的切入點(diǎn)非常類似:

<aop:config>  <aop:aspect id="myAspect" ref="aBean"><aop:pointcut id="businessService"  expression="execution(* com.xyz.myapp.service.*.*(..))"/>...  </aop:aspect></aop:config>

當(dāng)需要連接子表達(dá)式的時(shí)候,'&'在XML中用起來非常不方便,所以關(guān)鍵字'and', 'or' 和 'not'可以分別用來代替'&', '||' 和 '!'。

注意這種方式定義的切入點(diǎn)通過XML id來查找,并且不能定義切入點(diǎn)參數(shù)。在基于schema的定義風(fēng)格中命名切入點(diǎn)支持較之@AspectJ風(fēng)格受到了很多的限制。

6.3.3. 聲明通知

和@AspectJ風(fēng)格一樣,基于schema的風(fēng)格也支持5種通知類型并且兩者具有同樣的語義。

6.3.3.1. 通知(Advice)

Before通知在匹配方法執(zhí)行前進(jìn)入。在<aop:aspect>里面使用<aop:before>元素進(jìn)行聲明。

<aop:aspect id="beforeExample" ref="aBean"><aop:before  pointcut-ref="dataAccessOperation"  method="doAccessCheck"/>...</aop:aspect>

這里 dataAccessOperation 是一個(gè)頂級(jí)(<aop:config>)切入點(diǎn)的id。 要定義內(nèi)置切入點(diǎn),可將 pointcut-ref 屬性替換為 pointcut 屬性:

<aop:aspect id="beforeExample" ref="aBean"><aop:before  pointcut="execution(* com.xyz.myapp.dao.*.*(..))"  method="doAccessCheck"/>...</aop:aspect>

我們已經(jīng)在@AspectJ風(fēng)格章節(jié)中討論過了,使用命名切入點(diǎn)能夠明顯的提高代碼的可讀性。

Method屬性標(biāo)識(shí)了提供了通知的主體的方法(doAccessCheck)。這個(gè)方法必須定義在包含通知的切面元素所引用的bean中。 在一個(gè)數(shù)據(jù)訪問操作執(zhí)行之前(執(zhí)行連接點(diǎn)和切入點(diǎn)表達(dá)式匹配),切面中的"doAccessCheck"會(huì)被調(diào)用。

6.3.3.2. 返回后通知(After returning advice)

After returning通知在匹配的方法完全執(zhí)行后運(yùn)行。和Before通知一樣,可以在<aop:aspect>里面聲明。例如:

<aop:aspect id="afterReturningExample" ref="aBean"><aop:after-returning  pointcut-ref="dataAccessOperation"  method="doAccessCheck"/>...</aop:aspect>

和@AspectJ風(fēng)格一樣,通知主體可以接收返回值。使用returning屬性來指定接收返回值的參數(shù)名:

<aop:aspect id="afterReturningExample" ref="aBean"><aop:after-returning  pointcut-ref="dataAccessOperation"  returning="retVal"  method="doAccessCheck"/>...</aop:aspect>

doAccessCheck方法必須聲明一個(gè)名字叫 retVal 的參數(shù)。 參數(shù)的類型強(qiáng)制匹配,和先前我們?cè)贎AfterReturning中講到的一樣。例如,方法簽名可以這樣聲明:

public void doAccessCheck(Object retVal) {...

6.3.3.3. 拋出異常后通知(After throwing advice)

After throwing通知在匹配方法拋出異常退出時(shí)執(zhí)行。在 <aop:aspect> 中使用after-throwing元素來聲明:

<aop:aspect id="afterThrowingExample" ref="aBean"><aop:after-throwing  pointcut-ref="dataAccessOperation"  method="doRecoveryActions"/>...</aop:aspect>

和@AspectJ風(fēng)格一樣,可以從通知體中獲取拋出的異常。 使用throwing屬性來指定異常的名稱,用這個(gè)名稱來獲取異常:

<aop:aspect id="afterThrowingExample" ref="aBean"><aop:after-throwing  pointcut-ref="dataAccessOperation"  thowing="dataAccessEx"  method="doRecoveryActions"/>...</aop:aspect>

doRecoveryActions方法必須聲明一個(gè)名字為 dataAccessEx 的參數(shù)。 參數(shù)的類型強(qiáng)制匹配,和先前我們?cè)贎AfterThrowing中講到的一樣。例如:方法簽名可以如下這般聲明:

public void doRecoveryActions(DataAccessException dataAccessEx) {...

6.3.3.4. 后通知(After (finally) advice)

After (finally)通知在匹配方法退出后執(zhí)行。使用 after 元素來聲明:

<aop:aspect id="afterFinallyExample" ref="aBean"><aop:after  pointcut-ref="dataAccessOperation"  method="doReleaseLock"/>...</aop:aspect>

6.3.3.5. 通知

Around通知是最后一種通知類型。Around通知在匹配方法運(yùn)行期的“周圍”執(zhí)行。 它有機(jī)會(huì)在目標(biāo)方法的前面和后面執(zhí)行,并決定什么時(shí)候運(yùn)行,怎么運(yùn)行,甚至是否運(yùn)行。 Around通知經(jīng)常在需要在一個(gè)方法執(zhí)行前或后共享狀態(tài)信息,并且是線程安全的情況下使用(啟動(dòng)和停止一個(gè)計(jì)時(shí)器就是一個(gè)例子)。 注意選擇能滿足你需求的最簡(jiǎn)單的通知類型(i.e.如果簡(jiǎn)單的before通知就能做的事情絕對(duì)不要使用around通知)。

Around通知使用 aop:around 元素來聲明。 通知方法的第一個(gè)參數(shù)的類型必須是 ProceedingJoinPoint 類型。 在通知的主體中,調(diào)用 ProceedingJoinPointproceed() 方法來執(zhí)行真正的方法。 proceed 方法也可能會(huì)被調(diào)用并且傳入一個(gè) Object[] 對(duì)象 - 該數(shù)組將作為方法執(zhí)行時(shí)候的參數(shù)。 參見 Section 6.2.4.5, “環(huán)繞通知(Around Advice)” 中提到的一些注意點(diǎn)。

<aop:aspect id="aroundExample" ref="aBean"><aop:around  pointcut-ref="businessService"  method="doBasicProfiling"/>...</aop:aspect>

doBasicProfiling 通知的實(shí)現(xiàn)和@AspectJ中的例子完全一樣(當(dāng)然要去掉注解):

public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {    // start stopwatch    Object retVal = pjp.proceed();    // stop stopwatch    return retVal;}

6.3.3.6. 通知參數(shù)

Schema-based聲明風(fēng)格和@AspectJ支持一樣,支持通知的全名形式 - 通過通知方法參數(shù)名字來匹配切入點(diǎn)參數(shù)。 參見 Section 6.2.4.6, “通知參數(shù)(Advice parameters)” 獲取詳細(xì)信息。

如果你希望顯式指定通知方法的參數(shù)名(而不是依靠先前提及的偵測(cè)策略),可以通過 arg-names 屬性來實(shí)現(xiàn)。示例如下:

<aop:before  pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"  method="audit"  arg-names="auditable"/>

The arg-names attribute accepts a comma-delimited list of parameter names.

arg-names屬性接受由逗號(hào)分割的參數(shù)名列表。

請(qǐng)看下面這個(gè)基于XSD風(fēng)格的更復(fù)雜一些的實(shí)例,它展示了關(guān)聯(lián)多個(gè)強(qiáng)類型參數(shù)的環(huán)繞通知的使用。

首先,服務(wù)接口及它的實(shí)現(xiàn)將被通知:

package x.y.service;public interface FooService {   Foo getFoo(String fooName, int age);}// the attendant implementation (defined in another file of course)public class DefaultFooService implements FooService {   public Foo getFoo(String name, int age) {      return new Foo(name, age);   }}

下一步(無可否認(rèn)的)是切面。注意實(shí)際上profile(..)方法 接受多個(gè)強(qiáng)類型(strongly-typed)參數(shù),第一個(gè)參數(shù)是方法調(diào)用時(shí)要執(zhí)行的連接點(diǎn),該參數(shù)指明了 profile(..)方法被用作一個(gè)環(huán)繞通知:

package x.y;import org.aspectj.lang.ProceedingJoinPoint;import org.springframework.util.StopWatch;public class SimpleProfiler {   public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {      StopWatch clock = new StopWatch(            "Profiling for '" + name + "' and '" + age + "'");      try {         clock.start(call.toShortString());         return call.proceed();      } finally {         clock.stop();         System.out.println(clock.prettyPrint());      }   }}

最后,下面是為一個(gè)特定的連接點(diǎn)執(zhí)行上面的通知所必需的XML配置:

<beans xmlns="http://www./schema/beans"      xmlns:xsi="http://www./2001/XMLSchema-instance"      xmlns:aop="http://www./schema/aop"      xsi:schemaLocation="http://www./schema/beans http://www./schema/beans/spring-beans-2.0.xsdhttp://www./schema/aop http://www./schema/aop/spring-aop-2.0.xsd">   <!-- this is the object that will be proxied by Spring's AOP infrastructure -->   <bean id="fooService" class="x.y.service.DefaultFooService"/>   <!-- this is the actual advice itself -->   <bean id="profiler" class="x.y.SimpleProfiler"/>   <aop:config>      <aop:aspect ref="profiler">         <aop:pointcut id="theExecutionOfSomeFooServiceMethod"                    expression="execution(* x.y.service.FooService.getFoo(String,int))                    and args(name, age)"/>         <aop:around pointcut-ref="theExecutionOfSomeFooServiceMethod"                  method="profile"/>      </aop:aspect>   </aop:config></beans>

如果使用下面的驅(qū)動(dòng)腳本,我們將在標(biāo)準(zhǔn)輸出上得到如下的輸出:

import org.springframework.beans.factory.BeanFactory;import org.springframework.context.support.ClassPathXmlApplicationContext;import x.y.service.FooService;public final class Boot {   public static void main(final String[] args) throws Exception {      BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");      FooService foo = (FooService) ctx.getBean("fooService");      foo.getFoo("Pengo", 12);   }}
StopWatch 'Profiling for 'Pengo' and '12'': running time (millis) = 0-----------------------------------------ms     %     Task name-----------------------------------------00000  ?  execution(getFoo)

6.3.3.7. 通知順序

當(dāng)同一個(gè)切入點(diǎn)(執(zhí)行方法)上有多個(gè)通知需要執(zhí)行時(shí),執(zhí)行順序規(guī)則在 Section 6.2.4.7, “通知(Advice)順序” 已經(jīng)提及了。 切面的優(yōu)先級(jí)通過切面的支持bean是否實(shí)現(xiàn)了Ordered接口來決定。

6.3.4. 引入

Intrduction (在AspectJ中成為inter-type聲明)允許一個(gè)切面聲明一個(gè)通知對(duì)象實(shí)現(xiàn)指定接口,并且提供了一個(gè)接口實(shí)現(xiàn)類來代表這些對(duì)象。

aop:aspect 內(nèi)部使用 aop:declare-parents 元素定義Introduction。 該元素用于用來聲明所匹配的類型有了一個(gè)新的父類型(所以有了這個(gè)名字)。 例如,給定接口 UsageTracked,以及這個(gè)接口的一個(gè)實(shí)現(xiàn)類 DefaultUsageTracked, 下面聲明的切面所有實(shí)現(xiàn)service接口的類同時(shí)實(shí)現(xiàn) UsageTracked 接口。(比如為了通過JMX暴露statistics。)

<aop:aspect id="usageTrackerAspect" ref="usageTracking">  <aop:declare-parents  types-matching="com.xzy.myapp.service.*+",  implement-interface="UsageTracked"  default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>  <aop:beforepointcut="com.xyz.myapp.SystemArchitecture.businessService()  and this(usageTracked)"method="recordUsage"/></aop:aspect>

usageTracking bean的支持類可以包含下面的方法:

public void recordUsage(UsageTracked usageTracked) {    usageTracked.incrementUseCount();}

欲實(shí)現(xiàn)的接口由 implement-interface 屬性來指定。 types-matching 屬性的值是一個(gè)AspectJ類型模式:- 任何匹配類型的bean會(huì)實(shí)現(xiàn) UsageTracked 接口。 注意在Before通知的例子中,srevice bean可以用作 UsageTracked 接口的實(shí)現(xiàn)。 如果編程形式訪問一個(gè)bean,你可以這樣來寫:

UsageTracked usageTracked = (UsageTracked) context.getBean("myService");

6.3.5. 切面實(shí)例化模型

Schema-defined切面僅支持一種實(shí)例化模型就是singlton模型。其他的實(shí)例化模型或許在未來版本中將得到支持。

6.3.6. Advisors

"advisors"這個(gè)概念來自Spring1.2對(duì)AOP的支持,在AspectJ中是沒有等價(jià)的概念。 advisor就像一個(gè)小的自包含的切面,這個(gè)切面只有一個(gè)通知。 切面自身通過一個(gè)bean表示,并且必須實(shí)現(xiàn)一個(gè)通知接口, 在 Section 7.3.2, “Spring里的通知類型” 中我們會(huì)討論相應(yīng)的接口。Advisors可以很好的利用AspectJ切入點(diǎn)表達(dá)式。

Spring 2.0 通過 <aop:advisor> 元素來支持advisor 概念。 你將會(huì)發(fā)現(xiàn)它大多數(shù)情況下會(huì)和transactional advice一起使用,transactional advice在Spring 2.0中有自己的命名空間。格式如下:

<aop:config>  <aop:pointcut id="businessService"expression="execution(* com.xyz.myapp.service.*.*(..))"/>  <aop:advisor  pointcut-ref="businessService"  advice-ref="tx-advice"/></aop:config><tx:advice id="tx-advice"><tx:attributes><tx:method name="*" propagation="REQUIRED"/>  </tx:attributes></tx:advice>

和在上面使用的 pointcut-ref 屬性一樣,你還可以使用 pointcut 屬性來定義一個(gè)內(nèi)聯(lián)的切入點(diǎn)表達(dá)式。

為了定義一個(gè)advisord的優(yōu)先級(jí)以便讓通知可以有序,使用 order 屬性來定義 advisor的值 Ordered 。

6.3.7. 例子

讓我們來看看在 Section 6.2.7, “例子” 提過并發(fā)鎖失敗重試的例子,如果使用schema對(duì)這個(gè)例子進(jìn)行重寫是什么效果。

因?yàn)椴l(fā)鎖的關(guān)系,有時(shí)候business services可能會(huì)失敗(例如,死鎖失?。?。 如果重新嘗試一下,很有可能就會(huì)成功。對(duì)于business services來說,重試幾次是很正常的(Idempotent操作不需要用戶參與,否則會(huì)得出矛盾的結(jié)論) 我們可能需要透明的重試操作以避免讓客戶看見 PessimisticLockingFailureException 例外被拋出。 很明顯,在一個(gè)橫切多層的情況下,這是非常有必要的,因此通過切面來實(shí)現(xiàn)是很理想的。

因?yàn)槲覀兿胍卦嚥僮?,我們?huì)需要使用到環(huán)繞通知,這樣我們就可以多次調(diào)用proceed()方法。 下面是簡(jiǎn)單的切面實(shí)現(xiàn)(只是一個(gè)schema支持的普通Java 類):

public class ConcurrentOperationExecutor implements Ordered {      private static final int DEFAULT_MAX_RETRIES = 2;   private int maxRetries = DEFAULT_MAX_RETRIES;   private int order = 1;   public void setMaxRetries(int maxRetries) {      this.maxRetries = maxRetries;   }      public int getOrder() {      return this.order;   }      public void setOrder(int order) {      this.order = order;   }      public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {       int numAttempts = 0;      PessimisticLockingFailureException lockFailureException;      do {         numAttempts++;         try {             return pjp.proceed();         }         catch(PessimisticLockingFailureException ex) {            lockFailureException = ex;         }      }      while(numAttempts <= this.maxRetries);      throw lockFailureException;   }}

請(qǐng)注意切面實(shí)現(xiàn)了 Ordered 接口,這樣我們就可以把切面的優(yōu)先級(jí)設(shè)定為高于事務(wù)通知(我們每次重試的時(shí)候都想要在一個(gè)全新的事務(wù)中進(jìn)行)。 maxRetriesorder 屬性都可以在Spring中配置。 主要的動(dòng)作在 doConcurrentOperation 這個(gè)環(huán)繞通知中發(fā)生。 請(qǐng)注意這個(gè)時(shí)候我們所有的 businessService() 方法都會(huì)使用這個(gè)重試策略。 我們首先會(huì)嘗試處理,然后如果我們得到一個(gè) PessimisticLockingFailureException 異常,我們只需要簡(jiǎn)單的重試,直到我們耗盡所有預(yù)設(shè)的重試次數(shù)。

這個(gè)類跟我們?cè)贎AspectJ的例子中使用的是相同的,只是沒有使用注解。

對(duì)應(yīng)的Spring配置如下:

<aop:config>  <aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">    <aop:pointcut id="idempotentOperation"        expression="execution(* com.xyz.myapp.service.*.*(..))"/>           <aop:around       pointcut-ref="idempotentOperation"       method="doConcurrentOperation"/>    </aop:aspect></aop:config><bean id="concurrentOperationExecutor"  class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">     <property name="maxRetries" value="3"/>     <property name="order" value="100"/>  </bean>

請(qǐng)注意我們現(xiàn)在假設(shè)所有的bussiness services都是idempotent。如果不是這樣,我們可以改寫切面,加上 Idempotent 注解,讓它只調(diào)用idempotent:

@Retention(RetentionPolicy.RUNTIME)public @interface Idempotent {  // marker annotation}

并且對(duì)service操作的實(shí)現(xiàn)進(jìn)行注解。這樣如果你只希望改變切面使得idempotent的操作會(huì)嘗試多次,你只需要改寫切入點(diǎn)表達(dá)式,這樣只有 @Idempotent 操作會(huì)匹配:

  <aop:pointcut id="idempotentOperation"expression="execution(* com.xyz.myapp.service.*.*(..)) and@annotation(com.xyz.myapp.service.Idempotent)"/>

6.4. AOP聲明風(fēng)格的選擇

當(dāng)你確定切面是實(shí)現(xiàn)一個(gè)給定需求的最佳方法時(shí),你如何選擇是使用Spring AOP還是AspectJ,以及選擇 Aspect語言(代碼)風(fēng)格、@AspectJ聲明風(fēng)格或XML風(fēng)格?這個(gè)決定會(huì)受到多個(gè)因素的影響,包括應(yīng)用的需求、 開發(fā)工具和小組對(duì)AOP的精通程度。

6.4.1. Spring AOP還是完全用AspectJ?

做能起作用的最簡(jiǎn)單的事。Spring AOP比完全使用AspectJ更加簡(jiǎn)單,因?yàn)樗恍枰階spectJ的編譯器/織入器到你開發(fā)和構(gòu)建過程中。 如果你僅僅需要在Spring bean上通知執(zhí)行操作,那么Spring AOP是合適的選擇。如果你需要通知domain對(duì)象或其它沒有在Spring容器中 管理的任意對(duì)象,那么你需要使用AspectJ。如果你想通知除了簡(jiǎn)單的方法執(zhí)行之外的連接點(diǎn)(如:調(diào)用連接點(diǎn)、字段get或set的連接點(diǎn)等等), 也需要使用AspectJ。

當(dāng)使用AspectJ時(shí),你可以選擇使用AspectJ語言(也稱為“代碼風(fēng)格”)或@AspectJ注解風(fēng)格。 如果切面在你的設(shè)計(jì)中扮演一個(gè)很大的角色,并且你能在Eclipse中使用AspectJ Development Tools (AJDT), 那么首選AspectJ語言 :- 因?yàn)樵撜Z言專門被設(shè)計(jì)用來編寫切面,所以會(huì)更清晰、更簡(jiǎn)單。如果你沒有使用 Eclipse,或者在你的應(yīng)用中只有很少的切面并沒有作為一個(gè)主要的角色,你或許應(yīng)該考慮使用@AspectJ風(fēng)格 并在你的IDE中附加一個(gè)普通的Java編輯器,并且在你的構(gòu)建腳本中增加切面織入(鏈接)的段落。

6.4.2. Spring AOP中使用@AspectJ還是XML?

如果你選擇使用Spring AOP,那么你可以選擇@AspectJ或者XML風(fēng)格??偟膩碚f,如果你使用Java 5, 我們建議使用@AspectJ風(fēng)格。顯然如果你不是運(yùn)行在Java 5上,XML風(fēng)格是最佳選擇。XML和@AspectJ 之間權(quán)衡的細(xì)節(jié)將在下面進(jìn)行討論。

XML風(fēng)格對(duì)現(xiàn)有的Spring用戶來說更加習(xí)慣。它可以使用在任何Java級(jí)別中(參考連接點(diǎn)表達(dá)式內(nèi)部的命名連接點(diǎn),雖然它也需要Java 5) 并且通過純粹的POJO來支持。當(dāng)使用AOP作為工具來配置企業(yè)服務(wù)時(shí)(一個(gè)好的例子是當(dāng)你認(rèn)為連接點(diǎn)表達(dá)式是你的配置中的一部分時(shí), 你可能想單獨(dú)更改它)XML會(huì)是一個(gè)很好的選擇。對(duì)于XML風(fēng)格,從你的配置中可以清晰的表明在系統(tǒng)中存在那些切面。

XML風(fēng)格有兩個(gè)缺點(diǎn)。第一是它不能完全將需求實(shí)現(xiàn)的地方封裝到一個(gè)位置。DRY原則中說系統(tǒng)中的每一項(xiàng)知識(shí)都必須具有單一、無歧義、權(quán)威的表示。 當(dāng)使用XML風(fēng)格時(shí),如何實(shí)現(xiàn)一個(gè)需求的知識(shí)被分割到支撐類的聲明中以及XML配置文件中。當(dāng)使用@AspectJ風(fēng)格時(shí)就只有一個(gè)單獨(dú)的模塊 -切面- 信息被封裝了起來。 第二是XML風(fēng)格同@AspectJ風(fēng)格所能表達(dá)的內(nèi)容相比有更多的限制:僅僅支持"singleton"切面實(shí)例模型,并且不能在XML中組合命名連接點(diǎn)的聲明。 例如,在@AspectJ風(fēng)格中我們可以編寫如下的內(nèi)容:

  @Pointcut(execution(* get*()))  public void propertyAccess() {}  @Pointcut(execution(org.xyz.Account+ *(..))  public void operationReturningAnAccount() {}  @Pointcut(propertyAccess() && operationReturningAnAccount())  public void accountPropertyAccess() {}

在XML風(fēng)格中能聲明開頭的兩個(gè)連接點(diǎn):

  <aop:pointcut id="propertyAccess"       expression="execution(* get*())"/>  <aop:pointcut id="operationReturningAnAccount"       expression="execution(org.xyz.Account+ *(..))"/>

但是不能通過組合這些來定義accountPropertyAccess連接點(diǎn)

@AspectJ風(fēng)格支持其它的實(shí)例模型以及更豐富的連接點(diǎn)組合。它具有將將切面保持為一個(gè)模塊單元的優(yōu)點(diǎn)。 還有一個(gè)優(yōu)點(diǎn)就是@AspectJ切面能被Spring AOP和AspectJ兩者都理解 - 所以如果稍后你認(rèn)為你需要AspectJ 的能力去實(shí)現(xiàn)附加的需求,那么你非常容易轉(zhuǎn)移到基于AspectJ的途徑??偠灾?,我們更喜歡@AspectJ風(fēng)格只要你有切面 去做超出簡(jiǎn)單的“配置”企業(yè)服務(wù)之外的事情。

6.5. 混合切面類型

我們完全可以混合使用以下幾種風(fēng)格的切面定義:使用自動(dòng)代理的@AspectJ 風(fēng)格的切面,schema-defined <aop:aspect> 的切面,和用 <aop:advisor> 聲明的advisor,甚至是使用Spring 1.2風(fēng)格的代理和攔截器。 由于以上幾種風(fēng)格的切面定義的都使用了相同的底層機(jī)制,因此可以很好的共存。

6.6. 代理機(jī)制

Spring AOP部分使用JDK動(dòng)態(tài)代理或者CGLIB來為目標(biāo)對(duì)象創(chuàng)建代理。(建議盡量使用JDK的動(dòng)態(tài)代理)

如果被代理的目標(biāo)對(duì)象實(shí)現(xiàn)了至少一個(gè)接口,則會(huì)使用JDK動(dòng)態(tài)代理。所有該目標(biāo)類型實(shí)現(xiàn)的接口都將被代理。若該目標(biāo)對(duì)象沒有實(shí)現(xiàn)任何接口,則創(chuàng)建一個(gè)CGLIB代理。

如果你希望強(qiáng)制使用CGLIB代理,(例如:希望代理目標(biāo)對(duì)象的所有方法,而不只是實(shí)現(xiàn)自接口的方法)那也可以。但是需要考慮以下問題:

  • 無法通知(advise)Final 方法,因?yàn)樗麄儾荒鼙桓矊憽?/p>

  • 你需要將CGLIB 2二進(jìn)制發(fā)行包放在classpath下面,與之相較JDK本身就提供了動(dòng)態(tài)代理

強(qiáng)制使用CGLIB代理需要將 <aop:config>proxy-target-class 屬性設(shè)為true:

<aop:config proxy-target-class="true">...</aop:config>

當(dāng)需要使用CGLIB代理和@AspectJ自動(dòng)代理支持,請(qǐng)按照如下的方式設(shè)置 <aop:aspectj-autoproxy>proxy-target-class 屬性:

<aop:aspectj-autoproxy proxy-target-class="true"/>

6.7. 編程方式創(chuàng)建@AspectJ代理

除了在配置文件中使用 <aop:config> 或者 <aop:aspectj-autoproxy> 來聲明切面。 同樣可以通過編程方式來創(chuàng)建代理通知(advise)目標(biāo)對(duì)象。關(guān)于Spring AOP API的詳細(xì)介紹,請(qǐng)參看下一章。這里我們重點(diǎn)介紹自動(dòng)創(chuàng)建代理。

org.springframework.aop.aspectj.annotation.AspectJProxyFactory 可以為@AspectJ切面的目標(biāo)對(duì)象創(chuàng)建一個(gè)代理。該類的基本用法非常簡(jiǎn)單,示例如下。請(qǐng)參看Javadoc獲取更詳細(xì)的信息。

// create a factory that can generate a proxy for the given target objectAspectJProxyFactory factory = new AspectJProxyFactory(targetObject); // add an aspect, the class must be an @AspectJ aspect// you can call this as many times as you need with different aspectsfactory.addAspect(SecurityManager.class);// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspectfactory.addAspect(usageTracker);// now get the proxy object...MyInterfaceType proxy = factory.getProxy();

6.8. 在Spring應(yīng)用中使用AspectJ

到目前為止本章討論的一直是純Spring AOP。 在這一節(jié)里面我們將介紹如何使用AspectJ compiler/weaver來代替Spring AOP或者作為它的補(bǔ)充,因?yàn)橛行r(shí)候Spring AOP單獨(dú)提供的功能也許并不能滿足你的需要。

Spring提供了一個(gè)小巧的AspectJ aspect library (你可以在程序發(fā)行版本中單獨(dú)使用 spring-aspects.jar 文件,并將其加入到classpath下以使用其中的切面)。 Section 6.8.1, “在Spring中使用AspectJ來為domain object進(jìn)行依賴注入”Section 6.8.2, “Spring中其他的AspectJ切面” 討論了該庫和如何使用該庫。 Section 6.8.3, “使用Spring IoC來配置AspectJ的切面” 討論了如何對(duì)通過AspectJ compiler織入的AspectJ切面進(jìn)行依賴注入。 最后Section 6.8.4, “在Spring應(yīng)用中使用AspectJ Load-time weaving(LTW)”介紹了使用AspectJ的Spring應(yīng)用程序如何裝載期織入(load-time weaving)。

6.8.1. 在Spring中使用AspectJ來為domain object進(jìn)行依賴注入

Spring容器對(duì)application context中定義的bean進(jìn)行實(shí)例化和配置。 同樣也可以通過bean factory來為一個(gè)已經(jīng)存在且已經(jīng)定義為spring bean的對(duì)象應(yīng)用所包含的配置信息。 spring-aspects.jar中包含了一個(gè)annotation-driven的切面,提供了能為任何對(duì)象進(jìn)行依賴注入的能力。 這樣的支持旨在為 脫離容器管理 創(chuàng)建的對(duì)象進(jìn)行依賴注入。 Domain object經(jīng)常處于這樣的情形:它們可能是通過 new 操作符創(chuàng)建的對(duì)象, 也可能是ORM工具查詢數(shù)據(jù)庫的返回結(jié)果對(duì)象。

org.springframework.orm.hibernate.support 中的類 DependencyInjectionInterceptorFactoryBean 可以讓Spring為Hibernate創(chuàng)建并且配置prototype類型的domain object(使用自動(dòng)裝配或者確切命名的bean原型定義)。 當(dāng)然,攔截器不支持配置你編程方式創(chuàng)建的對(duì)象而非檢索數(shù)據(jù)庫返回的對(duì)象。 其他framework也會(huì)提供類似的技術(shù)。仍是那句話,Be Pragramatic選擇能滿足你需求的方法中最簡(jiǎn)單的那個(gè)。 請(qǐng)注意前面提及的類 沒有 隨Spring發(fā)行包一起發(fā)布。 如果你希望使用該類,需要從Spring CVS Respository上下載并且自行編譯。 你可以在Spring CVS respository下的 'sandbox' 目錄下找到該文件。

@Configurable 注解標(biāo)記了一個(gè)類可以通過Spring-driven方式來配置。 在最簡(jiǎn)單的情況下,我們只把它當(dāng)作標(biāo)記注解:

package com.xyz.myapp.domain;import org.springframework.beans.factory.annotation.Configurable;@Configurablepublic class Account {   ...}

當(dāng)只是簡(jiǎn)單地作為一個(gè)標(biāo)記接口來使用的時(shí)候,Spring將采用和該已注解的類型(比如Account類)全名 (com.xyz.myapp.domain.Account)一致的bean原型定義來配置一個(gè)新實(shí)例。 由于一個(gè)bean默認(rèn)的名字就是它的全名,所以一個(gè)比較方便的辦法就是省略定義中的id屬性:

<bean class="com.xyz.myapp.domain.Account" scope="prototype">  <property name="fundsTransferService" ref="fundsTransferService"/>  ...</bean>

如果你希望明確的指定bean原型定義的名字,你可以在注解中直接定義:

package com.xyz.myapp.domain;import org.springframework.beans.factory.annotation.Configurable;@Configurable("account")public class Account {   ...}

Spring會(huì)查找名字為"account"的bean定義,并使用它作為原型定義來配置一個(gè)新的Account對(duì)象。

你也可以使用自動(dòng)裝配來避免手工指定原型定義的名字。 只要設(shè)置 @Configurable 注解中的autowire屬性就可以讓Spring來自動(dòng)裝配了: @Configurable(autowire=Autowire.BY_TYPE) 或者 @Configurable(autowire=Autowire.BY_NAME,這樣就可以按類型或者按名字自動(dòng)裝配了。

最后,你可以設(shè)置 dependencyCheck 屬性,通過設(shè)置,Spring對(duì)新創(chuàng)建和配置的對(duì)象的對(duì)象引用進(jìn)行校驗(yàn) (例如:@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true) )。 如果這個(gè)屬性被設(shè)為true,Spring會(huì)在配置結(jié)束后校驗(yàn)除了primitives和collections類型的所有的屬性是否都被賦值了。

僅僅使用注解并沒有做任何事情。但當(dāng)注解存在時(shí),spring-aspects.jar中的 AnnotationBeanConfigurerAspect 就起作用了。 實(shí)質(zhì)上切面做了這些:當(dāng)初始化一個(gè)有 @Configurable 注解的新對(duì)象時(shí),Spring按照注解中的屬性來配置這個(gè)新創(chuàng)建的對(duì)象。 要實(shí)現(xiàn)上述的操作,已注解的類型必須由AspectJ weaver來織入 - 你可以使用一個(gè) build-time ant/maven任務(wù)來完成 (參見AspectJ Development Environment Guide) 或者使用load-time weaving(參見 Section 6.8.4, “在Spring應(yīng)用中使用AspectJ Load-time weaving(LTW)”)。

AnnotationBeanConfigurerAspect 本身也需要Spring來配置(獲得bean factory的引用,使用bean factory配置新的對(duì)象)。 為此Spring AOP命名空間定義了一個(gè)非常方便的標(biāo)簽。如下所示,可以很簡(jiǎn)單的在application context配置文件包含這個(gè)標(biāo)簽中。

<aop:spring-configured/>

如果你使用DTD代替Schema,對(duì)應(yīng)的定義如下:

<bean  class="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"  factory-method="aspectOf"/>

在切面配置完成 之前 創(chuàng)建的@Configurable對(duì)象實(shí)例會(huì)導(dǎo)致在log中留下一個(gè)warning,并且任何對(duì)于該對(duì)象的配置都不會(huì)生效。 舉一個(gè)例子,一個(gè)Spring管理配置的bean在被Spring初始化的時(shí)候創(chuàng)建了一個(gè)domain object。 對(duì)于這樣的情況,你需要定義bean屬性中的"depends-on"屬性來手動(dòng)指定該bean依賴于configuration切面。

<bean id="myService"  class="com.xzy.myapp.service.MyService"  depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">  ...</bean>

6.8.1.1.  @Configurable object的單元測(cè)試

提供 @Configurable 支持的一個(gè)目的就是使得domain object的單元測(cè)試可以獨(dú)立進(jìn)行,不需要通過硬編碼查找各種倚賴關(guān)系。 如果 @Configurable 類型沒有通過AspectJ織入, 則在單元測(cè)試過程中注解不會(huì)起到任何作用,測(cè)試中你可以簡(jiǎn)單的為對(duì)象的mock或者stub屬性賦值,并且和正常情況一樣的去使用該對(duì)象。 如果 @Configurable 類型通過AspectJ織入, 我們依然可以脫離容器進(jìn)行單元測(cè)試,不過每次創(chuàng)建一個(gè)新的 @Configurable 對(duì)象時(shí)都會(huì)看到一個(gè)warning標(biāo)示該對(duì)象不受Spring管理配置。

6.8.1.2. 多application context情況下的處理

AnnotationBeanConfigurerAspect 通過一個(gè)AspectJ singleton切面來實(shí)現(xiàn)對(duì) @Configurable 的支持。 一個(gè)singleton切面的作用域和一個(gè)靜態(tài)變量的作用域是一樣的,例如,對(duì)于每一個(gè)classloader有一個(gè)切面來定義類型。 這就意味著如果你在一個(gè)classloader層次結(jié)構(gòu)中定義了多個(gè)application context的時(shí)候就需要考慮在哪里定義 <aop:spring-configured/> bean和在哪個(gè)classpath下放置Srping-aspects.jar。

考慮一下典型的Spring web項(xiàng)目,一般都是由一個(gè)父application context定義大部分business service和所需要的其他資源,然后每一個(gè)servlet擁有一個(gè)子application context定義。 所有這些context共存于同一個(gè)classloader hierarchy下,因此對(duì)于全體context,AnnotationBeanConfigurerAspect 僅可以維護(hù)一個(gè)引用。 在這樣的情況下,我們推薦在父application context中定義 <aop:spring-configured/> bean: 這里所定義的service可能是你希望注入domain object的。 這樣做的結(jié)果是你不能為子application context中使用@Configurable的domain object配置bean引用(可能你也根本就不希望那么做?。?

當(dāng)在一個(gè)容器中部署多個(gè)web-app的時(shí)候,請(qǐng)確保每一個(gè)web-application使用自己的classloader來加載spring-aspects.jar中的類(例如將spring-aspects.jar放在WEB-INF/lib目錄下)。 如果spring-aspects.jar被放在了容器的classpath下(因此也被父classloader加載),則所有的web application將共享一個(gè)aspect實(shí)例,這可能并不是你所想要的。

6.8.2. Spring中其他的AspectJ切面

除了 @Configurable 支持,spring-aspects.jar包含了一個(gè)AspectJ切面可以用來為那些使用了 @Transactional annotation 的類型和方法驅(qū)動(dòng)Spring事務(wù)管理(參見 Chapter 9, 事務(wù)管理)。 提供這個(gè)的主要目的是有些用戶希望脫離Spring容器使用Spring的事務(wù)管理。

解析@Transactional annotations的切面是AnnotationTransactionAspect。 當(dāng)使用這個(gè)切面時(shí),你必須注解這個(gè)實(shí)現(xiàn)類(和/或這個(gè)類中的方法),而不是這個(gè)類實(shí)現(xiàn)的接口(如果有)。 AspectJ允許在接口上注解的Java規(guī)則 不被繼承。

類之上的一個(gè)@Transactional注解為該類中任何public操作的執(zhí)行指定了默認(rèn)的事務(wù)語義。

類內(nèi)部方法上的一個(gè)@Transactional注解會(huì)覆蓋類注解(如果存在)所給定的默認(rèn)的事務(wù)語義。 具有public、protected和default修飾符的方法都可以被注解。直接注解protected和default方法是讓這個(gè)操作的執(zhí)行 獲得事務(wù)劃分的唯一途徑。

對(duì)于AspectJ程序員,希望使用Spring管理配置和事務(wù)管理支持,不過他們不想(或者不能)使用注解,spring-aspects.jar也包含了一些抽象切面供你繼承來提供你自己的切入點(diǎn)定義。 參見 AbstractBeanConfigurerAspectAbstractTransactionAspect 的Javadoc獲取更多信息。 作為一個(gè)例子,下面的代碼片斷展示了如何編寫一個(gè)切面,然后通過bean原型定義中和類全名匹配的來配置domian object中所有的實(shí)例:

public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {  public DomainObjectConfiguration() {    setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());  }  // the creation of a new bean (any object in the domain model)  protected pointcut beanCreation(Object beanInstance) :    initialization(new(..)) &&    SystemArchitecture.inDomainModel() &&     this(beanInstance);      }

6.8.3. 使用Spring IoC來配置AspectJ的切面

當(dāng)在Spring application中使用AspectJ的時(shí)候,很自然的會(huì)想到用Spring來管理這些切面。 AspectJ runtime自身負(fù)責(zé)切面的創(chuàng)建,這意味著通過Spring來管理AspectJ 創(chuàng)建切面依賴于切面所使用的AspectJ instantiation model(per-clause)。

大多數(shù)AspectJ切面都是 singleton 切面。 管理這些切面非常容易,和通常一樣創(chuàng)建一個(gè)bean定義引用該切面類型就可以了,并且在bean定義中包含 'factory-method="aspectOf"' 這個(gè)屬性。 這確保Spring從AspectJ獲取切面實(shí)例而不是嘗試自己去創(chuàng)建該實(shí)例。示例如下:

<bean id="profiler" class="com.xyz.profiler.Profiler"  factory-method="aspectOf">  <property name="profilingStrategy" ref="jamonProfilingStrategy"/></bean>

對(duì)于non-singleton的切面,最簡(jiǎn)單的配置管理方法是定義一個(gè)bean原型定義并且使用@Configurable支持,這樣就可以在切面被AspectJ runtime創(chuàng)建后管理它們。

如果你希望一些@AspectJ切面使用AspectJ來織入(例如使用load-time織入domain object) 和另一些@AspectJ切面使用Spring AOP,而這些切面都是由Spring來管理的,那你就需要告訴Spring AOP @AspectJ自動(dòng)代理支持那些切面需要被自動(dòng)代理。 你可以通過在 <aop:aspectj-autoproxy> 聲明中使用一個(gè)或多個(gè) <include/>。 每一個(gè)指定了一種命名格式,只有bean命名至少符合其中一種情況下才會(huì)使用Spring AOP自動(dòng)代理配置:

<aop:aspectj-autoproxy>  <include name="thisBean"/>  <include name="thatBean"/></aop:aspectj-autoproxy>

6.8.4. 在Spring應(yīng)用中使用AspectJ Load-time weaving(LTW)

Load-time weaving(LTW)指的是在虛擬機(jī)載入字節(jié)碼文件時(shí)動(dòng)態(tài)織入AspectJ切面。 關(guān)于LTW的詳細(xì)信息,請(qǐng)查看 LTW section of the AspectJ Development Environment Guide。 在這里我們重點(diǎn)來看一下Java 5環(huán)境下Spring應(yīng)用如何配置LTW。

LTW需要定義一個(gè) aop.xml,并將其置于META-INF目錄。 AspectJ會(huì)自動(dòng)查找所有可見的classpath下的META-INF/aop.xml文件,并且通過定義內(nèi)容的合集來配置自身。

一個(gè)基本的META-INF/aop.xml文件應(yīng)該如下所示:

<!DOCTYPE aspectj PUBLIC  "-//AspectJ//DTD//EN""http://www./aspectj/dtd/aspectj.dtd"><aspectj>   <weaver> <include within="com.xyz.myapp..*"/>   </weaver></aspectj>

'<include/>'的內(nèi)容告訴AspectJ那些類型需要被納入織入過程。使用包名前綴并加上"..*"(表示該子包中的所有類型)是一個(gè)不錯(cuò)的默認(rèn)設(shè)定。 使用include元素是非常重要的,不然AspectJ會(huì)查找每一個(gè)應(yīng)用里面用到的類型(包括Spring的庫和其它許多相關(guān)庫)。通常你并不希望織入這些類型并且不愿意承擔(dān)AspectJ嘗試去匹配的開銷。

希望在日志中記錄LTW的活動(dòng),請(qǐng)?zhí)砑尤缦逻x項(xiàng):

<!DOCTYPE aspectj PUBLIC      "-//AspectJ//DTD//EN"    "http://www./aspectj/dtd/aspectj.dtd">    <aspectj>       <weaver  options="-showWeaveInfo              -XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler"> <include within="com.xyz.myapp..*"/>   </weaver></aspectj>

最后,如果希望精確的控制使用哪些切面,可以使用 aspects。 默認(rèn)情況下所有定義的切面都將被織入(spring-aspects.jar包含了META-INF/aop.xml,定義了配置管理和事務(wù)管理切面)。 如果你在使用spring-aspects.jar,但是只希望使用配制管理切面而不需要事務(wù)管理的話,你可以像下面那樣定義:

<!DOCTYPE aspectj PUBLIC  "-//AspectJ//DTD//EN""http://www./aspectj/dtd/aspectj.dtd"><aspectj>   <aspects>  <include within="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"/>   </aspects>   <weaver    options="-showWeaveInfo -XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler">    <include within="com.xyz.myapp..*"/>   </weaver></aspectj>

在Java 5平臺(tái)下,LTW可以通過虛擬機(jī)的參數(shù)來啟用。

-javaagent:<path-to-ajlibs>/aspectjweaver.jar

6.9. 其它資源

更多關(guān)于AspectJ的信息可以查看 AspectJ home page

Eclipse AspectJ by Adrian Colyer et. al. (Addison-Wesley, 2005)全面介紹并提供了AspectJ語言參考。

AspectJ in Action by Ramnivas Laddad (Manning, 2003)是一本非常出色介紹AOP的書籍;全書著重介紹了AspectJ,但也對(duì)一些通用的AOP場(chǎng)景進(jìn)行了比較深入的研究。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    国产盗摄精品一区二区视频| 国产麻豆精品福利在线| 国产一区二区三区草莓av| 四十女人口红哪个色好看| 激情综合五月开心久久| 免费黄色一区二区三区| 国产成人精品国产成人亚洲| 国产亚洲欧美一区二区| 色小姐干香蕉在线综合网| 欧美午夜伦理在线观看| 久热香蕉精品视频在线播放| 婷婷激情四射在线观看视频| 中国少妇精品偷拍视频 | 国产精品欧美激情在线观看| 四十女人口红哪个色好看| 亚洲专区中文字幕视频| 91精品国产综合久久福利| 人妻内射精品一区二区| 国产精品香蕉一级免费| 国产一区二区三区免费福利| 国产三级不卡在线观看视频| 亚洲男人的天堂就去爱| 欧美有码黄片免费在线视频| 在线观看免费视频你懂的| 中文字幕一区二区三区中文| 欧美亚洲美女资源国产| 欧美综合色婷婷欧美激情| 狠狠干狠狠操在线播放| 人妻人妻人人妻人人澡| 欧美激情一区二区亚洲专区| 四季精品人妻av一区二区三区| 国产又猛又大又长又粗| 日本不卡视频在线观看| 欧美成人高清在线播放| 午夜精品黄片在线播放| 成人日韩视频中文字幕| 色综合久久六月婷婷中文字幕| 深夜福利亚洲高清性感| 九九视频通过这里有精品| 激情内射亚洲一区二区三区| 久久精品国产第一区二区三区|