日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > javascript >内容正文

javascript

基于@AspectJ配置Spring AOP之一--转

發(fā)布時(shí)間:2025/4/5 javascript 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于@AspectJ配置Spring AOP之一--转 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

原文地址:http://tech.it168.com/j/2007-08-30/200708302209432.shtml

概述?

??? 在低版本Spring中定義一個(gè)切面是比較麻煩的,需要實(shí)現(xiàn)特定的接口,并進(jìn)行一些較為復(fù)雜的配置,低版本Spring AOP的配置是被批評(píng)最多的地方。Spring聽(tīng)取這方面的批評(píng)聲音,并下決心徹底改變這一現(xiàn)狀。在Spring2.0中,Spring AOP已經(jīng)煥然一新,你可以使用@AspectJ注解非常容易的定義一個(gè)切面,不需要實(shí)現(xiàn)任何的接口。?

Spring2.0采用@AspectJ注解對(duì)POJO進(jìn)行標(biāo)注,從而定義一個(gè)包含切點(diǎn)信息和增強(qiáng)橫切邏輯的切面,Spring 2.0可以將這個(gè)切面織入到匹配的目標(biāo)Bean中。@AspectJ注解使用AspectJ切點(diǎn)表達(dá)式語(yǔ)法進(jìn)行切點(diǎn)定義,可以通過(guò)切點(diǎn)函數(shù)、運(yùn)算符、通配符等高級(jí)功能進(jìn)行切點(diǎn)定義,擁有強(qiáng)大的連接點(diǎn)描述能力。在你學(xué)習(xí)基于@AspectJ的切面技術(shù)后,恐怕你就再也沒(méi)有興趣使用低版本Spring AOP的實(shí)現(xiàn)技術(shù)了,畢竟馬落桃花馬前雪,兩者的易用性、便捷性是不可同日而語(yǔ)的。?

著手使用@AspectJ?

??? 我們知道在低版本的Spring AOP中,你必須使用Pointcut和Advice接口描述切點(diǎn)和增強(qiáng),并用Advisor組合兩者描述一個(gè)切面,@AspectJ則采用JDK 5.0的注解技術(shù)描述切點(diǎn)和增強(qiáng)類型,而增強(qiáng)的橫切邏輯就在被標(biāo)注的POJO中定義。?

使用前的準(zhǔn)備

??? 在使用@AspectJ之前,首先你得保證你所使用的JDK的版本是5.0及以上版本,否則無(wú)法使用注解技術(shù)。 Spring在處理@Aspect注解表達(dá)式時(shí),需要使用位于<SPRING_HOME>/lib/asm/目錄下asm的類包:asm-2.2.2.jar、asm-commons-2.2.2.jar和asm-util-2.2.2.jar。asm是輕量級(jí)的字節(jié)碼處理框架,因?yàn)镴ava的反射機(jī)制無(wú)法獲取入?yún)⒚?#xff0c;Spring就利用asm處理@AspectJ中所描述的方法入?yún)⒚?br />
此外,Spring采用AspectJ提供的@AspectJ注解類庫(kù)及相應(yīng)的解析類庫(kù),它位于<SPRING_HOME>/lib/aspectj目錄下,將目錄下的aspectjrt.jar和aspectjweaver.jar類包加入類路徑中。

一個(gè)簡(jiǎn)單的例子?

??? 在做好上節(jié)中所提到的前置工作后,我們就可以開(kāi)始編寫(xiě)一個(gè)基于@AspectJ的切面了,首先來(lái)看一個(gè)簡(jiǎn)單的例子,以便對(duì)@AspectJ有一個(gè)切身的認(rèn)識(shí)。

下面,我們用@AspectJ注解對(duì)一個(gè)POJO進(jìn)行標(biāo)注,將使其成為一個(gè)切面類:?
代碼清單 1 PreGreetingAspect:切面

package com.baobaotao.aspectj.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect ①通過(guò)該注解將PreGreetingAspect標(biāo)識(shí)為一個(gè)切面
public class PreGreetingAspect{
@Before("execution(* greetTo(..))") ②定義切點(diǎn)和增強(qiáng)類型
public void beforeGreeting(){③增強(qiáng)的橫切邏輯
System.out.println("How are you");
}
}

???? 我們“驚奇”地發(fā)現(xiàn)這個(gè)切面沒(méi)有實(shí)現(xiàn)任何特殊的接口,它只是一個(gè)普通的POJO。它特殊的地方在于使用了@AspectJ注解。?
首先,在PreGreetingAspect類定義處,標(biāo)注了一個(gè)@Aspectj注解,第三方處理程序就可以通過(guò)類是否擁有@Aspectj注解判斷其是否是一個(gè)切面,如①所示。

其次,在beforeGreeting()方法標(biāo)簽處,標(biāo)注了@Before注解,并為該注解提供了成員值"execution(* greetTo(..))",如②所示。②處的注解提供了兩個(gè)信息:@Before注解表示該增強(qiáng)是前置增強(qiáng),而成員值通過(guò)@ApsectJ切點(diǎn)表達(dá)式語(yǔ)法定義切點(diǎn):即在目標(biāo)類的greetTo()方法上織入增強(qiáng),greetTo()方法可以帶任意的入?yún)⒑腿我獾姆祷刂怠?/span>

最后,在③處的beforeGreeting()方法是增強(qiáng)的橫切邏輯,該橫切邏輯在目標(biāo)方法前調(diào)用,我們通過(guò)下圖描述這種關(guān)系:?
???

?圖 1 切面的信息構(gòu)成?

??? PreGreetingAspect類通過(guò)注解和代碼,將切點(diǎn)、增強(qiáng)類型和增強(qiáng)的橫切邏輯揉合到一個(gè)類中,使切面的定義渾然天成。如果在低版本Spring AOP中,你必須同時(shí)創(chuàng)建增強(qiáng)類,切點(diǎn)類以及切面類,并使三者聯(lián)合表達(dá)相同的信息。?

NaiveWaiter是一個(gè)Bean,它擁有一個(gè)greetTo()的方法,這個(gè)方法連接點(diǎn)匹配于上面我們通過(guò)@AspectJ所定義的切點(diǎn),為了方便后續(xù)的說(shuō)明,我們給出NaiveWaiter的代碼:

