鏈接點(JointPoint):代碼中具備邊界性質特定點;Spring僅支持方法的鏈接點,包含方法和方位兩方面信息java
切點(Pointcut):定位到某個方法spring
加強(Advice):織入到目標鏈接點上的代碼數組
目標對象(Target):加強邏輯的目標織入類ide
引介(Introduction):特殊的加強,爲類添加一些屬性和方法函數
織入(Weaving):將加強添加到目標鏈接點上的過程:編譯期織入、類裝載期織入、動態代理織入(Spring的方案)性能
代理(Proxy):被AOP織入加強後的結果類測試
切面(Aspect):切點+加強this
JDK動態代理動態建立一個符合某一接口的實力,生成目標類的代理對象,缺點是須要提供接口;方法必須是public
或public final
的代理
CGLib採用底層的字節碼技術,在子類中對父類的方法進行攔截,織入橫切邏輯;不能爲final
和private
方法代理code
樣例
package test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class ProxyTest { public static void main(String[] args) { ServiceImpl jdkTarget = new ServiceImpl(); ProxyHandler handler = new ProxyHandler(jdkTarget); Service jdkProxy = (Service)Proxy.newProxyInstance( jdkTarget.getClass().getClassLoader(), jdkTarget.getClass().getInterfaces(), handler); jdkProxy.process("jdk proxy"); System.out.println(); CglibProxy cglibProxy = new CglibProxy(); ServiceImpl cglibTarget = (ServiceImpl)cglibProxy.getProxy(ServiceImpl.class); cglibTarget.process("cglib proxy"); } public interface Service { public void process(String arg); } public static class ServiceImpl implements Service { @Override public void process(String arg) { System.out.println("do something with " + arg); } } //jdk proxy public static class ProxyHandler implements InvocationHandler { private Object target; public ProxyHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before process jdk proxy"); Object obj = method.invoke(target, args); System.out.println("after process jdk proxy"); return obj; } } //cglib proxy public static class CglibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz) { enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("before process cglib proxy"); Object result = proxy.invokeSuper(obj, args); System.out.println("after process cglib proxy"); return result; } } }
結果
before process jdk proxy do something with jdk proxy after process jdk proxy before process cglib proxy do something with cglib proxy after process cglib proxy
性能:CGLib所建立的動態代理對象性能比JDK方式高(約10倍),但CGLib在建立代理對象時所花費的時間比JDK方式多(約8倍);CGLib適合Spring裏singleton模式的bean管理
Spring定義了org.springframework.aop.framework.AopProxy
接口及Cglib2AopProxy
和JdkDynamicAopProxy
兩個final
實現類
若是經過ProxyFactory
的setInterfaces(Class[] interfaces)
指定針對接口代理,則使用JdkDynamicAopProxy
;若是使用setOptimize(true)
,使用Cglib2AopProxy
ProxyFacotry
經過addAdvice(Advice)
造成加強鏈
接口:org.springframework.aop.BeforeAdvice
樣例
package com.aop; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class BeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { String arg = (String)arg1[0]; System.out.println("before advice " + arg); } }
接口:org.springframework.aop.AfterReturninigAdvice
樣例
package com.aop; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; public class AfterAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { String arg = (String)args[0]; System.out.println("after advice " + arg); } }
接口:org.aopalliance.intercept.MethodInterceptor
樣例
package com.aop; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class AroundAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object[] args = invocation.getArguments(); String arg = (String)args[0]; System.out.println("around advice: before " + arg); Object obj = invocation.proceed(); System.out.println("around advice: after " + arg); return obj; } }
接口:org.springframework.aop.ThrowsAdvice
樣例
package com.aop; import java.lang.reflect.Method; import org.springframework.aop.ThrowsAdvice; public class ExceptionAdvice implements ThrowsAdvice { public void afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable { System.out.println("------"); System.out.println("throws exception, method=" + method.getName()); System.out.println("throws exception, message=" + ex.getMessage()); } }
TestAopAdvice
package com.aop; import org.springframework.aop.ThrowsAdvice; import org.springframework.aop.framework.ProxyFactory; public class TestAopAdvice { public static void main(String[] args) { AopExample example = new AopExample(); BeforeAdvice beforeAdvice = new BeforeAdvice(); AfterAdvice afterAdvice = new AfterAdvice(); AroundAdvice aroundAdvice = new AroundAdvice(); ThrowsAdvice throwsAdvice = new ExceptionAdvice(); ProxyFactory pf = new ProxyFactory(); pf.setTarget(example); pf.addAdvice(beforeAdvice); pf.addAdvice(afterAdvice); pf.addAdvice(aroundAdvice); pf.addAdvice(throwsAdvice); AopExample proxy = (AopExample)pf.getProxy(); proxy.handle("blabla"); System.out.println(); try{ proxy.throwExp("blabla"); } catch(Exception e) { } } }
輸出
before advice blabla around advice: before blabla aop example blabla around advice: after blabla after advice blabla before advice blabla around advice: before blabla ----after throwing---- throws exception, method=throwExp throws exception, message=try throws advice
springAop.xml
<?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" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="aopExample" class="com.aop.AopExample" /> <bean id="beforeAdvice" class="com.aop.BeforeAdvice" /> <bean id="afterAdvice" class="com.aop.AfterAdvice" /> <bean id="aroundAdvice" class="com.aop.AroundAdvice" /> <bean id="exceptionAdvice" class="com.aop.ExceptionAdvice" /> <bean id="aopTest" class="org.springframework.aop.framework.ProxyFactoryBean" p:proxyTargetClass="true" p:interceptorNames="beforeAdvice,afterAdvice,aroundAdvice,exceptionAdvice" p:target-ref="aopExample" /> </beans>
TestAopAdvice2
package com.aop; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestAopAdvice2 { public static void main(String[] args) { ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("/springAop.xml"); AopExample aopExample = (AopExample)ctx.getBean("aopTest"); aopExample.handle("blabla"); System.out.println(); try{ aopExample.throwExp("blabla"); } catch(Exception e) { } ctx.close(); } }
輸出
二月 09, 2017 9:54:11 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@69d0a921: startup date [Thu Feb 09 21:54:11 CST 2017]; root of context hierarchy 二月 09, 2017 9:54:11 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [springAop.xml] before advice blabla around advice: before blabla aop example blabla around advice: after blabla after advice blabla before advice blabla around advice: before blabla ----after throwing---- throws exception, method=throwExp throws exception, message=try throws advice 二月 09, 2017 9:54:11 下午 org.springframework.context.support.ClassPathXmlApplicationContext doClose 信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@69d0a921: startup date [Thu Feb 09 21:54:11 CST 2017]; root of context hierarchy
接口:org.springframework.aop.IntroductionInterceptor
IntroductionAdvice
package com.aop; public interface IntroductionAdvice { public void setIntroductionActive(boolean active); }
ConfigurableIntroduction
package com.aop; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.support.DelegatingIntroductionInterceptor; public class ConfigurableIntroduction extends DelegatingIntroductionInterceptor implements IntroductionAdvice { private ThreadLocal<Boolean> map = new ThreadLocal<Boolean>(); @Override public void setIntroductionActive(boolean active) { map.set(active); } @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object obj = null; if(map.get() != null && map.get()) { System.out.println("before monitor operation"); obj = super.invoke(invocation); System.out.println("after monitor operation"); } else { obj = super.invoke(invocation); } return obj; } }
TestIntroductionAdvice
package com.aop; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestIntroductionAdvice { public static void main(String[] args) { ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("/springAop.xml"); AopExample introductionAop = (AopExample)ctx.getBean("introductionAop"); introductionAop.handle("introduction advice"); IntroductionAdvice ci = (IntroductionAdvice)introductionAop; ci.setIntroductionActive(true); introductionAop.handle("introduction advice"); ctx.close(); } }
springAop.xml
添加
<bean id="configurableIntroduction" class="com.aop.ConfigurableIntroduction" /> <bean id="introductionAop" class="org.springframework.aop.framework.ProxyFactoryBean" p:interfaces="com.aop.IntroductionAdvice" p:target-ref="aopExample" p:interceptorNames="configurableIntroduction" p:proxyTargetClass="true" />
輸出
二月 09, 2017 9:56:10 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@69d0a921: startup date [Thu Feb 09 21:56:10 CST 2017]; root of context hierarchy 二月 09, 2017 9:56:10 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [springAop.xml] aop example introduction advice ---- before monitor operation aop example introduction advice after monitor operation 二月 09, 2017 9:56:11 下午 org.springframework.context.support.ClassPathXmlApplicationContext doClose 信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@69d0a921: startup date [Thu Feb 09 21:56:10 CST 2017]; root of context hierarchy
須指定引介加強所實現的接口
只能經過爲目標類建立子類的方式生成引介加強的代理,所以proxyTargeClass
必須爲true
參數說明
target
:代理的對象
proxyInterfaces
:代理所要實現的接口
interceptorNames
:須要織入目標對象的bean列表,這些bean必須是實現了org.aopalliance.intercept.MethodInterceptor
或org.springframework.aop.Advisor
的bean,配置中的順序對應調用的順序
singleton
:返回的代理是否爲單例,默認爲true
optimize
:爲true
時使用CGLib代理
proxyTargetClass
:爲true
時使用CGLib代理,並覆蓋proxyInterfaces
設置
一個例子
package com.aspectj; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.annotation.ElementType; import java.lang.annotation.RetentionPolicy; @Target(ElementType.METHOD) //聲明可使用該註解的目標類型 @Retention(RetentionPolicy.RUNTIME)//聲明註解的保留期限 public @interface Authority { boolean value() default true;//聲明註解成員 }
成員以無入參無拋出異常的方式聲明
能夠經過default
爲成員指定一個默認值
在方法上使用註解:@Authority(value=true)
若是註解只有一個成員,需命名爲value()
,使用時能夠忽略成員名和賦值號(=),如@Authority(true)
註解類擁有多個成員時,若是僅對value
成員賦值,能夠不適用賦值號;若是同時對多個成員賦值,則必須使用賦值號
註解類能夠沒有成員,稱爲標識註解
全部註解類隱式繼承於java.lang.annotation.Annotation
,註解不容許顯式繼承於其餘接口
若是成員是數組類型,能夠經過{}賦值
定義切面
package com.aspectj; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class PreAspect { @Before("execution(*handle(..))") public void before() { System.out.println("aspect: before processing"); } }
測試
package com.aspectj; import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; import com.aop.AopExample; public class AspectJTest { public static void main(String[] args) { AspectJProxyFactory factory = new AspectJProxyFactory(); AopExample example = new AopExample(); factory.setTarget(example); factory.addAspect(PreAspect.class); AopExample proxy = factory.getProxy(); proxy.handle("pre aspect"); } }
結果
aspect: before processing aop example pre aspect
springAspectj.xml
<?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" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="aopExample" class="com.aop.AopExample" /> <bean id="preAspect" class="com.aspectj.PreAspect" /> <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" /> </beans>
springAspectj.xml
<?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" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:aspectj-autoproxy /> <bean id="aopExample" class="com.aop.AopExample" /> <bean id="preAspect" class="com.aspectj.PreAspect" /> </beans>
AspectJTest2
package com.aspectj; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.aop.AopExample; public class AspectJTest2 { public static void main(String[] args) { ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("/springAspectj.xml"); AopExample aopExample = (AopExample)ctx.getBean("aopExample"); aopExample.handle("blabla"); ctx.close(); } }
輸出
二月 09, 2017 10:13:56 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@69d0a921: startup date [Thu Feb 09 22:13:56 CST 2017]; root of context hierarchy 二月 09, 2017 10:13:56 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [springAspectj.xml] aspect: before processing aop example blabla 二月 09, 2017 10:13:57 下午 org.springframework.context.support.ClassPathXmlApplicationContext doClose 信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@69d0a921: startup date [Thu Feb 09 22:13:56 CST 2017]; root of context hierarchy
經過<aop:aspectj-autoproxy />
引入aop
命名空間,自動爲Spring容器中匹配@AspectJ
切面的bean建立代理,完成切面織入,其內部實現仍爲AnnotationAwareAspectJAutoProxyCreator
<aop:aspectj-autoproxy />
的proxy-target-class
屬性爲false
時,採用JDK動態代理;爲true
時使用CGLib
分類
類型 | 說明 | 舉例 |
---|---|---|
方法切點函數 | 經過描述目標類方法信息定義鏈接點 | execution() ,@annotation() |
方法入參切點函數 | 經過描述目標類方法入參的信息定義鏈接點 | args() ,@args() |
目標類切點函數 | 經過描述目標類類型信息定義鏈接點 | within() ,target() ,@within() ,@target() |
代理類切點函數 | 經過描述目標類的代理類的信息定義鏈接點 | this() |
函數說明
函數 | 入參 | 說明 |
---|---|---|
execution() |
方法匹配模式串 | 表示知足某一匹配模式的全部目標類方法鏈接點,如execution(* handle(..)) 表示全部目標類中的handle() 方法 |
@annotation() |
方法註解類名 | 表示標註了特定註解的目標方法鏈接點,如@annotation(com.aspectj.Authority) 表示任何標註了@Authority 註解的目標類方法 |
args() |
類名 | 經過判別目標類方法運行時入參對象的類型定義指定鏈接點,如args(com.data.Car) 表示全部有且僅有一個按類型匹配於Car (含子類)入參的方法 |
@args() |
類型註解類名 | 經過判別目標方法運行時入參對象的類是否標註特定註解來制定鏈接點,如@args(com.aspectj.Authority) 表示任何這樣的一個目標方法:它有一個入參且入參對象的類標註@Authority 註解。要使@args() 生效,類繼承樹中,標註註解的類類型須要不高於入參類類型 |
within |
類名匹配串 | 表示特定域下的全部鏈接點,如within(com.service.*) ,within(com.service.*Service) 和within(com.service..*) |
target() |
類名 | 假如目標按類型匹配於指定類,則目標類的全部鏈接點匹配這個切點。如經過target(com.data.Car) 定義的切點,Car 及Car 的子類中的全部鏈接點都匹配該切點,包括子類中擴展的方法 |
@within() |
類型註解類名 | 假如目標類按類型匹配於某個類A ,且類A 標註了特定註解,則目標類的全部鏈接點都匹配於這個切點。如@within(com.aspectj.Authority) 定義的切點,假如Car 類標註了@Authority 註解,則Car 以及Car 的子類的全部鏈接點都匹配。@within 標註接口類無效 |
@target() |
類型註解類名 | 目標類標註了特定註解,則目標類(不包括子類)全部鏈接點都匹配該切點。如經過@target(com.aspectj.Authority) 定義的切點,若BMWCar 標註了@Authority ,則BMWCar 全部鏈接點匹配該切點 |
this() |
類名 | 代理類按類型匹配於指定類,則被代理的目標類全部鏈接點匹配切點 |
類型 | 說明 |
---|---|
* |
匹配任意字符,但只能匹配上下文中的一個元素 |
.. |
匹配任意字符,能夠匹配上下文中的多個元素。表示類時,和* 聯合使用;表示入參時單獨使用 |
+ |
按類型匹配指定類的全部類(包括實現類和繼承類),必須跟在類名後面 |
支持全部通配符:execution()
,within()
僅支持+
通配符:args()
,this()
,target()
不支持通配符:@args
,@within
,@target
,@annotation
@Before
:前置加強,至關於BeforeAdvice
@AfterReturning
:後置加強,至關於AfterReturningAdvice
@Around
:環繞加強,至關於MethodInterceptor
@AfterThrowing
:至關於ThrowsAdvice
@After
:Final加強,拋出異常或正常退出都會執行的加強
@DeclareParents
:引介加強,至關於IntroductionInterceptor
語法:execution(<修飾符模式>? <返回類型模式> <方法名模式> (<參數模式>) <異常模式>?)
execution(pulic * *(..))
:匹配目標類的public
方法,第一個*
表明返回類型,第二個*
表明方法名,..
表明任意入參
execution(* *To(..))
:匹配目標類全部以To
結尾的方法,第一個*
表明返回類型,*To
表明任意以To
結尾的方法
execution(* com.data.User.*(..))
:匹配User
接口的全部方法
execution(* com.data.User+.*(..))
:匹配User
接口的全部方法,包括其實現類中不在User
接口中定義的方法
execution(* com.data.*(..))
:匹配data
包下全部類的全部方法
execution(* com.data.User..*(..))
:匹配data
包及其子孫包中的全部類的全部方法
execution(* com..*Manager.get*(..))
:匹配com
包及其子孫包中後綴爲Manager
的類裏以get
開頭的方法
execution(* get(String, int))
:匹配get(String, int)
方法
execution(* get(String, *))
:匹配名爲get且第一個入參類型爲String
、第二個入參類型任意的方法
execution(* get(String, ..))
:匹配名爲get
且第一個入參爲String
類型的方法
execution(* get(Object+))
:匹配名爲get
且惟一入參是Object
或其子類的方法
與&&
,或||
,非!
例如:@After("within(com.data.*) && execution(* handle(..))")
使用@Pointcut
命名切點
使用方法名做爲切點的名稱,方法的訪問控制符控制切點的可用性
@Pointcut("within(com.data.*)") public void inPackage(){} //別名爲inPackage
若是加強在同一個切面類中聲明,則依照加強在切面類中定義的順序織入
若是加強位於不一樣的加強類中,且都實現了org.springframework.core.Ordered
接口,則由接口方法的順序號決定(順序號小的先織入)
若是加強位於不一樣的加強類中,且沒有實現org.springframework.core.Ordered
接口,織入順序不肯定
AspectJ使用org.aspectj.lang.JointPoint
接口表示目標類鏈接點對象。若是是環繞加強時,使用org.aspectj.lang.ProceedingJointPoint
表示鏈接點對象,該類是JointPoint
接口的子接口。任何一個加強方法均可以經過將第一個入參聲明爲JointPoint
訪問到鏈接點上下文的信息
args()
用於綁定鏈接點方法的入參,@annotation()
用於綁定鏈接點方法的註解對象,@args()
用於綁定鏈接點方法的入參註解。下例表示方法入參爲(String, int, ..)
的方法匹配該切點,並將name
和age
兩個參數綁定到切面方法的入參中
@Before("args(name, age, ..)") public void bindJointPointValues(String name, int age) { //do something }
使用this()
或target()
能夠綁定被代理對象的實例。下例表示代理對象爲User
類的全部方法匹配該切點,且代理對象綁定到user
入參中
@Before("this(user)") public void bindProxy(User user) { //do something }
@within()
和@target()
函數能夠將目標類的註解對象綁定到加強方法中
@Before("@within(a)") public void bindAnnotation(Authority a) { //do something }
經過returning
綁定鏈接點方法的返回值
@AfterReturning(value="target(com.data.Car)", returning="rvl") public void bindReturningValue(int rvl) { //do something }
rvl
的類型必須和鏈接點方法的返回值類型匹配
使用AfterThrowing
註解的throwing
成員綁定
@AfterThrowing(value="target(com.data.Car)", throwing="iae") public void bindException(IllegalArgumentException iae) { //do something }