摘要:具體的動態代理運行原理這里暫不展開,網上有很多相關的內容,比如這篇翻譯過來就是面向方面切面編程。所以切面可以理解為和的集合。
1.靜態代理
在提及動態代理前先說明一下靜態代理模式,靜態代理模式是一種很常見的通用設計模式,實現也很簡單,uml類圖如下:
如上圖所示,代理類ProxyImpl和委托類都實現了同一個接口ObjectInterface,代理類和委托類是關聯關系。
舉個栗子,現在有一個發送短信消息的類SmsMessagePush,實現了MessagePush
public interface MessagePush { public void push(String receiver, String msg); }
public class SmsMessagePush implements MessagePush { @Override public void push(String receiver, String msg) { //do push } }
一般情況下,用戶直接調用SmsMessage.push()即可,為什么要用代理呢?一般有兩種情況
1:用戶無法直接訪問目標對象(委托對象)或者是不想讓用戶直接訪問目標對象,這時候proxy對象就承擔了一種類似跳板機或者防火墻的角色,代替用戶訪問目標對象或者對訪問者的權限進行篩選。
2:對委托對象的功能增強,比如上面的例子,在發送短信前添加對手機號碼進行校驗之類的功能。
public class SmsMessagePushProxy implements MessagePush { private SmsMessagePush smsMessagePush; public SmsMessagePushProxy(SmsMessagePush smsMessagePush) { this.smsMessagePush = smsMessagePush; } @Override public void push(String mobile, String msg) { if (!checkMobile(mobile)){ return; } smsMessagePush.push(mobile, msg); } private Boolean checkMobile(String mobile) { //do check } }
public class App { public static void main() { SmsMessagePushProxy smsMessagePushProxy = new SmsMessagePushProxy(new SmsMessagePush()); smsMessagePushProxy.push("10086", "老子明天不上班"); } }
上面的代理SmsMessagePushProxy在調用push方法前會對手機號碼進行過濾。代理類作為中介提供了對委托資源的訪問,但是代理和委托對象本質上是一樣的,都實現了同一個接口,所以當接口變化時代理類就需要對應的修改。而且沒增加一個委托類就需要增加一個對應的代理類,管理起來十分不方便且難以維護。為了解決這種情況,便引入了jdk動態代理。
2.動態代理靜態代理是在編碼階段就寫好的,而動態代理是在程序運行時通過類反射動態創建的。java的動態代理分為jdk動態代理和cglib動態代理,二者使用上最大的區別是jdk動態代理是面向接口的,cglib動態代理是面向對象的。即jdk proxy的委托對象必須實現了某個接口。這里暫不討論cglib的實現,只講下jdk。
動態代理主要涉及的類放在java.lang.reflect包下面(由此可見是基于反射實現的),主要涉及兩個類:調用處理器java.lang.reflect.InvocationHandle和主類java.lang.reflect.Proxy
其中InvocationHandler是一個接口,只定義了一個方法
public Object invoke(Object proxy, Method method, Object[] args)
從參數名就可以猜測,invoke方法是用來執行委托對象的具體方法的
Proxy類有很多靜態方法,最常用的一個方法是newProxyInstance,該方法會根據傳入的參數創建一個代理對象
@CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)
該方法接受三個參數,第一個參數指定要使用的類加載器,第二個參數是委托對象實現的一組接口。第三個參數指定使用哪個調用處理器。
第二個參數我開始還糾結為什么是個接口數組,難道一個代理實例可以代理多個實現了不同接口的委托對象?在invoke方法中也區分不了當前要執行的是哪個對象啊,而且這樣兩個接口有相同名稱的方法會產生沖突啊。。。后來發現大多數情況下是因為委托對象實現了多個接口。。。
下面舉個栗子:
假設有一個類PushServiceImpl實現了SmsPushInterface和MailPushInterface兩個接口
public class PushServiceImpl implements SmsPushInterface, MailPushInterface { @Override public void pushEmail(String address, String msg) { System.out.println("push a email to " + address + ",message is " + msg); } @Override public void pushSms(String mobile, String msg) { System.out.println("push a sms to " + mobile + ",message is " + msg); } } public interface SmsPushInterface { public void pushSms(String mobile, String msg); } public interface MailPushInterface { public void pushEmail(String address, String msg); }
現在想要對PushServiceImpl對象進行代理,定義一個getProxy方法,方法接收一個委托對象作為參數,返回的類型是Object,實際上返回的是一個代理對象,該代理對象實現了SmsPushInterface和MailPushInterface兩個接口。代理對象通過Proxy.newProxyInstance()方法創建。
public class ProxyTest { public Object getProxy(Object target) throws Exception { InvocationHandler handler = new PushHandler(target); return Proxy.newProxyInstance( ProxyTest.class.getClassLoader(), target.getClass().getInterfaces(), handler ); } @Test public void proxyTest() throws Exception { SmsPushInterface smsPushService = (SmsPushInterface) getProxy(new PushServiceImpl()); smsPushService.pushSms("10086", "這一切都是命運石之門的選擇"); MailPushInterface mailPushService = (MailPushInterface) getProxy(new PushServiceImpl()); mailPushService.pushEmail("31415926@qq.com", "都是時臣的錯"); } }
由于獲取代理類返回的是Object類型,在實際使用時要根據調用的方法轉換成對應的接口類型,注意這里不能轉成PushServiceImpl即實例對象的類型,因為返回的代理類是Proxy的子類,實現了這兩個接口而已。
在調用代理類的方法時,實際上是執行處理器在運行,如下所示,我們編寫一個PushHandler實現InvocationHandler接口并覆寫invoke方法:
public class PushHandler implements InvocationHandler { private Object proxied; PushHandler(Object proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("pushSms".equals(method.getName())) { String mobile = args[0].toString(); String msg = args[1].toString(); if (!checkMobile(mobile)) { throw new Exception("mobile invalid"); } } if ("pushEmail".equals(method.getName())) { String address = args[0].toString(); String msg = args[1].toString(); if (!checkMail(address)) { throw new Exception("mail address invalid"); } } return method.invoke(proxied, args); } private Boolean checkMobile(String mobile) { // check mobile valid return true; } private Boolean checkMail(String mailAddress) { // check mail valid return true; } }
PushHandler在構造方法中接受一個委托對象實例,最后實際執行的就是這個對象的方法。在執行getProxy返回的代理對象的方法時會調用PushHandler的invoke方法,其中proxy參數就是當前的代理對象(java.lang.reflect.Proxy的子類),method是當前執行方法,args是參數數組。在這個例子里我們根據方法名來做對應的檢查之后通過反射方法method.invoke()執行。
具體的動態代理運行原理這里暫不展開,網上有很多相關的內容,比如這篇
http://blog.jobbole.com/104433
3.AOPaop(Aspect Oriented Programming) 翻譯過來就是面向方面/切面編程。關于aop的定義有許多,這里引用一個可能不是特別準確但很容易理解的解釋:(出處www.zhihu.com/question/24863332/answer/253016908)
AOP是對OOP的一種補充。
面向對象(OOP)引入了繼承、多態、封裝,將系統的業務功能按照模塊劃分,每個模塊用一個或多個類來表示。
而對于一些系統功能,無法使用OOP的思想來實現它們。這些系統功能往往穿插在業務功能的各處,和業務代碼耦合在一起;而且系統功能往往會被重復使用,這就導致了模塊不利于復用,這就是使用OOP實現系統功能的弊端。
AOP即為面向切面編程,它把系統需求按照功能分門歸類,把它們封裝在一個個切面中,然后再指定這些系統功能往業務功能中織入的規則。最后由第三方機構根據你指定的織入規則,將系統功能整合到業務功能中。
aop是一種思想而不是一種技術。所以說,如果拋開spring,我上面寫的動態代理甚至靜態代理的例子也可以算是一種aop。
spring中的aop實現分為兩種,基于動態代理的aop和基于AspectJ的aop,這里不得不吐槽國內的各種文章,根本沒搞清二者的區別,或者打著spring aop的標題然后開始講aspectJ的使用,你抄我我抄他,越抄越混亂。
在網上一搜一大片所謂AspectJ的用法,其實都是AspectJ的“切面語法”,只是AspectJ框架的冰山一角,AspectJ是完全獨立于Spring存在的一個Eclipse發起的項目,官方關于AspectJ的描述是:
Eclipse AspectJ is a seamless aspect-oriented extension to the Java? programming language. It is Java platform compatible easy to learn and use.
是的AspectJ甚至可以說是一門獨立的語言,我們常看到的在spring中用的@Aspect注解只不過是Spring2.0以后使用了AspectJ的風格而已本質上還是Spring的原生實現,關于這點Spring的手冊中有提及:
@AspectJ使用了Java 5的注解,可以將切面聲明為普通的Java類。@AspectJ樣式在AspectJ 5發布的AspectJ project部分中被引入。Spring 2.0使用了和AspectJ 5一樣的注解,并使用AspectJ來做切入點解析和匹配。但是,AOP在運行時仍舊是純的Spring AOP,并不依賴于AspectJ的編譯器或者織入器(weaver)。
so 我們常用的org.aspectj.lang.annotation包下的AspectJ相關注解只是使用了AspectJ的樣式,至于全套的AspectJ以及織入器,那完全是另一套獨立的東西。
名詞解釋在看aop實現前,先解釋幾個基本名詞,對就是網上一搜一大片那些
advice
advice,常被翻譯成”增強“或者”通知“,實際上advice就是在切面中執行的額外操作,拿上面動態代理的例子來說在PushHandler::invoke()方法中,對手機號碼以及郵箱地址的檢查就是兩個advice。在很多aop框架中advice是以攔截器的形式存在的,advice又常分為前置型advice和后置型advice,像手機號碼檢查這種就屬于前置的advice,返回結果記錄日志就屬于后置advice
join point
連接點很容易理解,先看下spring手冊上的定義
a· point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.
join point 就是程序運行時的一個特征點,比如方法的執行或者異常的拋出。可以把join point理解為一個觸發條件。
point cut
point cut大部分情況下被翻譯為’切入點‘。很多人經常搞不清楚join point 和 point cut的區別,實際上二者完全不是一個維度的概念,如果說join point是名詞 point cut就是謂詞。pointcut是一個規則,指定了哪些切入點會被切入。
比如:在test.network.message包下所有類的push()方法執行前,對入參做校驗 其中push()就是一個join point , 在xx前,對入參進行驗證是一個advice,而”在test.network.message包下所有類的push()方法“就是一個point cut。
做個比喻的話,一個插排,每個插孔都是一個join point,而電視插在哪,電腦插在哪,整個一個布線規則就是一個point cut
aspect
在test.network.message包下所有類的push()方法執行前,對入參做校驗 整個這個行為就是一個切面了。所以切面可以理解為point cut和advice的集合。在使用AspectJ樣式時,被@Aspect注解標注的類就是一個切面。
為防止翻譯不統一造成的誤解,下面對名詞的使用直接使用英文原文
Spring AOPspring aop常被人詬病復雜,難用。但又有多少新司機真正的用過而不是上來就被@Aspectj那一套洗腦了,問你為什么spring aop難用,AspectJ的優勢在哪又有多少人能答上來。
spring aop相關的內容基本都在org.springframework.aop.framework包下,spring aop是通過代理工廠實現的,主要涉及的類圖如下:
兩個常用的aop工廠類ProxyFactoryBean和ProxyFactoryBean繼承自ProxyCreatorSupport,proxyCreator是代理類的基類,并擁有一個實現了AopProxyFactory接口的屬性DefaultAopProxyFactory,ProxyFactory中的getProxy方法實際上最后是調用的是AopProxy接口中的getProxy方法,而實現了AopProxy接口的對象(JdkDynamicAopProxy或CglibAopProxy)是由DefaultAopProxyFactory創建的。這么說可能會有點繞,下面用一張時序圖來解釋(圖是我偷的,懶得畫了,出處:https://www.jianshu.com/p/500...)
DefaultAopProxyFactory在創建aop代理時會判斷委托類是否實現了某個接口,是的話創建JdkDynamicAopProxy,否則的話創建ObjenesisCglibAopProxy,代碼如下:
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }
下面看一個使用proxyFactory實現aop的例子
public class SpringAopTest { @Test public void proxy() { PushServiceImpl pushService = new PushServiceImpl(); //創建工廠 ProxyFactory proxyFactory = new ProxyFactory(pushService); //添加advice proxyFactory.addAdvice(new SmsPushBeforeAdvice()); proxyFactory.addAdvice(new SmsPushAfterAdvice()); //獲取代理 SmsPushInterface proxy = (SmsPushInterface) proxyFactory.getProxy(); proxy.pushSms("10086", "EL PSY CONGROO"); } }
public class SmsPushBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { if ("pushSms".equals(method.getName())) { String mobile = args[0].toString(); String msg = args[1].toString(); if (!checkMobile(mobile)) { throw new Exception("mobile invalid"); } } } private Boolean checkMobile(String mobile) { //do mobile check System.out.println("check mobile valid"); return true; } }
public class SmsPushAfterAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { StringBuffer logData = new StringBuffer(); logData.append("get method return : method:" + method.getName()); logData.append(" message : "" + args[1] + ""was sent"); if (returnValue == null) { logData.append(" and return value is void"); } addLog(logData.toString()); } private void addLog(String logMsg) { //do log System.out.println("get log info: " + logMsg); } }
下面是測試運行時的輸出:
check mobile valid push a sms to 10086,message is EL PSY CONGROO get log info: get method return : method:pushSms message : "EL PSY CONGROO"was sent and return value is void
可以看到,使用proxyFactory實現aop和上面的動態代理的例子十分相似 ,spring aop中的advice就等同于我們在動態代理中InvocationHandler中的增強操作。
常用的advice有四種:
前置advice:在方法執行前觸發的advice,實現了 org.springframework.aop.MethodBeforeAdvice接口
后置advice: 在方法返回時觸發的advice,實現了org.springframework.aop.AfterReturningAdvice接口
異常advice:在拋出異常后觸發,實現了org.springframework.aop.ThrowsAdviceArround
環繞advice:自定義觸發時機,實現和InvokeHandler類似,實現了org.aopaliance.intercept.MethodInterceptor
advisor:
上面的例子,添加的advice在實際運行時會包裝為Advisor對象,advisor包含了advice和pointcut,可以理解為一個切面(aspect),下面是AdvisedSupport類的addAdvice的方法實現,可以看到在執行addAdvice方法時會封裝為DefaultPointcutAdvisor實例
public void addAdvice(int pos, Advice advice) throws AopConfigException { Assert.notNull(advice, "Advice must not be null"); if (advice instanceof IntroductionInfo) { // We don"t need an IntroductionAdvisor for this kind of introduction: // It"s fully self-describing. addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice)); } else if (advice instanceof DynamicIntroductionAdvice) { // We need an IntroductionAdvisor for this kind of introduction. throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor"); } else { addAdvisor(pos, new DefaultPointcutAdvisor(advice)); } }
在DefaultPointcutAdvisor中,pointCut被默認設置為Poincut.TRUE,此時會匹配被代理對象的所有方法
public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable { private Pointcut pointcut = Pointcut.TRUE;
除了直接設置advice以外,我們還可以設置advisor,并指定對應的pointcut,這樣就可以指定哪些方法會被切入,pointcut和advisor有許多包裝類型,都在org.springframework.aop.support包下,這里使用最常用的正則pointcut:JdkRegexpMethodPointcut舉個栗子
public void pointTest() { SmsPushInterface pushService = new PushServiceImpl(); //創建工廠 ProxyFactory proxyFactory = new ProxyFactory(pushService); //創建advisor并添加advice DefaultPointcutAdvisor beforeAdvisor = new DefaultPointcutAdvisor(new SmsPushBeforeAdvice()); DefaultPointcutAdvisor afterAdvisor = new DefaultPointcutAdvisor(new SmsPushAfterAdvice()); //創建正則方法pointcut JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut(); //設置正則規則 pointcut.setPattern(PushServiceImpl.class.getName() + ".push.*"); beforeAdvisor.setPointcut(pointcut); afterAdvisor.setPointcut(pointcut); //設置advisor proxyFactory.addAdvisor(beforeAdvisor); proxyFactory.addAdvisor(afterAdvisor); //獲取代理 SmsPushInterface proxy = (SmsPushInterface) proxyFactory.getProxy(); proxy.pushSms("10086", "EL PSY CONGROO"); }
可以看到這次我們沒有直接使用addAdvice而是創建了兩個advisor,并創建了一個pointcut,設置正則pattern匹配所有以push開頭的方法。
這里有一個坑,我開始在設置pattern時是直接寫成 setPattern("push.*")的,發現沒有匹配,后來發現AbstractRegexpMethodPointcut的matches方法里是這樣寫的
public boolean matches(Method method, Class> targetClass) { return ((targetClass != null && matchesPattern(ClassUtils.getQualifiedMethodName(method, targetClass))) || matchesPattern(ClassUtils.getQualifiedMethodName(method))); }
ClassUtils.getQualifiedMethodName方法會將method名稱拼上類名,即要匹配的方法名實際上是 spring.aop.PushServiceImpl.pushSms
在使用時我們會發現這樣手動設置pointcut和advisor十分麻煩,所以spring aop提供了一些包裝好的advisor,比如上面的這個正則的例子我們就可以直接使用org.springframework.aop.supportRegexpMethodPointcutAdvisor,還有一些更方便的包裝advisor比如下面這個例子直接使用了NameMatchMethodPointcutAdvisor設置匹配的方法名
@Test public void proxy() { PushServiceImpl pushService = new PushServiceImpl(); //創建工廠 ProxyFactory proxyFactory = new ProxyFactory(pushService); //創建方法名稱匹配advisor并添加advice NameMatchMethodPointcutAdvisor beforeAdvisor = new NameMatchMethodPointcutAdvisor(new SmsPushBeforeAdvice()); NameMatchMethodPointcutAdvisor afterAdvisor = new NameMatchMethodPointcutAdvisor(new SmsPushAfterAdvice()); //設置匹配的方法名 beforeAdvisor.setMappedName("pushSms"); afterAdvisor.setMappedName("pushSms"); //設置advisor proxyFactory.addAdvisor(beforeAdvisor); proxyFactory.addAdvisor(afterAdvisor); //獲取代理 SmsPushInterface proxy = (SmsPushInterface) proxyFactory.getProxy(); proxy.pushSms("10086", "EL PSY CONGROO"); }
以上就是SpringAop的基本使用方法,通過上面的例子可以看出springAop的確存在一些問題,最明顯的就是切面不夠獨立,對業務代碼的侵入性很強,聲明Aspect需要以過程的形式顯示聲明(雖然ProxyFactoryBean可以將切面部分封裝為bean,但是我看到xml是在是想吐)。而且advice和pointcut的結合靈活性較差,實際使用時還需要自己寫一些輪子。spring也認識到了這些問題并在spring2.0之后推出了AspectJ樣式的Aop
AspectJ樣式AOP再次強調一下這里講的AspectJ樣式的aop只是使用了AspectJ的一些語法特性,底層依舊是SpringAop實現的
首先 使用aspectJ樣式的aop需要一些額外配置
springboot
如果你使用的是springboot,可以通過在主類使用@EnableAspectJAutoProxy注解來開啟,另外如果你使用了@EnableAutoConfiguration會默認開啟。如果想關閉aop可以配置設置spring.aop.auto = false,spring.aop.proxy-target-class可以指定使用jdk代理還是cglib代理,默認是jdk(false:jdk,true:cglib)
spring
普通的spring框架可以通過設置
前面springAop已經將aop的概念說的很清楚這里就不扯淡了直接上個例子:
@Aspect注解將當前類標記為一個切面,@Component將PushAspect注冊為Bean
@Pointcut注解將一個void方法標記為一個pointcut,execution、within、args等被稱為pointcut designators(切點標志符)簡稱為PCD,每個PCD都有對應的表達式,比如最常用的execution,下面的例子第一個表示修飾符,即public還是private之類。后面緊接著是包名+類名+方法名,spring.aop.PushServiceImpl.* 表示匹配 spring.aop包下PushServiceImpl類的所有方法。最后是參數標識(String,Stirng)表示參數是兩個String類型的方法,若要匹配所有參數類型,可以使用(..)表示
@Aspect @Component public class PushAspect { @Pointcut(value = "execution(* spring.aop.PushServiceImpl.pushSms(String,String))") public void pushSmsPointcut() { } @Pointcut("execution(* spring.aop.PushServiceImpl.*(..))") public void pushMethodPointcut() { } @Before("pushSmsPointcut() && args(mobile,msg)") public void checkMobile(String mobile, String msg) throws Exception { if (!checkMobile(mobile)) { throw new Exception("mobile invalid"); } } private Boolean checkMobile(String mobile) { //do mobile check System.out.println("check mobile valid"); return true; } @AfterReturning(pointcut = "pushMethodPointcut()", returning = "returnValue") public void writeLog(JoinPoint joinPoint, Object returnValue) { StringBuffer logData = new StringBuffer(); logData.append("get method return : method:" + joinPoint.getSignature().getName()); logData.append(" message : "" + joinPoint.getArgs()[1].toString() + ""was sent"); if (returnValue == null) { logData.append(" and return value is void"); } addLog(logData.toString()); } private void addLog(String logMsg) { //do log System.out.println("get log info: " + logMsg); } }
@Before、@AfterReturn、@Around等注解標識了一個advice方法,advice和pointcut相關聯,像上面這個例子前置advice checkMobile()就是作用于被pushSmsPointcut標識的方法上,pointcut可以顯示綁定在value或pointcut參數上,或者不寫參數默認第一表達式就是pointcut。也可以不寫pointcut,在advice表達式內直接寫PCD,但是不建議這樣做
關于怎么向advice body里傳遞參數,先看下手冊里的描述:
To make argument values available to the advice body, you can use the binding form of args. If a parameter name is used in place of a type name in an args expression, then the value of the corresponding argument will be passed as the parameter value when the advice is invoked
所以想要傳遞參數在advcie中使用args表達式即可,注意這里要在args里寫參數名,而不是寫參數類型。寫參數類型的話是另一種用法,一般寫在PCD中,作為joinpoint匹配時參數類型的篩選。另外傳遞的參數中還有一個特別的參數類型JoinPoint. jointpoint包含了很多實用的反射方法,必須獲取當前的代理類,獲取參數列表等等,joinpoint可以作為第一個參數傳入advice且不用再參數列表里指定。這里注意使用around advice時傳入的是ProceedingJoinPoint類型的jointpoint
execution表達式中的參數和args
在designator表達式中,有兩種方式可以限定方法的參數,一個是通過execution表達式的最后一個參數,另一個是通過args標識符,二者有什么區別?
args(java.lang.String) execution(* *(java.lang.String))
區別是args表達式標識的是在運行時傳入的參數是String類型,而execution表達式表示的是方法簽名在定義的時候是String。
Aspectj風格還有很多designators(with,@annotation等),以及advice類型,這里不再贅述,可以參考spring的手冊。雖然是英文的但寫的很詳細
手冊:https://docs.spring.io/spring...
下面是委托對象的執行(實際上不應該再叫他委托對象了,因為這里已經將aop剝離出來了,用戶感知不到代理的過程)。可以看到和IOC結合起來十分方便,切面和業務代碼沒有任何耦合。這里如果把注入的SmsPushInterface換成SmsBean named "pushServiceImpl" is expected to be of type "spring.aop.PushServiceImpl" but was actually of type "com.sun.proxy.$Proxy61".可以看出底層的實現還是使用的動態代理,如果這里想聲明Impl類型可以把代理改成cglib.
@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) public class SpringAopTest { @Resource SmsPushInterface pushService; @Test public void aspectjTest() throws Exception { pushService.pushSms("10086", "EL PSY CONGROO"); }
上面的例子都是在IOC環境中自動加載的,如果脫離這個環境想在過程中執行怎么辦?spring提供了一個org.springframework.aop.aspectj.annotation.AspectJProxyFactory 代理工廠,和ProxyFactory一樣繼承ProxyCreatorSupport,通過該工廠可以像proxyFactory一樣直接顯示進行代理
@Test public void testAspectJProxyFactory() { PushServiceImpl pushService = new PushServiceImpl(); AspectJProxyFactory proxyFactory = new AspectJProxyFactory(pushService); proxyFactory.addAspect(PushAspect.class); SmsPushInterface proxy = (SmsPushInterface) proxyFactory.getProxy(); proxy.pushSms("10086", "EL PSY CONGROO"); }
從下面的源碼可以看出,這里addAspect實際上還是把@Aspect解析成Advisor來處理
public void addAspect(Class> aspectClass) { String aspectName = aspectClass.getName(); AspectMetadata am = createAspectMetadata(aspectClass, aspectName); MetadataAwareAspectInstanceFactory instanceFactory = createAspectInstanceFactory(am, aspectClass, aspectName); addAdvisorsFromAspectInstanceFactory(instanceFactory); }
由此可見,AspectJ樣式的方便不只是體現在提供了一些方便的注解以及PCD,更體現在和Spring IOC的完美結合
關于@Aspect的解析原理,是在沒時間寫了,以后有時間再補吧。
寫的比較匆忙如有問題歡迎指正
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69735.html
摘要:了解的相關術語通知通知定義了切面是什么,以及何時使用。描述了切面要完成的工作和何時需要執行這個工作。就是用來配置切面設置代理模式。 了解AOP的相關術語 1.通知(Advice): 通知定義了切面是什么,以及何時使用。描述了切面要完成的工作和何時需要執行這個工作。 2.連接點(Joinpoint): 程序能夠應用通知的一個時機,這些時機就是連接點,例如方法被調用時、異常被拋出時等等。 ...
摘要:是一種特殊的增強切面切面由切點和增強通知組成,它既包括了橫切邏輯的定義也包括了連接點的定義。實際上,一個的實現被拆分到多個類中在中聲明切面我們知道注解很方便,但是,要想使用注解的方式使用就必須要有源碼因為我們要 前言 只有光頭才能變強 上一篇已經講解了Spring IOC知識點一網打盡!,這篇主要是講解Spring的AOP模塊~ 之前我已經寫過一篇關于AOP的文章了,那篇把比較重要的知...
摘要:入門篇學習總結時間年月日星期三說明本文部分內容均來自慕課網。主要的功能是日志記錄,性能統計,安全控制,事務處理,異常處理等等。 《Spring入門篇》學習總結 時間:2017年1月18日星期三說明:本文部分內容均來自慕課網。@慕課網:http://www.imooc.com教學示例源碼:https://github.com/zccodere/s...個人學習源碼:https://git...
摘要:下例表示方法入參為的方法匹配該切點,并將和兩個參數綁定到切面方法的入參中綁定代理對象使用或可以綁定被代理對象的實例。 1. 術語 連接點(JointPoint):代碼中具有邊界性質特定點;Spring僅支持方法的連接點,包含方法和方位兩方面信息 切點(Pointcut):定位到某個方法 增強(Advice):織入到目標連接點上的代碼 目標對象(Target):增強邏輯的目標織入類 引...
摘要:何為簡單點來定義就是切面,是一種編程范式。定義一個切面的載體定義一個切點定義一個為,并指定對應的切點一個注冊配置類,啟動容器,初始化時期獲取對象,獲取對象時期,并進行打印好了,這樣我們整體的代理就已經完成。 問題:Spring AOP代理中的運行時期,是在初始化時期織入還是獲取對象時期織入? 織入就是代理的過程,指目標對象進行封裝轉換成代理,實現了代理,就可以運用各種代理的場景模式。 ...
閱讀 1104·2021-09-22 15:37
閱讀 1131·2021-09-13 10:27
閱讀 2466·2021-08-25 09:38
閱讀 2445·2019-08-26 11:42
閱讀 1524·2019-08-26 11:39
閱讀 1554·2019-08-26 10:58
閱讀 2317·2019-08-26 10:56
閱讀 2569·2019-08-23 18:08