package com.baobaotao; public class NaiveWaiter implements Waiter { public void greetTo(String clientName) { System.out.println("NaiveWaiter:greet to "+clientName+"..."); } public void serveTo(String clientName){ System.out.println("NaiveWaiter:serving "+clientName+"..."); } }


下面,我們通過(guò)org.springframework.aop.aspectj.annotation.AspectJProxyFactory為NaiveWaiter生成織入PreGreetingAspect切面的代理,如代碼清單 2所示:?
代碼清單 2 AspectJProxyTest?

package com.baobaotao.aspectj.example; import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; import com.baobaotao.NaiveWaiter; import com.baobaotao.Waiter; publicclass AspectJProxyTest { Waiter target =new NaiveWaiter(); AspectJProxyFactory factory =new AspectJProxyFactory(); factory.setTarget(target); ① 設(shè)置目標(biāo)對(duì)象 factory.addAspect(PreGreetingAspect.class); ②添加切面類 Waiter proxy = factory.getProxy(); ③ 生成織入切面的代理對(duì)象 proxy.greetTo("John"); proxy.serveTo("John"); } }

Spring使用AspectJProxyFactory織入基于@AspectJ切面的工作。在①處,設(shè)置了目標(biāo)對(duì)象,在②處添加一個(gè)切面類,該類必須是帶@AspectJ注解的類,在③處,我們就可以獲取織入切面的代理對(duì)象了。?
接下來(lái),我們直接通過(guò)代理對(duì)象調(diào)用greetTo()和serveTo()代碼,它們產(chǎn)生以下的輸出信息:?
How are you ①表示greetTo()方法被成功地織入了切面?
greet to John...?
serving John...?
通過(guò)①處的輸出信息我們可以知道代理對(duì)象的greetTo()方法已經(jīng)織入了切面類所定義的增強(qiáng)邏輯了。

通過(guò)配置織入@AspectJ切面?

雖然可以通過(guò)編程的方式織入切面,但一般情況下,我們還是使用Spring的配置自動(dòng)完成創(chuàng)建代理織入切面的工作。?

<bean id="waiter"class="com.baobaotao.NaiveWaiter"/> ①目標(biāo)Bean ②使用了@AspectJ注解的切面類 <bean class="com.baobaotao.aspectj.example.PreGreetingAspect"/> ③自動(dòng)代理創(chuàng)建器,自動(dòng)將@AspectJ注解切面類織入到目標(biāo)Bean中 <bean class="org.springframework.aop.aspectj.annotation. AnnotationAwareAspectJAutoProxyCreator"/>

AnnotationAwareAspectJAutoProxyCreator能夠?qū)?#64;AspectJ注解切面的自動(dòng)織入到目標(biāo)Bean中。這里,PreGreetingAspect是使用了@AspectJ注解描述的切面類,而NaiveWaiter是匹配切點(diǎn)的目標(biāo)類。

如果使用基于Schema的aop命名空間進(jìn)行配置,事情就更簡(jiǎn)單了:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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-2.0.xsd http://www.springframework.org/schema/aop ② http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <aop:aspectj-autoproxy /> ③基于@AspectJ切面的驅(qū)動(dòng)器 <bean id="waiter"class="com.baobaotao.NaiveWaiter"/> <bean class="com.baobaotao.aspectj.example.PreGreetingAspect"/> </beans>

首先,在配置文件中引入aop命名空間,如①、②處所示,然后通過(guò)aop命名空間的<aop:aspectj-autoproxy />聲明自動(dòng)為Spring容器中那些匹配@AspectJ切面的Bean創(chuàng)建代理,織入切面。當(dāng)然,Spring在內(nèi)部依舊采用AnnotationAwareAspectJAutoProxyCreator進(jìn)行自動(dòng)代理的創(chuàng)建工作,但具體實(shí)現(xiàn)的細(xì)節(jié)已經(jīng)被<aop:aspectj-autoproxy />隱藏起來(lái)了。?

<aop:aspectj-autoproxy />有一個(gè)proxy-target-class屬性,默認(rèn)為false,表示使用JDK動(dòng)態(tài)代理織入增強(qiáng),當(dāng)配置為<aop:aspectj-autoproxy proxy-target-class="true" />時(shí),表示使用CGLib動(dòng)態(tài)代理技術(shù)織入增強(qiáng)。不過(guò)即使proxy-target-class設(shè)置為false,如果目標(biāo)類沒(méi)有聲明接口,則Spring將自動(dòng)使用CGLib動(dòng)態(tài)代理。

@AspectJ語(yǔ)法基礎(chǔ)?

@AspectJ使用JDK 5.0注解和正規(guī)的AspectJ 5的切點(diǎn)表達(dá)式語(yǔ)言描述切面,由于Spring只支持方法的連接點(diǎn),所以Spring僅支持部分AspectJ的切點(diǎn)語(yǔ)言。在這節(jié)時(shí),我們將對(duì)AspectJ切點(diǎn)表達(dá)式語(yǔ)言進(jìn)行必要的學(xué)習(xí)。

切點(diǎn)表達(dá)式函數(shù)

??? AspectJ 5的切點(diǎn)表達(dá)式由關(guān)鍵字和操作參數(shù)組成,如execution(* greetTo(..))的切點(diǎn)表達(dá)式,“execute”為關(guān)鍵字,而“* greetTo(..)”為操作參數(shù)。在這里,execute代表目標(biāo)類執(zhí)行某一方法,而“* greetTo(..)”是描述目標(biāo)方法的匹配模式串,兩者聯(lián)合起來(lái)所表示的切點(diǎn)匹配目標(biāo)類greetTo()方法的連接點(diǎn)。為了描述方便,我們將execution()稱作函數(shù),而將匹配串“* greetTo(..)”稱作函數(shù)的入?yún)ⅰ?
Spring支持9個(gè)@ApsectJ切點(diǎn)表達(dá)式函數(shù),它們用不同的方式描述目標(biāo)類的連接點(diǎn),根據(jù)描述對(duì)象的不同,可以將它們大致分為4種類型:?
? 方法切點(diǎn)函數(shù):通過(guò)描述目標(biāo)類方法信息定義連接點(diǎn);?
? 方法入?yún)⑶悬c(diǎn)函數(shù):通過(guò)描述目標(biāo)類方法入?yún)⒌男畔⒍x連接點(diǎn);?
? 目標(biāo)類切點(diǎn)函數(shù):通過(guò)描述目標(biāo)類類型信息定義連接點(diǎn);?
? 代理類切點(diǎn)函數(shù):通過(guò)描述目標(biāo)類的代理類的信息定義連接點(diǎn);?
???? 這4種類型的切點(diǎn)函數(shù),通過(guò)表 1進(jìn)行說(shuō)明:?
??? 表 1 切點(diǎn)函數(shù)?

