一、AOP是什么穗酥?AOP與攔截器的區(qū)別护赊?
太抽象的不說(shuō),如果你知道Struts2的攔截器迷扇,攔截器就是應(yīng)用的AOP的思想百揭,它用于攔截Action以進(jìn)行一些預(yù)處理或結(jié)果處理爽哎。而Spring的AOP是一種更通用的模式蜓席,可以攔截Spring管理的Bean,功能更強(qiáng)大课锌,適用范圍也更廣厨内,它是通過(guò)動(dòng)態(tài)代理與反射機(jī)制實(shí)現(xiàn)的。
二渺贤、AOP一些概念
1.通知(Advice)
通知定義了在切入點(diǎn)代碼執(zhí)行時(shí)間點(diǎn)附近需要做的工作雏胃。
Spring支持五種類型的通知:
Before(前) ?org.apringframework.aop.MethodBeforeAdvice
After-returning(返回后) org.springframework.aop.AfterReturningAdvice
After-throwing(拋出后) org.springframework.aop.ThrowsAdvice
Arround(周圍) org.aopaliance.intercept.MethodInterceptor
Introduction(引入) org.springframework.aop.IntroductionInterceptor
2.連接點(diǎn)(Joinpoint)
程序能夠應(yīng)用通知的一個(gè)“時(shí)機(jī)”,這些“時(shí)機(jī)”就是連接點(diǎn)志鞍,例如方法調(diào)用時(shí)瞭亮、異常拋出時(shí)、方法返回后等等固棚。
3.切入點(diǎn)(Pointcut)
通知定義了切面要發(fā)生的“故事”统翩,連接點(diǎn)定義了“故事”發(fā)生的時(shí)機(jī)仙蚜,那么切入點(diǎn)就定義了“故事”發(fā)生的地點(diǎn),例如某個(gè)類或方法的名稱厂汗,Spring中允許我們方便的用正則表達(dá)式來(lái)指定。
4.切面(Aspect)
通知、連接點(diǎn)蝶押、切入點(diǎn)共同組成了切面:時(shí)間狞悲、地點(diǎn)和要發(fā)生的“故事”。
5.引入(Introduction)
引入允許我們向現(xiàn)有的類添加新的方法和屬性(Spring提供了一個(gè)方法注入的功能)衷畦。
6.目標(biāo)(Target)
即被通知的對(duì)象栗涂,如果沒(méi)有AOP,那么通知的邏輯就要寫在目標(biāo)對(duì)象中霎匈,有了AOP之后它可以只關(guān)注自己要做的事戴差,解耦合!
7.代理(proxy)
應(yīng)用通知的對(duì)象铛嘱,詳細(xì)內(nèi)容參見(jiàn)設(shè)計(jì)模式里面的動(dòng)態(tài)代理模式暖释。
8.織入(Weaving)
把切面應(yīng)用到目標(biāo)對(duì)象來(lái)創(chuàng)建新的代理對(duì)象的過(guò)程,織入一般發(fā)生在如下幾個(gè)時(shí)機(jī):
(1)編譯時(shí):當(dāng)一個(gè)類文件被編譯時(shí)進(jìn)行織入墨吓,這需要特殊的編譯器才可以做的到球匕,例如AspectJ的織入編譯器;
(2)類加載時(shí):使用特殊的ClassLoader在目標(biāo)類被加載到程序之前增強(qiáng)類的字節(jié)代碼帖烘;
(3)運(yùn)行時(shí):切面在運(yùn)行的某個(gè)時(shí)刻被織入,SpringAOP就是以這種方式織入切面的亮曹,原理應(yīng)該是使用了JDK的動(dòng)態(tài)代理技術(shù)。
三秘症、使用AOP的方式
1.經(jīng)典的基于代理的AOP
2.@AspectJ注解驅(qū)動(dòng)的切面
3.純POJO切面
4.注入式AspectJ切面
四照卦、如何實(shí)現(xiàn)AOP
?1.代理方式實(shí)現(xiàn)AOP
(1)可睡覺(jué)的接口,任何可以睡覺(jué)的人或機(jī)器都可以實(shí)現(xiàn)它乡摹。
? ?public interface Sleepable {
? ? public void sleep();
? ? }
(2)接口實(shí)現(xiàn)類役耕,“Me”可以睡覺(jué),“Me”就實(shí)現(xiàn)可以睡覺(jué)的接口聪廉。
? ?public class Me implements Sleepable{
? ? ? ? ?public void sleep() {
? ? ? ? ?System.out.println("\n睡覺(jué)瞬痘!不休息哪里有力氣學(xué)習(xí)!\n");
}
}
(3)Me關(guān)注于睡覺(jué)的邏輯板熊,但是睡覺(jué)需要其他功能輔助框全,比如睡前脫衣服,起床脫衣服干签,這里開(kāi)始就需要AOP替“Me”完成津辩!解耦!首先需要一個(gè)SleepHelper類。因?yàn)橐粋€(gè)是切入點(diǎn)前執(zhí)行喘沿、一個(gè)是切入點(diǎn)之后執(zhí)行情萤,所以實(shí)現(xiàn)對(duì)應(yīng)接口。
public class SleepHelper implements MethodBeforeAdvice, AfterReturningAdvice {
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("睡覺(jué)前要脫衣服摹恨!");
}
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("起床后要穿衣服筋岛!");
}
}
(4)最關(guān)鍵的來(lái)了,Spring核心配置文件application.xml配置AOP晒哄。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
<span style="white-space:pre"> </span>xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<span style="white-space:pre"> </span>xmlns:aop="http://www.springframework.org/schema/aop"
<span style="white-space:pre"> </span>xsi:schemaLocation="http://www.springframework.org/schema/beans
<span style="white-space:pre"> </span>http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
<span style="white-space:pre"> </span>http://www.springframework.org/schema/aop
<span style="white-space:pre"> </span>http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
? <!-- 定義被代理者 -->
? <bean id="me" class="com.springAOP.bean.Me"></bean>
? <!-- 定義通知內(nèi)容睁宰,也就是切入點(diǎn)執(zhí)行前后需要做的事情 -->
? <bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
? <!-- 定義切入點(diǎn)位置 -->
? <bean id="sleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*sleep"></property>
? </bean>
? <!-- 使切入點(diǎn)與通知相關(guān)聯(lián),完成切面配置 -->
? <bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="sleepHelper"></property>?
? <property name="pointcut" ref="sleepPointcut"></property>
? </bean>
? <!-- 設(shè)置代理 -->
? <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理的對(duì)象寝凌,有睡覺(jué)能力 -->
<property name="target" ref="me"></property>
<!-- 使用切面 -->
<property name="interceptorNames" value="sleepHelperAdvisor"></property>
<!-- 代理接口柒傻,睡覺(jué)接口 -->
<property name="proxyInterfaces" value="com.springAOP.bean.Sleepable"></property>
? </bean>
</beans>
其中:
<beans>是Spring的配置標(biāo)簽,beans里面幾個(gè)重要的屬性:
xmlns:
是默認(rèn)的xml文檔解析格式较木,即spring的beans红符。地址是http://www.springframework.org/schema/beans;通過(guò)設(shè)置這個(gè)屬性伐债,所有在beans里面聲明的屬性预侯,可以直接通過(guò)<>來(lái)使用,比如<bean>等等峰锁。一個(gè)XML文件萎馅,只能聲明一個(gè)默認(rèn)的語(yǔ)義解析的規(guī)范。例如上面的xml中就只有beans一個(gè)是默認(rèn)的虹蒋,其他的都需要通過(guò)特定的標(biāo)簽來(lái)使用糜芳,比如aop,它自己有很多的屬性魄衅,如果要使用峭竣,前面就必須加上aop:xxx才可以。類似的晃虫,如果默認(rèn)的xmlns配置的是aop相關(guān)的語(yǔ)義解析規(guī)范皆撩,那么在xml中就可以直接寫config這種標(biāo)簽了。
xmlns:xsi:
是xml需要遵守的規(guī)范傲茄,通過(guò)URL可以看到毅访,是w3的統(tǒng)一規(guī)范沮榜,后面通過(guò)xsi:schemaLocation來(lái)定位所有的解析文件盘榨。
xmlns:aop:
這個(gè)是重點(diǎn),是我們這里需要使用到的一些語(yǔ)義規(guī)范蟆融,與面向切面AOP相關(guān)草巡。
xmlns:tx:
Spring中與事務(wù)相關(guān)的配置內(nèi)容。
(5)測(cè)試類型酥,Test山憨,其中查乒,通過(guò)AOP代理的方式執(zhí)行Me的sleep()方法,會(huì)把執(zhí)行前郁竟、執(zhí)行后的操作執(zhí)行玛迄,實(shí)現(xiàn)了AOP的效果!
public class Test {
public static void main(String[] args){
@SuppressWarnings("resource")
//如果是web項(xiàng)目棚亩,則使用注釋的代碼加載配置文件蓖议,這里是一般的Java項(xiàng)目,所以使用下面的方式
? ? //ApplicationContext appCtx = new ClassPathXmlApplicationContext("application.xml");
ApplicationContext appCtx = new FileSystemXmlApplicationContext("application.xml");
Sleepable me = (Sleepable)appCtx.getBean("proxy");
me.sleep();
}
}
執(zhí)行結(jié)果:
(6)通過(guò)org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator簡(jiǎn)化配置讥蟆。
將配置文件中設(shè)置代理的代碼去掉勒虾,加上:
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
然后,在Test中瘸彤,直接獲取me對(duì)象修然,執(zhí)行sleep方法,就可以實(shí)現(xiàn)同樣的功能质况!
通過(guò)自動(dòng)匹配愕宋,切面會(huì)自動(dòng)匹配符合切入點(diǎn)的bean,會(huì)被自動(dòng)代理结榄,實(shí)現(xiàn)功能掏婶!
————————————————
2.更簡(jiǎn)單的方式,通過(guò)AspectJ提供的注解實(shí)現(xiàn)AOP
1)同樣的例子潭陪,修改后的SleepHelper:
@Aspect
public class SleepHelper{
? ? public SleepHelper(){
? ? }
? ? @Pointcut("execution(* *.sleep())")
? ? public void sleeppoint(){}
? ? @Before("sleeppoint()")
? ? public void beforeSleep(){
? ? ? ? System.out.println("睡覺(jué)前要脫衣服!");
? ? }
? ? @AfterReturning("sleeppoint()")
? ? public void afterSleep(){
? ? ? ? System.out.println("睡醒了要穿衣服雄妥!");
? ? }
}
(2)在方法中,可以加上JoinPoint參數(shù)以進(jìn)行相關(guān)操作依溯,如:
//當(dāng)拋出異常時(shí)被調(diào)用
? ? public void doThrowing(JoinPoint point, Throwable ex)
? ? {
? ? ? ? System.out.println("doThrowing::method "
? ? ? ? ? ? ? ? + point.getTarget().getClass().getName() + "."
? ? ? ? ? ? ? ? + point.getSignature().getName() + " throw exception");
? ? ? ? System.out.println(ex.getMessage());
? ? }
(3)然后修改配置為:
? ? ? ? <aop:aspectj-autoproxy />
<!-- 定義通知內(nèi)容老厌,也就是切入點(diǎn)執(zhí)行前后需要做的事情 -->
<bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
<!-- 定義被代理者 -->
<bean id="me" class="com.springAOP.bean.Me"></bean>
(4)最后測(cè)試,一樣的結(jié)果黎炉!
public class Test {
public static void main(String[] args){
@SuppressWarnings("resource")
//如果是web項(xiàng)目枝秤,則使用注釋的代碼加載配置文件,這里是一般的Java項(xiàng)目慷嗜,所以使用下面的方式
? ? //ApplicationContext appCtx = new ClassPathXmlApplicationContext("application.xml");
ApplicationContext appCtx = new FileSystemXmlApplicationContext("application.xml");
Sleepable me = (Sleepable)appCtx.getBean("me");
me.sleep();
}
}
3.使用Spring來(lái)定義純粹的POJO切面(名字很繞口淀弹,其實(shí)就是純粹通過(guò)<aop:fonfig>標(biāo)簽配置,也是一種比較簡(jiǎn)單的方式)
1)修改后的SleepHelper類庆械,很正常的類薇溃,所以這種方式的優(yōu)點(diǎn)就是在代碼中不體現(xiàn)任何AOP相關(guān)配置,純粹使用xml配置缭乘。
public class SleepHelper{
? ? public void beforeSleep(){
? ? ? ? System.out.println("睡覺(jué)前要脫衣服!");
? ? }
? ? public void afterSleep(){
? ? ? ? System.out.println("睡醒了要穿衣服沐序!");
? ? }
}
(2)配置文件:
<?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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<!-- 定義通知內(nèi)容,也就是切入點(diǎn)執(zhí)行前后需要做的事情 -->
<bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
<!-- 定義被代理者 -->
<bean id="me" class="com.springAOP.bean.Me"></bean>
<aop:config>
<aop:aspect ref="sleepHelper">
<aop:before method="beforeSleep" pointcut="execution(* *.sleep(..))" />
<aop:after method="afterSleep" pointcut="execution(* *.sleep(..))" />
</aop:aspect>
</aop:config>
</beans>
(3)配置的另一種寫法
<aop:config>
<aop:aspect ref="sleepHelper">
? ? ? ? ? ? <aop:pointcut id="sleepHelpers" expression="execution(* *.sleep(..))" />
? ? ? ? ? ? <aop:before pointcut-ref="sleepHelpers" method="beforeSleep" />
? ? ? ? ? ? <aop:after pointcut-ref="sleepHelpers" method="afterSleep" />? ? ?
? ? ? ? </aop:aspect>
</aop:config>
五、AspectJ與AOP與CGLIB
AspectJ與AOP
1.spectJ是一套獨(dú)立的面向切面編程的解決方案策幼。
1.1AspectJ 安裝
? ? ? AspectJ 下載地址(http://www.eclipse.org/aspectj/downloads.php)邑时。
1.2下載AspectJ? jar包,然后雙擊安裝特姐。安裝好的目錄結(jié)構(gòu)為:
bin:存放了 aj晶丘、aj5、ajc唐含、ajdoc铣口、ajbrowser 等命令,其中 ajc 命令最常用觉壶,它的作用類似于 javac
doc:存放了 AspectJ 的使用說(shuō)明脑题、參考手冊(cè)、API 文檔等文檔
lib:該路徑下的 4 個(gè) JAR 文件是 AspectJ 的核心類庫(kù)
1.3AspectJ HelloWorld 實(shí)現(xiàn)
業(yè)務(wù)組件SayHelloServicepackagecom.ywsc.fenfenzhong.aspectj.learn;publicclassSayHelloService{publicvoidsay(){System.out.print("Hello? AspectJ");}}需要來(lái)了铜靶,在需要在調(diào)用say()方法之后叔遂,需要記錄日志。那就是通過(guò)AspectJ的后置增強(qiáng)吧争剿。
LogAspect日志記錄組件已艰,實(shí)現(xiàn)對(duì)com.ywsc.fenfenzhong.aspectj.learn.SayHelloService后置增強(qiáng)packagecom.ywsc.fenfenzhong.aspectj.learn;publicaspectLogAspect{pointcutlogPointcut():execution(voidSayHelloService.say());after():logPointcut(){System.out.println("記錄日志 ...");}}
編譯SayHelloService
執(zhí)行命令? ajc-d.SayHelloService.javaLogAspect.java生成SayHelloService.class執(zhí)行命令? ? javaSayHelloService輸出HelloAspectJ記錄日志
ajc.exe 可以理解為 javac.exe 命令,都用于編譯 Java 程序蚕苇,區(qū)別是 ajc.exe 命令可識(shí)別 AspectJ 的語(yǔ)法哩掺;我們可以將 ajc.exe 當(dāng)成一個(gè)增強(qiáng)版的 javac.exe 命令.執(zhí)行ajc命令后的 SayHelloService.class 文件不是由原來(lái)的 SayHelloService.java 文件編譯得到的,該 SayHelloService.class 里新增了打印日志的內(nèi)容——這表明 AspectJ 在編譯時(shí)“自動(dòng)”編譯得到了一個(gè)新類涩笤,這個(gè)新類增強(qiáng)了原有的 SayHelloService.java 類的功能嚼吞,因此 AspectJ 通常被稱為編譯時(shí)增強(qiáng)的 AOP 框架。
與 AspectJ 相對(duì)的還有另外一種 AOP 框架蹬碧,它不需要在編譯時(shí)對(duì)目標(biāo)類進(jìn)行增強(qiáng)舱禽,而是運(yùn)行時(shí)生成目標(biāo)類的代理類,該代理類要么與目標(biāo)類實(shí)現(xiàn)相同的接口恩沽,要么是目標(biāo)類的子類——總之誊稚,代理類的實(shí)例可作為目標(biāo)類的實(shí)例來(lái)使用。一般來(lái)說(shuō)罗心,編譯時(shí)增強(qiáng)的 AOP 框架在性能上更有優(yōu)勢(shì)——因?yàn)檫\(yùn)行時(shí)動(dòng)態(tài)增強(qiáng)的 AOP 框架需要每次運(yùn)行時(shí)都進(jìn)行動(dòng)態(tài)增強(qiáng)里伯。
2.Spring AOP也是對(duì)目標(biāo)類增強(qiáng),生成代理類渤闷。但是與AspectJ的最大區(qū)別在于---Spring AOP的運(yùn)行時(shí)增強(qiáng)疾瓮,而AspectJ是編譯時(shí)增強(qiáng)。
? ? ? 曾經(jīng)以為AspectJ是Spring AOP一部分肤晓,是因?yàn)镾pring AOP使用了AspectJ的Annotation爷贫。使用了Aspect來(lái)定義切面,使用Pointcut來(lái)定義切入點(diǎn),使用Advice來(lái)定義增強(qiáng)處理补憾。雖然使用了Aspect的Annotation漫萄,但是并沒(méi)有使用它的編譯器和織入器。其實(shí)現(xiàn)原理是JDK 動(dòng)態(tài)代理盈匾,在運(yùn)行時(shí)生成代理類腾务。
CGLIB與JDK動(dòng)態(tài)代理
1、JDK動(dòng)態(tài)代理
利用攔截器(攔截器必須實(shí)現(xiàn)InvocationHanlder)加上反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的匿名類削饵,
在調(diào)用具體方法前調(diào)用InvokeHandler來(lái)處理岩瘦。
2、CGLIB動(dòng)態(tài)代理
利用ASM開(kāi)源包窿撬,對(duì)代理對(duì)象類的class文件加載進(jìn)來(lái)启昧,通過(guò)修改其字節(jié)碼生成子類來(lái)處理。
3劈伴、何時(shí)使用JDK還是CGLIB密末?
1)如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,默認(rèn)情況下會(huì)采用JDK的動(dòng)態(tài)代理實(shí)現(xiàn)AOP跛璧。
2)如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口严里,可以強(qiáng)制使用CGLIB實(shí)現(xiàn)AOP。
3)如果目標(biāo)對(duì)象沒(méi)有實(shí)現(xiàn)了接口追城,必須采用CGLIB庫(kù)刹碾,Spring會(huì)自動(dòng)在JDK動(dòng)態(tài)代理和CGLIB之間轉(zhuǎn)換。
4座柱、如何強(qiáng)制使用CGLIB實(shí)現(xiàn)AOP迷帜?
1)添加CGLIB庫(kù)(aspectjrt-xxx.jar、aspectjweaver-xxx.jar色洞、cglib-nodep-xxx.jar)
2)在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
4.4具體實(shí)現(xiàn)
接口:
package com.jpeony.spring.proxy.compare;
/**
* 用戶管理接口(真實(shí)主題和代理主題的共同接口瞬矩,這樣在任何可以使用真實(shí)主題的地方都可以使用代理主題代理。)
* --被代理接口定義
*/
public interface IUserManager {
? ? void addUser(String id, String password);
}
實(shí)現(xiàn)類:
package com.jpeony.spring.proxy.compare;
/**
* 用戶管理接口實(shí)現(xiàn)(被代理的實(shí)現(xiàn)類)
*/
public class UserManagerImpl implements IUserManager {
? ? @Override
? ? public void addUser(String id, String password) {
? ? ? ? System.out.println("======調(diào)用了UserManagerImpl.addUser()方法======");
? ? }
}
JDK代理實(shí)現(xiàn):
package com.jpeony.spring.proxy.compare;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK動(dòng)態(tài)代理類
*/
public class JDKProxy implements InvocationHandler {
? ? /** 需要代理的目標(biāo)對(duì)象 */
? ? private Object targetObject;
? ? /**
? ? * 將目標(biāo)對(duì)象傳入進(jìn)行代理
? ? */
? ? public Object newProxy(Object targetObject) {
? ? ? ? this.targetObject = targetObject;
? ? ? ? //返回代理對(duì)象
? ? ? ? return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
? ? ? ? ? ? ? ? targetObject.getClass().getInterfaces(), this);
? ? }
? ? /**
? ? * invoke方法
? ? */
? ? @Override
? ? public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
? ? ? ? // 一般我們進(jìn)行邏輯處理的函數(shù)比如這個(gè)地方是模擬檢查權(quán)限
? ? ? ? checkPopedom();
? ? ? ? // 設(shè)置方法的返回值
? ? ? ? Object ret = null;
? ? ? ? // 調(diào)用invoke方法锋玲,ret存儲(chǔ)該方法的返回值
? ? ? ? ret? = method.invoke(targetObject, args);
? ? ? ? return ret;
? ? }
? ? /**
? ? * 模擬檢查權(quán)限的例子
? ? */
? ? private void checkPopedom() {
? ? ? ? System.out.println("======檢查權(quán)限checkPopedom()======");
? ? }
}
CGLIB代理實(shí)現(xiàn):
package com.jpeony.spring.proxy.compare;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* CGLibProxy動(dòng)態(tài)代理類
*/
public class CGLibProxy implements MethodInterceptor {
? ? /** CGLib需要代理的目標(biāo)對(duì)象 */
? ? private Object targetObject;
? ? public Object createProxyObject(Object obj) {
? ? ? ? this.targetObject = obj;
? ? ? ? Enhancer enhancer = new Enhancer();
? ? ? ? enhancer.setSuperclass(obj.getClass());
? ? ? ? enhancer.setCallback(this);
? ? ? ? Object proxyObj = enhancer.create();
? ? ? ? // 返回代理對(duì)象
? ? ? ? return proxyObj;
? ? }
? ? @Override
? ? public Object intercept(Object proxy, Method method, Object[] args,
? ? ? ? ? ? ? ? ? ? ? ? ? ? MethodProxy methodProxy) throws Throwable {
? ? ? ? Object obj = null;
? ? ? ? // 過(guò)濾方法
? ? ? ? if ("addUser".equals(method.getName())) {
? ? ? ? ? ? // 檢查權(quán)限
? ? ? ? ? ? checkPopedom();
? ? ? ? }
? ? ? ? obj = method.invoke(targetObject, args);
? ? ? ? return obj;
? ? }
? ? private void checkPopedom() {
? ? ? ? System.out.println("======檢查權(quán)限checkPopedom()======");
? ? }
}
客戶端測(cè)試類:
package com.jpeony.spring.proxy.compare;
/**
* 代理模式[[ 客戶端--》代理對(duì)象--》目標(biāo)對(duì)象 ]]
*/
public class Client {
? ? public static void main(String[] args) {
? ? ? ? System.out.println("**********************CGLibProxy**********************");
? ? ? ? CGLibProxy cgLibProxy = new CGLibProxy();
? ? ? ? IUserManager userManager = (IUserManager) cgLibProxy.createProxyObject(new UserManagerImpl());
? ? ? ? userManager.addUser("jpeony", "123456");
? ? ? ? System.out.println("**********************JDKProxy**********************");
? ? ? ? JDKProxy jdkPrpxy = new JDKProxy();
? ? ? ? IUserManager userManagerJDK = (IUserManager) jdkPrpxy.newProxy(new UserManagerImpl());
? ? ? ? userManagerJDK.addUser("jpeony", "123456");
? ? }
}
程序運(yùn)行結(jié)果:
5景用、JDK動(dòng)態(tài)代理和CGLIB字節(jié)碼生成的區(qū)別?
1)JDK動(dòng)態(tài)代理只能對(duì)實(shí)現(xiàn)了接口的類生成代理惭蹂,而不能針對(duì)類伞插。
2)CGLIB是針對(duì)類實(shí)現(xiàn)代理,主要是對(duì)指定的類生成一個(gè)子類盾碗,覆蓋其中的方法媚污,
? ? ?并覆蓋其中方法實(shí)現(xiàn)增強(qiáng),但是因?yàn)椴捎玫氖抢^承廷雅,所以該類或方法最好不要聲明成final耗美,
? ? ?對(duì)于final類或方法京髓,是無(wú)法繼承的。
6商架、CGlib比JDK快堰怨?
1)使用CGLib實(shí)現(xiàn)動(dòng)態(tài)代理,CGLib底層采用ASM字節(jié)碼生成框架蛇摸,使用字節(jié)碼技術(shù)生成代理類备图,
在jdk6之前比使用Java反射效率要高。唯一需要注意的是赶袄,CGLib不能對(duì)聲明為final的方法進(jìn)行代理揽涮,
因?yàn)镃GLib原理是動(dòng)態(tài)生成被代理類的子類。
2)在jdk6饿肺、jdk7蒋困、jdk8逐步對(duì)JDK動(dòng)態(tài)代理優(yōu)化之后,在調(diào)用次數(shù)較少的情況下敬辣,JDK代理效率高于CGLIB代理效率家破,
只有當(dāng)進(jìn)行大量調(diào)用的時(shí)候,jdk6和jdk7比CGLIB代理效率低一點(diǎn)购岗,但是到j(luò)dk8的時(shí)候汰聋,jdk代理效率高于CGLIB代理,
總之喊积,每一次jdk版本升級(jí)烹困,jdk代理效率都得到提升,而CGLIB代理消息確有點(diǎn)跟不上步伐乾吻。
7髓梅、Spring如何選擇用JDK還是CGLIB?
1)當(dāng)Bean實(shí)現(xiàn)接口時(shí)绎签,Spring就會(huì)用JDK的動(dòng)態(tài)代理枯饿。
2)當(dāng)Bean沒(méi)有實(shí)現(xiàn)接口時(shí),Spring使用CGlib是實(shí)現(xiàn)诡必。
3)可以強(qiáng)制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)奢方。
8.總結(jié)JDK動(dòng)態(tài)代理與CGLIB
JDK代理是不需要第三方庫(kù)支持,只需要JDK環(huán)境就可以進(jìn)行代理爸舒,使用條件:
1)實(shí)現(xiàn)InvocationHandler?
2)使用Proxy.newProxyInstance產(chǎn)生代理對(duì)象
3)被代理的對(duì)象必須要實(shí)現(xiàn)接口
CGLib必須依賴于CGLib的類庫(kù)蟋字,但是它需要類來(lái)實(shí)現(xiàn)任何接口代理的是指定的類生成一個(gè)子類,
覆蓋其中的方法扭勉,是一種繼承但是針對(duì)接口編程的環(huán)境下推薦使用JDK的代理