前面簡單介紹了Spring中AOP的使用,是基于XML配置。這次詳細介紹一下Spring中AOP的使用和實現(xiàn)。
0x00 回顧
package com.zing.aspect_oriented_test;
/**
* 方法切面
*/
public class SubjectService implements InterfaceSubjectService {
@Override
public void AspectMethod(String str) {
System.out.println("yo yo yo!\t" + str);
}
}
package com.zing.aspect_oriented_test;
/**
* 對切面的處理,之前與之后
*/public class AspectTarget {
public void beforeYouTalk() {
System.out.println("************beforeYouTalk, I know everything,so do not lie!");
}
private void afterYouTalk() {
System.out.println("************afterYouTalk, ha ha ha,good boy");
}
}
我們知道了切面和切面處理方法舵稠,接下來告訴Spring,切面位置和處理方法
在XML配置文件中將兩個bean配置到IoC容器中
<bean id="aspectService" class="com.zing.aspect_oriented_test.SubjectService"></bean>
<bean id="aspect" class="com.zing.aspect_oriented_test.AspectTarget"></bean>
再配置切面和切面方法
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* com.zing...*.*(..))"></aop:pointcut>
<aop:aspect ref="aspect">
<aop:before pointcut-ref="pointCut" method="beforeYouTalk"></aop:before>
<aop:after pointcut="execution(* com.zing..*.*(..))" method="afterYouTalk"></aop:after>
</aop:aspect>
</aop:config>
上面的配置可能不太明白,因為用的是AspectJ語法,* com.zing..*.*(..)
表示com.zing
包下的所有子包的類與方法
例子擼完驰弄,寫個Junit測試一下
package com.zing.aspect_oriented_test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by zing on 16/4/23.
*/
public class AspectJunitTest {
@Test
public void AopTest(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
InterfaceSubjectService subjectService = applicationContext.getBean("aspectService",InterfaceSubjectService.class);
subjectService.AspectMethod("Monster");
}
}
想必看完云里霧里的冕广,這里面用到了AOP的配置,完全沒有概念盈滴,接下來詳細解釋一下涯肩,配置的含義
0x01 Aspect切面
Aspect,是織入的節(jié)點巢钓,前面兩篇文章已經(jīng)介紹了代理病苗,而代理的節(jié)點,就是用Aspect來標記的症汹,因為Spring封裝了優(yōu)秀的AspectJ解決方案硫朦,Aspect作為一個既定的接口,被Spring擴展了多個具體類型的通知BeforeAdvice
,AfterAdvice
,ThrowsAdvice
從BeforeAdvice
中分析背镇,Spring中將前置接口設(shè)定為MethodBeforeAdvice
咬展,這個接口中只需要實現(xiàn)before
方法
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method var1, Object[] var2, Object var3) throws Throwable;
}
這個方法將會在目標方法執(zhí)行前被回調(diào)泽裳。
同樣AfterAdvice
是后置通知,具體的繼承有AfterReturningAdvice
public interface AfterReturningAdvice extends AfterAdvice {
void afterReturning(Object var1, Method var2, Object[] var3, Object var4) throws Throwable;
}
實現(xiàn)這個接口的afterReturning
方法破婆,在目標方法成功執(zhí)行并返回值之后涮总,AOP會回調(diào)afterReturning
方法
最后ThrowsAdvice
,其實是AfterAdvice
子接口祷舀,在目標方法執(zhí)行發(fā)生異常時瀑梗,會被回調(diào)。
具體可以這么實現(xiàn)
class BoomException implements ThrowsAdvice{
public void afterThrowing(IOException ioEx){
System.out.println("IO異常:"+ioEx.getMessage());
}
public void afterThrowing(ClassCastException ccEx){
System.out.println("轉(zhuǎn)換異常:"+ccEx.getMessage());
}
}
0x02 Pointcut切點
切點定義了一個代理接入位置裳扯,決定了通知作用的連接點抛丽。當然,也可以是一堆連接點嚎朽,一般用一個正則表達式標識铺纽。
public interface Pointcut {
Pointcut TRUE = TruePointcut.INSTANCE;
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
其中getMethodMatcher()
就是獲取一個方法過濾器,這個方法過濾器將符合標準的方法哟忍,作為切面的連接點狡门。
關(guān)于MethodMatcher,可以查看一下Spring源碼锅很。這里不做過多解釋其馏。
0x03 Advisor通知器
配置中并沒有寫Advisor,所以簡單介紹一下爆安,一個完整的模塊叛复,當要進行AOP編程時,需要將方法標記為切面扔仓,并定義了切面前置通知褐奥、后置通知、異常通知翘簇。定義完成撬码,需要通過通知器,將切面和通知綁定起來版保,這個通知器就是Advisor呜笑。
Advisor將Advice和Pointcut結(jié)合起來,通過IoC容器來配置AOP來使用彻犁。
0x04 ProxyFactoryBean
ProxyFactoryBean是Spring利用Java的代理模式或者CGLIB來實現(xiàn)Aop的一種方式叫胁,如何在XML中配置ProxyFactoryBean?
- 通知器Advisor使用Bean來配置汞幢。
- 織入方法類使用Bean配置
- 定義ProxyFactoryBean驼鹅,為這個bean配置幾個參數(shù):
- 目標:target
- 代理接口:proxyInterface
- 織入類:interceptName
如果不清楚可以看AOP和Spring中AOP的簡單介紹中的第0x03小節(jié)的例子。這里就扣下代碼,里面還有配置后置方法織入谤民,異常方法通知織入堰酿,不一一介紹。
<bean id="aspectService" class="com.zing.aspect_oriented_test.SubjectService"></bean>
<bean id="aspect" class="com.zing.aspect_oriented_test.AspectTarget"></bean>
<bean id="rocketProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="launchingControl"></property>
<property name="proxyInterfaces" value="com.zing.aoptest.IRocketLaunching"></property>
<property name="interceptorNames" value="beforeLaunch"></property>
<property name="proxyTargetClass" value="true"></property>
</bean>
因為ProxyFactoryBean是依靠Java或CGLIB的Proxy方式來獲取對象的张足,使用依靠代理的getObject()
方法來作為入口触创。使用接下來看一下這個方法的實現(xiàn)方式
public Object getObject() throws BeansException {
//初始化通知器
this.initializeAdvisorChain();
//區(qū)分單例模式和原始模式prototype
if(this.isSingleton()) {
return this.getSingletonInstance();
} else {
if(this.targetName == null) {
this.logger.warn("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the \'targetName\' property.");
}
return this.newPrototypeInstance();
}
}
具體可以追一追newPrototypeInstance()
和getSingletonInstance()
兩個方法,得到實現(xiàn)方式的完整過程为牍。留給感興趣的小伙伴哼绑。因為再往里挖就挖到CGLIB和JDK對象生成里去了,感覺刨過頭了碉咆。
0x05 Schema的AOP配置
前面啰嗦了一大堆抖韩,我覺得應(yīng)該介紹一下具體的配置方式
<!--aop定義開始-->
<aop:config>
<!--定義通知器-->
<aop:advisor ref="aspectSupportBean"></aop:advisor>
<!--定義切面 ref表示引用的bean-->
<aop:aspect ref="aspectSupportBean">
<!--定義切面增強位置-->
<aop:pointcut id="pcut" expression="execution(* cn.javass..*.*(..))" ></aop:pointcut>
<!--前置通知,下面的參數(shù)跟第一個類似-->
<aop:before pointcut="切入點表達式" pointcut-ref="切入點Bean引用" method="前置通知實現(xiàn)方法名" arg-names="前置通知實現(xiàn)方法參數(shù)列表參數(shù)名字"/>
<!--后置返回通知-->
<aop:after-returning></aop:after-returning>
<!--異常通知-->
<aop:after-throwing></aop:after-throwing>
<!--最終通知-->
<aop:after></aop:after>
<!--環(huán)繞通知-->
<aop:around></aop:around>
<!--引入定義-->
<aop:declare-parents types-matching="AspectJ語法類型表達式" implement-interface=引入的接口" default-impl="引入接口的默認實現(xiàn)" delegate-ref="引入接口的默認實現(xiàn)Bean引用"/>
</aop:aspect>
</aop:config>
如果你看完前面的東西應(yīng)該不難理解這些配置
但是有一個execution疫铜,為此查閱了一下文檔茂浮,切入點指示符。執(zhí)行表達式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
除了返回類型模式(上面代碼片斷中的ret-type-pattern)壳咕,名字模式和參數(shù)模式以外席揽, 所有的部分都是可選的。返回類型模式?jīng)Q定了方法的返回類型必須依次匹配一個連接點谓厘。 你會使用的最頻繁的返回類型模式是*
幌羞,它代表了匹配任意的返回類型。 一個全限定的類型名將只會匹配返回給定類型的方法竟稳。名字模式匹配的是方法名属桦。
你可以使用*
通配符作為所有或者部分命名模式。 參數(shù)模式稍微有點復(fù)雜:()
匹配了一個不接受任何參數(shù)的方法他爸, 而(..)
匹配了一個接受任意數(shù)量參數(shù)的方法(零或者更多)聂宾。 模式(*)
匹配了一個接受一個任何類型的參數(shù)的方法。 模式(*,String)
匹配了一個接受兩個參數(shù)的方法诊笤,第一個可以是任意類型亏吝, 第二個則必須是String類型。
0x06 @AspectJ的AOP
是基于注解的AOP盏混,默認Spring是不開啟的,需要再XML里添加一行配置
<aop:aspectj-autoproxy/>
之后便可以用注解的方式使用AOP了,我列舉一下
//配置切面
@Aspect()
Public class Aspect{
……
}
//配置織入方法
@Pointcut(value="切入點表達式", argNames = "參數(shù)名列表")
public void pointcutName(……) {}
//前置通知
@Before(value = "切入點表達式或命名切入點", argNames = "參數(shù)列表參數(shù)名")
//后置返回通知
@AfterReturning(
value="切入點表達式或命名切入點",
pointcut="切入點表達式或命名切入點",
argNames="參數(shù)列表參數(shù)名",
returning="返回值對應(yīng)參數(shù)名")
// value:指定切入點表達式或命名切入點惜论;
// pointcut:同樣是指定切入點表達式或命名切入點许赃,如果指定了將覆蓋value屬性指定的,pointcut具有高優(yōu)先級馆类;
// argNames:與Schema方式配置中的同義混聊;
// returning:與Schema方式配置中的同義。
//后置通知
@After (
value="切入點表達式或命名切入點",
argNames="參數(shù)列表參數(shù)名")
//value:指定切入點表達式或命名切入點乾巧;
// argNames:與Schema方式配置中的同義句喜;
//異常通知
@AfterThrowing (
value="切入點表達式或命名切入點",
pointcut="切入點表達式或命名切入點",
argNames="參數(shù)列表參數(shù)名",
throwing="異常對應(yīng)參數(shù)名")
//環(huán)繞通知
@Around (
value="切入點表達式或命名切入點",
argNames="參數(shù)列表參數(shù)名")
//引用
@DeclareParents(
value=" AspectJ語法類型表達式",
defaultImpl=引入接口的默認實現(xiàn)類)
private Interface MyInterface;
只是簡化了配置预愤,用起來跟Schema類似
參考
http://www.importnew.com/17795.html
http://www.importnew.com/17813.html
《Spring 內(nèi)幕技術(shù)》
轉(zhuǎn)載請注明出處:理清楚Spring的AOP到底怎么玩