類別 函數(shù) 入?yún)? 說(shuō)明
方法切點(diǎn)函數(shù) execution() 方法 匹配模式串 表示滿足某一匹配模式的所有目標(biāo)類方法連接點(diǎn)。如execution(* greetTo(..))表示所有目標(biāo)類中的greetTo()方法。
@annotation() 方法注 解類名 表示標(biāo)注了特定注解的目標(biāo)方法連接點(diǎn)。如@annotation(com.baobaotao.anno.NeedTest)表示任何標(biāo)注了@NeedTest注解的目標(biāo)類方法。
方法入?yún)⑶悬c(diǎn)函數(shù) args() 類名 通過(guò)判別目標(biāo)類方法運(yùn)行時(shí)入?yún)?duì)象的類型定義指定連接點(diǎn)。如args(com.baobaotao.Waiter)表示所有有且僅有一個(gè)按類型匹配于Waiter的入?yún)⒌姆椒ā?
@args() 類型注 解類名 通過(guò)判別目標(biāo)方法的運(yùn)行時(shí)入?yún)?duì)象的類是否標(biāo)注特定注解來(lái)指定連接點(diǎn)。如@args(com.baobaotao.Monitorable)表示任何這樣的一個(gè)目標(biāo)方法:它有一個(gè)入?yún)⑶胰雲(yún)?duì)象的類標(biāo)注@Monitorable注解。
目標(biāo)類切點(diǎn)函數(shù) within() 類名匹配串 表示特定域下的所有連接點(diǎn)。如within(com.baobaotao.service.*)表示com.baobaotao.service包中的所有連接點(diǎn),也即包中所有類的所有方法,而within(com.baobaotao.service.*Service)表示在com.baobaotao.service包中,所有以Service結(jié)尾的類的所有連接點(diǎn)。
target() 類名 假如目標(biāo)類按類型匹配于指定類,則目標(biāo)類的所有連接點(diǎn)匹配這個(gè)切點(diǎn)。如通過(guò)target(com.baobaotao.Waiter)定義的切點(diǎn),Waiter、以及Waiter實(shí)現(xiàn)類NaiveWaiter中所有連接點(diǎn)都匹配該切點(diǎn)。
@within() 類型注解類名 假如目標(biāo)類按類型匹配于某個(gè)類A,且類A標(biāo)注了特定注解,則目標(biāo)類的所有連接點(diǎn)匹配這個(gè)切點(diǎn)。 如@within(com.baobaotao.Monitorable)定義的切點(diǎn),假如Waiter類標(biāo)注了@Monitorable注解,則Waiter以及Waiter實(shí)現(xiàn)類NaiveWaiter類的所有連接點(diǎn)都匹配。
@target() 類型注解類名 目標(biāo)類標(biāo)注了特定注解,則目標(biāo)類所有連接點(diǎn)匹配該切點(diǎn)。如@target(com.baobaotao.Monitorable),假如NaiveWaiter標(biāo)注了@Monitorable,則NaiveWaiter所有連接點(diǎn)匹配切點(diǎn)。
代理類切點(diǎn)函數(shù) this() 類名 代理類按類型匹配于指定類,則被代理的目標(biāo)類所有連接點(diǎn)匹配切點(diǎn)。這個(gè)函數(shù)比較難理解,這里暫不舉例,留待后面詳解。

?

@AspectJ除上表中所列的函數(shù)外,還有call()、initialization()、 preinitialization()、 staticinitialization()、 get()、 set()、handler()、 adviceexecution()、 withincode()、 cflow()、 cflowbelow()、 if()、 @this()以及@withincode()等函數(shù),這些函數(shù)在Spring中不能使用,否則會(huì)拋出IllegalArgumentException異常。在不特別聲明的情況下,本書(shū)中所講@AspectJ函數(shù)均指表 1中所列的函數(shù)。?

的控制流。After注解類擁有2個(gè)成員:?
? value:該成員用于定義切點(diǎn);?
? argNames:如前所述。

@DeclareParents?
引介增強(qiáng),相當(dāng)于IntroductionInterceptor,DeclareParents注解類擁有2個(gè)成員:?
? value:該成員用于定義切點(diǎn),它表示在哪個(gè)目標(biāo)類上添加引介增強(qiáng);?
? defaultImpl:默認(rèn)的接口實(shí)現(xiàn)類。?

除引介增強(qiáng)外,其它增強(qiáng)都很容易理解,我們將在本文后續(xù)內(nèi)容中統(tǒng)一講述,但引介增強(qiáng)的使用比較特別,因?yàn)槲覀兲貏e在下節(jié)中為其準(zhǔn)備了一個(gè)實(shí)例。?

引介增強(qiáng)用法?
請(qǐng)看以下兩個(gè)接口及其實(shí)現(xiàn)類,如圖 2所示:?

圖 2 Waiter和Seller?

假設(shè)我們希望NaiveWaiter能夠同時(shí)充當(dāng)售貨員的角色,即通過(guò)切面技術(shù)為NaiveWaiter新增Seller接口的實(shí)現(xiàn)。我們可以使用@AspectJ的引介增強(qiáng)來(lái)實(shí)現(xiàn)這一功能。?
代碼清單 3 EnableSellerAspect?

