PointCut
AOP標準中的Joinpoit
可以有很多類型:構造方法調用劫映、字段的設置和獲取房午、方法調用和執(zhí)行等蹋绽。而Spring AOP中只支持方法執(zhí)行類型的Joinpoint
拍霜,不過這已經(jīng)夠我們用了。
Spring AOP中通過接口org.springframework.aop.Pointcut
來表示所有連接點Joinpoit
的抽象挽铁。Pointcut
接口的代碼定義如下:
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}
ClassFilter
將用來匹配目標對象伟桅,MethodMatcher
用來匹配將被執(zhí)行織入操作的相應方法。TruePointcut
表示匹配所有對象叽掘。
public interface ClassFilter {
/**
* 當織入的目標對象的Class類型和Pointcut所規(guī)定的類型相同時贿讹,
* 該方法返回true
*/
boolean matches(Class<?> clazz);
/**
* 匹配所以類的ClassFilter實例.
*/
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
MethodMatcher
接口的代碼定義如下:
public interface MethodMatcher {
/**
* 判斷方法是否匹配,靜態(tài)的MethodMatcher調用
*/
boolean matches(Method method, Class<?> targetClass);
/**
* 判斷MethodMatcher是否是動態(tài)的够掠,如果是動態(tài)的該方法返回TRUE,將會調用3個參數(shù)的matches方法茄菊。
* 如果是靜態(tài)的疯潭,該方法返回FALSE,將會調用2個參數(shù)的matches方法面殖。
*/
boolean isRuntime();
/**
* 判斷是否匹配方法竖哩,動態(tài)的MethodMatcher調用
*
*/
boolean matches(Method method, Class<?> targetClass, Object... args);
/**
* 匹配所有方法的MethodMatcher實例
*/
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
根據(jù)是否需要捕捉目標方法執(zhí)行時的參數(shù),可以將MethodMatcher
分為動態(tài)和靜態(tài)兩種脊僚。在MethodMatcher
類型的基礎上相叁,Pointcut
可以分為兩類遵绰,即StaticMethodMatcherPointcut
和DynamicMethodMatcherPointcut
。因為StaticMethodMatcherPointcut
具有明顯的性能優(yōu)勢增淹,所以椿访,Spring為其提供了更多支持。
如果想要自定義PointCut我們可以根據(jù)實現(xiàn)需求 虑润,可以選擇繼承
StaticMethodMatcherPointcut
或者繼承DynamicMethodMatcherPointcut
Joinpoint和Pointcut的區(qū)別:這兩個概念差不多成玫,可以把他們當成一回事。一個Pointcut可以包含多個Joinpoint.
Advice(增強)
Spring中的Advice
實現(xiàn)全部基于AOP Alliance規(guī)定的接口拳喻。
按照增強(advice)在目標對象方法連接點的位置哭当,可以將增強分為以下五類:
- 前置增強:
org.springframework.aop.BeforeAdvice
,在目標方法執(zhí)行前執(zhí)行冗澈; - 后置增強:
org.springframework.aop.AfterReturningAdvice
钦勘,在目標方法調用后執(zhí)行; - 環(huán)繞增強:
org.aopalliance.intercept.MethodInterceptor
亚亲,截取目標類方法的執(zhí)行彻采,并在前后添加橫切邏輯; - 拋出異常增強:
org.springframework.aop.ThrowsAdvice
朵栖,目標方法拋出異常后執(zhí)行颊亮; - Introduction增強:
org.springframework.aop.introductioninterceptor
Spring AOP中的AfterReturningAdvice
只有在方法正常返回時才會執(zhí)行,且不能更改方法的返回值陨溅。所以要想實現(xiàn)類似資源清理的橫切工作终惑,無法使用AfterReturningAdvice
,而Spring AOP并沒有提供After Finally Advice门扇。如果要想實現(xiàn)資源清理的工作我們可以借助Around Advice雹有,它在Spring AOP的API編程實現(xiàn)中沒有對應的實現(xiàn)類,不過可以借助MethodInterceptor
來實現(xiàn)Around Advice臼寄。下面來看看如何定義一個Around Advice
/**
* 通過MethodInterceptor來實現(xiàn)Around Advice
*/
public class PerformanceMethodInterceptor implements MethodInterceptor{
private final Logger logger = LoggerFactory.getLogger(PerformanceMethodInterceptor.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
StopWatch stopWatch = new StopWatch();
try {
stopWatch.start();
return invocation.proceed();
} catch (Exception e){
// do nothing
} finally {
stopWatch.stop();
if (logger.isInfoEnabled()){
logger.info(stopWatch.toString());
}
}
return null;
}
}
異常拋出增強類的定義接口是ThrowsAdvice
霸奕,它是一個標志接口,內部沒有定義任何方法吉拳。不過我們在編寫ThrowsAdvice
的實現(xiàn)類時质帅,必須要定義如下方法:
/**
* 1. 方法名必須是afterThrowing
* 2. 前三個參數(shù)(method,args,target)是可選的,不過必須是要么同時存在留攒,要么同時不存在
* 3. 第四個參數(shù)必須存在煤惩,可以是Throwable或者其任何子類
* 4. 可以存在多個符合規(guī)則的afterThrowing,Spring會自動選擇最匹配的
*/
public void afterThrowing(Method method,Object[] args,Object target,Throwable t)
ThrowsAdvice
的實現(xiàn)如下:
public class MyThrowsAdvice implements ThrowsAdvice {
private Logger logger = LoggerFactory.getLogger(MyThrowsAdvice.class);
public void afterThrowing(Method method, Object[] args, Object target, Throwable t) {
logger.error("發(fā)送異常啦",t);
}
public void afterThrowing(RuntimeException t) {
logger.error("發(fā)生了運行時異常炼邀,異常信息:",t);
}
}
Introduction
除了常見的Advice之外魄揉,還有一種特殊的Advice--Introduction。Introduction可以在不改變目標類的情況下拭宁,為目標類添加新的屬性以及行為洛退。要想為目標對象添加新的屬性和行為瓣俯,必須要先聲明對應的接口和實現(xiàn)類,然后可以通過攔截器IntroductionInterceptor
實現(xiàn)添加兵怯。
下面來演示一下
DelegatingIntroductionInterceptor
的用法彩匕。
public class DelegatingIntroductionInterceptorSample {
public static void main(String[] args) {
IDancer dancer = new Dancer();
DelegatingIntroductionInterceptor interceptor = new DelegatingIntroductionInterceptor(dancer);
ProxyFactory weaver = new ProxyFactory(new Singer());
weaver.setInterfaces(new Class[]{IDancer.class,ISinger.class});
weaver.addAdvice(interceptor);
Object proxy = weaver.getProxy();
((IDancer)proxy).dance();
((ISinger)proxy).sing();
}
}
Aspect
我們知道@Aspect可以用來表示Aspect。不過在針對面向API編程的Spring AOP中摇零,Advisor
用來表示Spring中的Aspect推掸。Advisor
只能看成是一種特殊的Aspect
,因為在Advisor
中通常只持有一個Pointcut和一個Advice(實際的Aspect定義中可以有多個Pointcut和多個Advice)驻仅。
Advisor可以分為兩種:
- PointcutAdvisor
- IntroductionAdvisor
Spring-AOP 切點/切面類型和創(chuàng)建切面
織入
織入就是為了創(chuàng)建代理對象。當有了切入點和橫切邏輯(advice)之后噪服,如何在目標對象(或方法)中加入橫切邏輯呢毡泻?這個時候我們需要借助織入器將橫切邏輯織入目標對象當中。
在Spring AOP中粘优,根據(jù)一次創(chuàng)建代理的個數(shù)仇味,可以分為創(chuàng)建單個代理的織入器和創(chuàng)建多個代理的織入器(即自動代理)。
Spring AOP中創(chuàng)建單個代理的織入器的類有:
- ProxyFactory
- ProxyFactoryBean
- AspectJProxyFactory
在介紹織入相關的內容之前,我們先來看一下相關類的繼承圖嬉愧。理解了這張圖贩挣,織入的原理(其實差不多也就是Spring AOP的原理)便能了然于心。AspectJ使用ajc編譯器作為織入器雹顺;Jboss aop使用自定義的ClassLoader作為織入器丹墨。
ProxyFactory
ProxyFactory
的使用示例:
// 創(chuàng)建目標對象
IService target = new ServiceOne();
// 創(chuàng)建增強類對象(advice)
MethodInterceptor advice = new PerformanceMethodInterceptor();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvice(advice);
/* 或者
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
*/
IService proxy = (IService) proxyFactory.getProxy();
System.out.println(target.getClass());
System.out.println(proxy.getClass());
Spring AOP借助動態(tài)代理技術没酣,可以創(chuàng)建基于接口的代理王财;借助CGLIB,可以創(chuàng)建基于類的代理裕便。一般根據(jù)代理對象class的輸出System.out.println(proxy.getClass());
绒净,可以觀察出代理對象是通過什么技術生成的。
class $Proxy0 表示代理對象是通過動態(tài)代理生成的偿衰;
$$EnhancerByCGLIB$$9e62fc83 表示代理對象是通過cglib生成的挂疆;
默認情況下,Spring AOP會使用動態(tài)代理基于接口生成代理對象下翎,當出現(xiàn)下列情況會使用CGLIB基于類生成代理對象囱嫩。
- 目標類沒有實現(xiàn)任何接口;
- ProxyFactory的proxyTargetClass屬性值被設置為true漏设;
- ProxyFactory的optimize屬性值被設置為true。
ProxyFactoryBean
ProxyFactoryBean和ProxyFactory的使用沒有太大的區(qū)別今妄。一般ProxyFactory
是脫離IOC
容器來使用郑口,而ProxyFactoryBean
則與IOC容器結合使用鸳碧。
<!-- 目標對象 -->
<bean id="targetObject" class="cn.zgc.aop.apis.advice.advices.TargetObject"/>
<!-- 前置增強 -->
<bean id="myBeforeAdvice" class="cn.zgc.aop.apis.advice.advices.MyBeforeAdvice"/>
<!-- 后置增強 -->
<bean id="myAfterReturningAdvice" class="cn.zgc.aop.apis.advice.advices.MyAfterReturningAdvice"/>
<!-- 拋出異常增強 -->
<bean id="myThrowsAdvice" class="cn.zgc.aop.apis.advice.advices.MyThrowsAdvice"/>
<!-- 環(huán)繞增強 -->
<bean id="myAroundAdvice" class="cn.zgc.aop.apis.advice.advices.MyAroundAdvice"/>
<bean id="targetProxy" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyTargetClass="true"
p:target-ref="targetObject"
p:interceptorNames="myAroundAdvice,myBeforeAdvice,myAfterReturningAdvice"
/>
自動代理
Spring AOP為我們提供了自動代理機制,讓容器為我們自動生成代理犬性,這樣我們就不用針對每個目標對象若想生成代理對象瞻离,都需要配置相應的ProxyFactoryBean。在Spring內部乒裆,通過借助BeanPostProcessor完成自動代理這項工作套利。
BeanPostProcessor類可以在對象實例化前為其生成代理對象并返回,而不是實例化后的目標對象本身鹤耍,從而達到代理對象自動生成的目的
常用的自動代理類
- BeanNameAutoProxyCreator
- DefaultAdvisorAutoProxyCreator
- AnnotationAwareAspectJAutoProxyCreator
看看下面的類繼承圖肉迫,可以發(fā)現(xiàn)自動代理類都實現(xiàn)了BeanPostProcessor
BeanNameAutoProxyCreator
BeanNameAutoProxyCreator
通過beanNames
屬性指定目標對象集合,通過interceptorNames
屬性指定advice
稿黄。只要在IOC容器中注冊BeanNameAutoProxyCreator
喊衫,就能為目標對象創(chuàng)建出代理。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 通過Bean名稱自動創(chuàng)建代理 -->
<!-- 目標Bean -->
<bean id="serviceOne" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.ServiceOne"/>
<bean id="serviceTwo" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.ServiceTwo"/>
<!-- 增強 -->
<bean id="privilegeDetectionAdvice" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.PrivilegeDetectionAdvice"/>
<!-- 自動代理-->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>service*</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>privilegeDetectionAdvice</value>
</list>
</property>
</bean>
</beans>
DefaultAdvisorAutoProxyCreator
要想使用DefaultAdvisorAutoProxyCreator
的話杆怕,需要在配置文件中注冊(配置)DefaultAdvisorAutoProxyCreator
和相關Advisor
的bean族购,并且要想DefaultAdvisorAutoProxyCreator
的自動配置生效的話,必須得配置Advisor
陵珍,因為只有Advisor
當中才即包含Pointcut
又包含Advice
寝杖,有了這兩個信息之后,DefaultAdvisorAutoProxyCreator
就能通過Advisor
的信息自動為目標對象生成代理互纯。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 通過Advisor自動創(chuàng)建代理 -->
<!-- 目標Bean -->
<bean id="serviceOne" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.ServiceOne"/>
<bean id="serviceTwo" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.ServiceTwo"/>
<!-- 增強 -->
<bean id="privilegeDetectionAdvice" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.PrivilegeDetectionAdvice"/>
<!-- Aspect-->
<bean id="privilegeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
<bean class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>service*</value>
</list>
</property>
</bean>
</property>
<property name="advice" ref="privilegeDetectionAdvice"/>
</bean>
<!-- 自動代理 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
</beans>
AnnotationAwareAspectJAutoProxyCreator
AnnotationAwareAspectJAutoProxyCreator
可以自動將@AspectJ注解切面類織入目標Bean中瑟幕。注意,要想使用@AspectJ
需要導入aspectjweaver
的jar包伟姐。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 通過配置使用@AspectJ -->
<!-- 目標Bean -->
<bean id="service" class="cn.zgc.aop.apis.weave.autoCreateProxy.AnnotationAwareAspectJAutoProxyCreator.ServiceOne"/>
<!-- 使用了@AspectJ注解的切面類 -->
<bean class="cn.zgc.aop.apis.weave.autoCreateProxy.AnnotationAwareAspectJAutoProxyCreator.PrivilegeDetectionAspect"/>
<!-- 自動代理創(chuàng)建器收苏,自動將@AspectJ注解切面類織入目標Bean中 -->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator">
<!-- true:使用cglib生成代理對象,false(默認):使用動態(tài)代理生成代理對象 -->
<property name="proxyTargetClass" value="true"/>
</bean>
</beans>
建議
基于API編程的方式對于我們理解Spring AOP的原理很有幫助愤兵,我們應該對其要有所了解鹿霸,但是不推薦再使用這種方式了。
參考
《Spring揭秘 》.王福強