package com.baobaotao.aspectj.basic;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import com.baobaotao.Seller;
import com.baobaotao.SmartSeller;

@Aspect
public class EnableSellerAspect {
①為NaiveWaiter添加接口實(shí)現(xiàn)
@DeclareParents(value="com.baobaotao.NaiveWaiter",
defaultImpl=SmartSeller.class) ② 默認(rèn)的接口實(shí)現(xiàn)類
public Seller seller; ③要實(shí)現(xiàn)的目標(biāo)接口
}

??? 在EnableSellerAspect切面中,我們通過(guò)@DeclareParents為NaiveWaiter添加了一個(gè)需要實(shí)現(xiàn)的Seller接口,并指定其默認(rèn)實(shí)現(xiàn)類為SmartSeller,然后通過(guò)切面技術(shù)將SmartSeller融合到NaiveWaiter中,這樣NaiveWaiter就實(shí)現(xiàn)Seller接口了。?
在Spring配置文件中配置好切面和NaiveWaiter Bean:?

<aop:aspectj-autoproxy/> <bean id="waiter"class="com.baobaotao.NaiveWaiter"/> <bean class="com.baobaotao.aspectj.basic.EnableSellerAspect"/>

運(yùn)行以下測(cè)試代碼:?

package com.baobaotao.aspectj.basic;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.baobaotao.Seller;
import com.baobaotao.Waiter;
public class DeclaredParentsTest ...{
public static void main(String[] args) ...{
String configPath = "com/baobaotao/aspectj/basic/beans.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
Waiter waiter = (Waiter)ctx.getBean("waiter");
waiter.greetTo("John");
Seller seller = (Seller)waiter; ①可以成功進(jìn)行強(qiáng)制類型轉(zhuǎn)換
seller.sell("Beer", "John");
}
}


代碼成功執(zhí)行,并輸出以下信息:?
NaiveWaiter:greet to John...?
SmartSeller: sell Beer to John...?
可見(jiàn),NaiveWaiter已經(jīng)成功地新增了Seller接口的實(shí)現(xiàn)。

切點(diǎn)函數(shù)詳解?

切點(diǎn)函數(shù)是AspectJ表達(dá)式語(yǔ)言的核心,是使用@AspectJ進(jìn)行切面定義的難點(diǎn),本節(jié)我們通過(guò)具體實(shí)例對(duì)切點(diǎn)函數(shù)進(jìn)行深入學(xué)習(xí)。為了方便講解,我們假設(shè)目標(biāo)類包括以下7個(gè)類,這些目標(biāo)類都位于com.baobaotao.*包中:?

圖 3 Waiter和Seller類圖?
這些類中,除了SmartSeller#showGoods()方法是protected外,其它的方法都是public。?

@annotation()?

@annotation表示標(biāo)注了某個(gè)注解的所有方法。我們通過(guò)一個(gè)實(shí)例說(shuō)明@annotation()的用法,TestAspect定義了一個(gè)后置增強(qiáng)切面,該增強(qiáng)將應(yīng)用到標(biāo)注有NeedTest的目標(biāo)方法中:?

package com.baobaotao.aspectj.fun; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect publicclass TestAspect { @AfterReturning("@annotation(com.baobaotao.anno.NeedTest)") ①后置增強(qiáng)切面 publicvoid needTestFun(){ System.out.println("needTestFun() executed!"); } }

假設(shè)NaughtyWaiter#greetTo()方法標(biāo)注了@NeedTest注解,而NaiveWaiter#greetTo()方法沒(méi)有標(biāo)注@NeedTest注解,如代碼清單 4所示:?
代碼清單 4 標(biāo)注了NeedTest注解的NaughtyWaiter?

package com.baobaotao; import com.baobaotao.anno.NeedTest; publicclass NaughtyWaiter implements Waiter { @NeedTest publicvoid greetTo(String clientName) { System.out.println("NaughtyWaiter:greet to "+clientName+"..."); } … }

通過(guò)Spring配置自動(dòng)應(yīng)用切面:

<aop:aspectj-autoproxy />?
<bean id="naiveWaiter" class="com.baobaotao.NaiveWaiter" />?
<bean id="naughtyWaiter" class="com.baobaotao.NaughtyWaiter" />?
<bean class="com.baobaotao.aspectj.fun.TestAspect" />?
運(yùn)行以下的代碼:?
代碼清單 5 PointcutFunTest:測(cè)試代碼?

package com.baobaotao.aspectj.fun; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.baobaotao.Seller; import com.baobaotao.Waiter; publicclass PointcutFunTest{ publicstaticvoid main(String[] args) { String configPath ="com/baobaotao/aspectj/fun/beans.xml"; ApplicationContext ctx =new ClassPathXmlApplicationContext(configPath); Waiter naiveWaiter = (Waiter) ctx.getBean("naiveWaiter"); Waiter naughtyWaiter = (Waiter) ctx.getBean("naughtyWaiter"); naiveWaiter.greetTo("John"); ①該方法未被織入增強(qiáng) naughtyWaiter.greetTo("Tom");②該方法被織入增強(qiáng) } }

輸出以下信息:?
NaiveWaiter:greet to John...?
NaughtyWaiter:greet to Tom... ①對(duì)應(yīng)NaughtyWaiter的greetTo()?
needTestFun() executed!?
從以上的信息中,我們可以獲知切面被正確地織入到NaughtyWaiter#greetTo()方法中。?

execution()?
execution()是最常用的切點(diǎn)函數(shù),其語(yǔ)法如下所示:

execution(<修飾符模式>?<返回類型模式><方法名模式>(<參數(shù)模式>) <異常模式>?)

除了返回類型模式、方法名模式和參數(shù)模式外,其它項(xiàng)都是可選的。與其直接講解該方法的使用規(guī)則,還不如通過(guò)一個(gè)個(gè)具體的例子進(jìn)行理解。下面,我們給出各種使用execution()函數(shù)實(shí)例。?

1)通過(guò)方法簽名定義切點(diǎn)?
? execution(public * *(..))?
匹配所有目標(biāo)類的public方法,但不匹配SmartSeller和protected void showGoods()方法。第一個(gè)*代表返回類型,第二個(gè)*代表方法名,而..代表任意入?yún)⒌姆椒?#xff1b;?

? execution(* *To(..))?
匹配目標(biāo)類所有以To為后綴的方法。它匹配N(xiāo)aiveWaiter和NaughtyWaiter的greetTo()和serveTo()方法。第一個(gè)*代表返回類型,而*To代表任意以To為后綴的方法;?

2)通過(guò)類定義切點(diǎn)?
? execution(* com.baobaotao.Waiter.*(..))?
匹配Waiter接口的所有方法,它匹配N(xiāo)aiveWaiter和NaughtyWaiter類的greetTo()和serveTo()方法。第一個(gè)*代表返回任意類型,com.baobaotao.Waiter.*代表Waiter接口中的所有方法;?

? execution(* com.baobaotao.Waiter+.*(..))?
匹配Waiter接口及其所有實(shí)現(xiàn)類的方法,它不但匹配N(xiāo)aiveWaiter和NaughtyWaiter類的greetTo()和serveTo()這兩個(gè)Waiter接口定義的方法,同時(shí)還匹配N(xiāo)aiveWaiter#smile()和NaughtyWaiter#joke()這兩個(gè)不在Waiter接口中定義的方法。

3)通過(guò)類包定義切點(diǎn)?
在類名模式串中,“.*”表示包下的所有類,而“..*”表示包、子孫包下的所有類。?
? execution(* com.baobaotao.*(..))?
匹配com.baobaotao包下所有類的所有方法;?

? execution(* com.baobaotao..*(..))?
匹配com.baobaotao包、子孫包下所有類的所有方法,如com.baobaotao.dao,com.baobaotao.servier以及com.baobaotao.dao.user包下的所有類的所有方法都匹配。“..”出現(xiàn)在類名中時(shí),后面必須跟“*”,表示包、子孫包下的所有類;?

? execution(* com..*.*Dao.find*(..))?
匹配包名前綴為com的任何包下類名后綴為Dao的方法,方法名必須以find為前綴。如com.baobaotao.UserDao#findByUserId()、com.baobaotao.dao.ForumDao#findById()的方法都匹配切點(diǎn)。?

4)通過(guò)方法入?yún)⒍x切點(diǎn)?
切點(diǎn)表達(dá)式中方法入?yún)⒉糠直容^復(fù)雜,可以使用“*”和“ ..”通配符,其中“*”表示任意類型的參數(shù),而“..”表示任意類型參數(shù)且參數(shù)個(gè)數(shù)不限。?

? execution(* joke(String,int)))?
匹配joke(String,int)方法,且joke()方法的第一個(gè)入?yún)⑹荢tring,第二個(gè)入?yún)⑹莍nt。它匹配N(xiāo)aughtyWaiter#joke(String,int)方法。如果方法中的入?yún)㈩愋褪莏ava.lang包下的類,可以直接使用類名,否則必須使用全限定類名,如joke(java.util.List,int);?

? execution(* joke(String,*)))?
匹配目標(biāo)類中的joke()方法,該方法第一個(gè)入?yún)镾tring,第二個(gè)入?yún)⒖梢允侨我忸愋?#xff0c;如joke(String s1,String s2)和joke(String s1,double d2)都匹配,但joke(String s1,double d2,String s3)則不匹配;

? execution(* joke(String,..)))?
匹配目標(biāo)類中的joke()方法,該方法第一個(gè)入?yún)镾tring,后面可以有任意個(gè)入?yún)⑶胰雲(yún)㈩愋筒幌?#xff0c;如joke(String s1)、joke(String s1,String s2)和joke(String s1,double d2,String s3)都匹配。?

? execution(* joke(Object+)))?
匹配目標(biāo)類中的joke()方法,方法擁有一個(gè)入?yún)?#xff0c;且入?yún)⑹荗bject類型或該類的子類。 它匹配joke(String s1)和joke(Client c)。如果我們定義的切點(diǎn)是execution(* joke(Object)),則只匹配joke(Object object)而不匹配joke(String cc)或joke(Client c)。

args()和@args()?
args()函數(shù)的入?yún)⑹穷惷?#xff0c;@args()函數(shù)的入?yún)⒈仨毷亲⒔忸惖念惷km然args()允許在類名后使用+通配符后綴,但該通配符在此處沒(méi)有意義:添加和不添加效果都一樣。?

1)args()?
該函數(shù)接受一個(gè)類名,表示目標(biāo)類方法入?yún)?duì)象按類型匹配于指定類時(shí),切點(diǎn)匹配,如下面的例子:
args(com.baobaotao.Waiter)?
表示運(yùn)行時(shí)入?yún)⑹荳aiter類型的方法,它和execution(* *(com.baobaotao.Waiter))區(qū)別在于后者是針對(duì)類方法的簽名而言的,而前者則針對(duì)運(yùn)行時(shí)的入?yún)㈩愋投浴H鏰rgs(com.baobaotao.Waiter)既匹配于addWaiter(Waiter waiter),也匹配于addNaiveWaiter(NaiveWaiter naiveWaiter),而execution(* *(com.baobaotao.Waiter))只匹配addWaiter(Waiter waiter)方法;實(shí)際上,args(com.baobaotao.Waiter)等價(jià)于execution(* *(com.baobaotao.Waiter+)),當(dāng)然也等價(jià)于args(com.baobaotao.Waiter+)。

2)@args()?
該函數(shù)接受一個(gè)注解類的類名,當(dāng)方法的運(yùn)行時(shí)入?yún)?duì)象標(biāo)注發(fā)指定的注解時(shí),方法匹配切點(diǎn)。這個(gè)切點(diǎn)函數(shù)的匹配規(guī)則不太容易理解,我們通過(guò)以下示意圖對(duì)此進(jìn)行詳細(xì)講解:?

??????????? 圖 4 @arg(M)匹配示意圖(1)?
??? T0、T1、T2、T3具有如圖所示的繼承關(guān)系,假設(shè)目標(biāo)類方法的簽名為fun(T1 t),它的入?yún)門(mén)1,而切面的切點(diǎn)定義為@args(M),T2類標(biāo)注了@M。當(dāng)fun(T1 t)傳入對(duì)象是T2或T3時(shí),則方法匹配@args(M)所聲明定義的切點(diǎn);?

再看下面的情況,假設(shè)方法簽名是fun(T1 t),入?yún)?duì)于T1,而標(biāo)注@M的類是T0,當(dāng)funt(T1 t)傳入T1、T2、T3的實(shí)例時(shí),均不匹配切點(diǎn)@args(M)。?

??????????? 圖 5 @arg(M)匹配示意圖(2)?
??? 在類的繼承樹(shù)中,①點(diǎn)為方法簽名中入?yún)㈩愋驮陬惱^承樹(shù)中的位置,我們稱之為入?yún)㈩愋忘c(diǎn),而②為標(biāo)注了@M注解的類在類繼承樹(shù)中位置,我們稱之為注解點(diǎn)。判斷方法在運(yùn)行時(shí)是否匹配@agrs(M)切點(diǎn),可以根據(jù)①點(diǎn)和②點(diǎn)在類繼承樹(shù)中的相對(duì)位置來(lái)判別:?
1) 如果在類繼承樹(shù)中注解點(diǎn)②高于入?yún)㈩愋忘c(diǎn)①,則該目標(biāo)方法不可能匹配切點(diǎn)@args(M),如圖 5所示;?
2) 如果在類繼承樹(shù)中注解點(diǎn)②低于入?yún)㈩愋忘c(diǎn)①,則注解點(diǎn)所在類及其子孫類作為方法入?yún)r(shí),該方法匹配@args(M)切點(diǎn),如圖 4所示。?
下面舉一個(gè)具體的例子,假設(shè)我們定義這樣的切點(diǎn):@args(com.baobaotao.Monitorable) ,如果NaiveWaiter標(biāo)注了@Monitorable,則對(duì)于WaiterManager#addWaiter(Waiter w)方法來(lái)說(shuō),如果入?yún)⑹荖aiveWaiter或其子類對(duì)象,該方法匹配切點(diǎn),如果入?yún)⑹荖aughtyWaiter對(duì)象,不匹配切點(diǎn)。如果Waiter標(biāo)注了@Monitorable,但NaiveWaiter未標(biāo)注@Monitorable,則WaiterManager#addNaiveWaiter(NaiveWaiter w)卻不匹配切點(diǎn),這是因?yàn)樽⒔恻c(diǎn)(在Waiter)高于入?yún)㈩愋忘c(diǎn)(NaiveWaiter)。

within()?

??? 通過(guò)類匹配模式串聲明切點(diǎn),within()函數(shù)定義的連接點(diǎn)是針對(duì)目標(biāo)類而言,而非針對(duì)運(yùn)行期對(duì)象的類型而言,這一點(diǎn)和execetion()是相同的。但和execution()函數(shù)不同的是,within()所指定的連接點(diǎn)最小范圍只能是類,而execution()所指定的連接點(diǎn),可以大到包,小到方法入?yún)ⅰK詮哪撤N意義上說(shuō),execution()函數(shù)的功能涵蓋了within()函數(shù)的功能。within()函數(shù)的語(yǔ)法如下所示:?

within(<類匹配模式>)

形如within(com.baobaotao.NaiveWaiter)是within()函數(shù)所能表達(dá)的最小粒度,如果試圖用within()匹配方法級(jí)別的連接點(diǎn),如within(com.baobaotao.NaiveWaiter.greet*)將會(huì)產(chǎn)生解析錯(cuò)誤。?

下面是一些使用within()函數(shù)的實(shí)例:?
? within(com.baobaotao.NaiveWaiter)?
匹配目標(biāo)類NaiveWaiter的所有方法。如果切點(diǎn)調(diào)整為within(com.baobaotao.Waiter),則NaiveWaiter和NaughtyWaiter中的所有方法都不匹配,而Waiter本身是接口不可能實(shí)例化,所以within(com.baobaotao.Waiter)的聲明是無(wú)意義的;?

? within(com.baobaotao.*)?
匹配com.baobaotao包中的所有類,但不包括子孫包,所以com.baobaotao.service包中類的方法不匹配這個(gè)切點(diǎn);?

? within(com.baobaotao..*)?
匹配com.baobaotao包及子孫包中的類,所以com.baobaotao.service、com.baobaotao.dao以及com.baobaotao.service.fourm等包中所有類的方法都匹配這個(gè)切點(diǎn)。

@within()和@target()?

除@annotation()和@args()外,還有另外兩個(gè)用于注解的切點(diǎn)函數(shù),它們分別是@target()和@within(),和@annotation()及@args()函數(shù)一樣,它們也只接受注解類名作為入?yún)ⅰF渲?#64;target(M)匹配任意標(biāo)注了@M的目標(biāo)類,而@within(M)匹配標(biāo)注了@M的類及子孫類。?
@target(M)切點(diǎn)的匹配規(guī)則如圖 6所示:?

圖 6 @target(M)匹配目標(biāo)類示意圖?
假設(shè)NaiveWaiter標(biāo)注了@Monitorable,則其子類CuteNaiveWaiter沒(méi)有標(biāo)注@Monitorable,則@target(com.baobaotao.Monitorable)匹配N(xiāo)aiveWaiter類的所有方法,但不匹配CuteNaiveWaiter類的方法。?
@within(M)切點(diǎn)的匹配規(guī)則如圖 7所示:?

圖 7 @within(M)匹配目標(biāo)類示意圖?

假設(shè)NaiveWaiter標(biāo)注了@Monitorable,而其子類CuteNaiveWaiter沒(méi)有標(biāo)注@Monitorable,則@within(com.baobaotao.Monitorable)不但匹配N(xiāo)aiveWaiter類中的所有方法也匹配CuteNaiveWaiter類中的所有方法。

但有一個(gè)特別值得注意地方是,如果標(biāo)注@M注解的是一個(gè)接口,則所有實(shí)現(xiàn)該接口的類并不匹配@within(M)。假設(shè)Waiter標(biāo)注了@Monitorable注解,但NaiveWaiter、NaughtyWaiter及CuteNaiveWaiter這些接口實(shí)現(xiàn)類都沒(méi)有標(biāo)注@Monitorable,則@within(com.baobaotao.Monitorable)和@target(com.baobaotao.Monitorable)都不匹配N(xiāo)aiveWaiter、NaughtyWaiter及CuteNaiveWaiter。這是因?yàn)?#64;within()、@target()以及@annotation()都是針對(duì)目標(biāo)類而言,而非針對(duì)運(yùn)行時(shí)的引用類型而言,這點(diǎn)區(qū)別需要在開(kāi)發(fā)中特別注意。

target()的this()?

??? target()切點(diǎn)函數(shù)通過(guò)判斷目標(biāo)類是否按類型匹配指定類決定連接點(diǎn)是否匹配,而this()則通過(guò)判斷代理類是否按類型匹配指定類來(lái)決定是否和切點(diǎn)匹配。兩者都僅接受類名的入?yún)?#xff0c;雖然類名可以帶“+”通配符,但對(duì)于這兩個(gè)函數(shù)來(lái)說(shuō),使用與不使用+通配符,效果完全相同。

1)target()?
target(M)表示如果目標(biāo)類按類型匹配于M,則目標(biāo)類所有方法匹配切點(diǎn),我們通過(guò)一些例子理解target(M)的匹配規(guī)則:?
? target(com.baobaotao.Waiter)?
NaiveWaiter、NaughtyWaiter以及CuteNaiveWaiter的所有方法都匹配切點(diǎn),包括那些未在Waiter接口中定義的方法,如NaiveWaiter#simle()和NaughtyWaiter#joke()方法。?
? target(com.baobaotao.Waiter+)?
和target(com.baobaotao.Waiter)是等價(jià)的。?

2)this()?
根據(jù)Spring的官方文檔,this()函數(shù)判斷代理對(duì)象的類是否按類型匹配于指定類,如果匹配,則代理對(duì)象的所有連接點(diǎn)匹配切點(diǎn)。但通過(guò)實(shí)驗(yàn),我們發(fā)現(xiàn)實(shí)際情況和文檔有出入,如我們聲明一個(gè)this(com.baobaotao.NaiveWaiter)的切點(diǎn),如果不使用CGLib代理,則生成的代理對(duì)象是Waiter類型,而非NaiveWaiter類型,這一點(diǎn)可以簡(jiǎn)單地通過(guò)instanceof操作符進(jìn)行判斷。但是,我們發(fā)現(xiàn)NaiveWaiter中所有的方法還是被織入了增強(qiáng)。?

在一般情況下,使用this()和target()通過(guò)定義切點(diǎn),兩者是等效的:?
1) target(com.baobaotao.Waiter) 等價(jià)于this(com.baobaotao.Waiter)?
2) target(com.baobaotao.NaiveWaiter) 等價(jià)于 this(com.baobaotao.NaiveWaiter)?
兩者區(qū)別體現(xiàn)在通過(guò)引介切面產(chǎn)生的代理對(duì)象時(shí)的具體表現(xiàn),如果我們通過(guò)本文前面的方法為NaiveWaiter引介一個(gè)Seller接口的實(shí)現(xiàn),則this(com.baobaotao.Seller)匹配N(xiāo)aiveWaiter代理對(duì)象的所有方法,包括NaiverWaiter本身的greetTo()、serverTo()方法以及通過(guò)Seller接口引入的sell()方法。而target(com.baobaotao.Seller)不匹配通過(guò)引介切面產(chǎn)生的NaiveWaiter代理對(duì)象。

下面通過(guò)具體的實(shí)例來(lái)了解這一微妙的區(qū)別,EnableSellerAspect是為NaiveWaiter添加Seller接口實(shí)現(xiàn)的引介切面:

package com.baobaotao.aspectj.fun; … @Aspect publicclass EnableSellerAspect{ @DeclareParents(value ="com.baobaotao.NaiveWaiter", defaultImpl = SmartSeller.class) publicstatic Seller seller; }

TestAspect是通過(guò)判斷運(yùn)行期代理對(duì)象所屬類型來(lái)定義切點(diǎn)的切面:?
代碼清單 6 TestAspect:通過(guò)this()指定切點(diǎn)?

package com.baobaotao.aspectj.fun; … @Aspect publicclass TestAspect { @AfterReturning("this(com.baobaotao.Seller)") ①后置增強(qiáng),織入到任何運(yùn)行期對(duì)象為 Seller 類型的Bean中 publicvoid thisTest(){ System.out.println("thisTest() executed!"); } }

在Spring中配置這兩個(gè)切面和NaiveWaiter:?
<aop:aspectj-autoproxy/>?
<bean id="naiveWaiter" class="com.baobaotao.NaiveWaiter" />?
<bean class="com.baobaotao.aspectj.fun.EnableSellerAspect"/>?
<bean class="com.baobaotao.aspectj.fun.TestAspect" />?

首先EnableSellerAspect切面為NaiveWaiter引介Seller接口產(chǎn)生一個(gè)實(shí)現(xiàn)Seller接口的代理對(duì)象,TestAspect在判斷出NaiveWaiter這個(gè)代理對(duì)象實(shí)現(xiàn)Seller接口后,就將其切面織入到這個(gè)代理對(duì)象中,所以最終NaiveWaiter的代理對(duì)象其實(shí)共織入了兩個(gè)切面。?

運(yùn)行以下的測(cè)試代碼:

String configPath ="com/baobaotao/aspectj/fun/beans.xml"; ApplicationContext ctx =new ClassPathXmlApplicationContext(configPath); Waiter naiveWaiter = (Waiter) ctx.getBean("naiveWaiter"); naiveWaiter.greetTo("John"); naiveWaiter.serveTo("John"); ((Seller)naiveWaiter).sell("Beer", "John");

輸出以下的代碼:?

NaiveWaiter:greet to John... thisTest() executed! NaiveWaiter:serving John... thisTest() executed! SmartSeller: sell Beer to John... thisTest() executed!

可見(jiàn)代理對(duì)象的3個(gè)方法都織入了代碼清單 6通過(guò)this()所定義切面。?

小結(jié)?
??? 使用@AspectJ定義切面比基于接口定義的切面更加直觀、更加簡(jiǎn)潔,成為Spring所推薦的切面定義方式。掌握切點(diǎn)表達(dá)式語(yǔ)法和切點(diǎn)函數(shù)是學(xué)習(xí)@AspectJ的重心,我們分別對(duì)9個(gè)切點(diǎn)函數(shù)進(jìn)行了詳細(xì)的講述。切點(diǎn)表達(dá)式非常靈活,擁有強(qiáng)大的切點(diǎn)表達(dá)能力,你可以使用通配符、切點(diǎn)函數(shù)以及切點(diǎn)運(yùn)算符定義切點(diǎn)。

Spring 2.0在AOP上花費(fèi)了很大的功夫,相比于低版本的Spring,我們看到了很大的改進(jìn)。在掌握低版本Spring AOP相關(guān)知識(shí)的基礎(chǔ)上,你會(huì)發(fā)現(xiàn)學(xué)習(xí)Spring 2.0 基于@AspectJ AOP的新知識(shí)不會(huì)有太多的門(mén)檻。?

?

一個(gè)簡(jiǎn)單的例子?

??? 在做好上節(jié)中所提到的前置工作后,我們就可以開(kāi)始編寫(xiě)一個(gè)基于@AspectJ的切面了,首先來(lái)看一個(gè)簡(jiǎn)單的例子,以便對(duì)@AspectJ有一個(gè)切身的認(rèn)識(shí)。

下面,我們用@AspectJ注解對(duì)一個(gè)POJO進(jìn)行標(biāo)注,將使其成為一個(gè)切面類:?
代碼清單 1 PreGreetingAspect:切面

package com.baobaotao.aspectj.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect ①通過(guò)該注解將PreGreetingAspect標(biāo)識(shí)為一個(gè)切面
public class PreGreetingAspect{
@Before("execution(* greetTo(..))") ②定義切點(diǎn)和增強(qiáng)類型
public void beforeGreeting(){③增強(qiáng)的橫切邏輯
System.out.println("How are you");
}
}

???? 我們“驚奇”地發(fā)現(xiàn)這個(gè)切面沒(méi)有實(shí)現(xiàn)任何特殊的接口,它只是一個(gè)普通的POJO。它特殊的地方在于使用了@AspectJ注解。?
首先,在PreGreetingAspect類定義處,標(biāo)注了一個(gè)@Aspectj注解,第三方處理程序就可以通過(guò)類是否擁有@Aspectj注解判斷其是否是一個(gè)切面,如①所示。

其次,在beforeGreeting()方法標(biāo)簽處,標(biāo)注了@Before注解,并為該注解提供了成員值"execution(* greetTo(..))",如②所示。②處的注解提供了兩個(gè)信息:@Before注解表示該增強(qiáng)是前置增強(qiáng),而成員值通過(guò)@ApsectJ切點(diǎn)表達(dá)式語(yǔ)法定義切點(diǎn):即在目標(biāo)類的greetTo()方法上織入增強(qiáng),greetTo()方法可以帶任意的入?yún)⒑腿我獾姆祷刂怠?/span>

最后,在③處的beforeGreeting()方法是增強(qiáng)的橫切邏輯,該橫切邏輯在目標(biāo)方法前調(diào)用,我們通過(guò)下圖描述這種關(guān)系:?
???

?圖 1 切面的信息構(gòu)成?

??? PreGreetingAspect類通過(guò)注解和代碼,將切點(diǎn)、增強(qiáng)類型和增強(qiáng)的橫切邏輯揉合到一個(gè)類中,使切面的定義渾然天成。如果在低版本Spring AOP中,你必須同時(shí)創(chuàng)建增強(qiáng)類,切點(diǎn)類以及切面類,并使三者聯(lián)合表達(dá)相同的信息。?

NaiveWaiter是一個(gè)Bean,它擁有一個(gè)greetTo()的方法,這個(gè)方法連接點(diǎn)匹配于上面我們通過(guò)@AspectJ所定義的切點(diǎn),為了方便后續(xù)的說(shuō)明,我們給出NaiveWaiter的代碼:

package com.baobaotao; public class NaiveWaiter implements Waiter { public void greetTo(String clientName) { System.out.println("NaiveWaiter:greet to "+clientName+"..."); } public void serveTo(String clientName){ System.out.println("NaiveWaiter:serving "+clientName+"..."); } }


下面,我們通過(guò)org.springframework.aop.aspectj.annotation.AspectJProxyFactory為NaiveWaiter生成織入PreGreetingAspect切面的代理,如代碼清單 2所示:?
代碼清單 2 AspectJProxyTest?

package com.baobaotao.aspectj.example; import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; import com.baobaotao.NaiveWaiter; import com.baobaotao.Waiter; publicclass AspectJProxyTest { Waiter target =new NaiveWaiter(); AspectJProxyFactory factory =new AspectJProxyFactory(); factory.setTarget(target); ① 設(shè)置目標(biāo)對(duì)象 factory.addAspect(PreGreetingAspect.class); ②添加切面類 Waiter proxy = factory.getProxy(); ③ 生成織入切面的代理對(duì)象 proxy.greetTo("John"); proxy.serveTo("John"); } }

Spring使用AspectJProxyFactory織入基于@AspectJ切面的工作。在①處,設(shè)置了目標(biāo)對(duì)象,在②處添加一個(gè)切面類,該類必須是帶@AspectJ注解的類,在③處,我們就可以獲取織入切面的代理對(duì)象了。?
接下來(lái),我們直接通過(guò)代理對(duì)象調(diào)用greetTo()和serveTo()代碼,它們產(chǎn)生以下的輸出信息:?
How are you ①表示greetTo()方法被成功地織入了切面?
greet to John...?
serving John...?
通過(guò)①處的輸出信息我們可以知道代理對(duì)象的greetTo()方法已經(jīng)織入了切面類所定義的增強(qiáng)邏輯了。

轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/5533671.html

總結(jié)

以上是生活随笔為你收集整理的基于@AspectJ配置Spring AOP之一--转的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。