概述
在學(xué)習(xí)spring aop過(guò)程中呀打,發(fā)現(xiàn)有個(gè)怎么都繞不過(guò)去的坎肪虎,就是AspectJ的使用。少了這一部分哮幢,一些spring aop的源碼總覺(jué)得少了點(diǎn)什么带膀,看不大懂。所以接下來(lái)會(huì)寫(xiě)幾篇關(guān)于AspectJ的入門(mén)使用橙垢。
安裝示例代碼
1垛叨、首先,下載最新的穩(wěn)定版本(我自己下的是AspectJ 1.9.2, Released 24 Oct 2018 版本)aspectj-1.9.2.jar
http://www.eclipse.org/aspectj/downloads.php#stable_release
2柜某、安裝
aspectJ的安裝很簡(jiǎn)單嗽元,運(yùn)行 java -jar aspectj-1.9.2.jar 然后選下jdk路徑及最終aspectj要安裝(AspecJ安裝路徑)到哪里就好
3敛纲、安裝最后,會(huì)提示你要配置環(huán)境變量了
按照環(huán)境配下PATH變量 和 CLASSPATH變量就好
export PATH=${JAVA_HOME}/bin:$PATH:/Users/hdj/software/aspectj/bin
export CLASSPATH=${CLASSPATH}:/Users/hdj/software/aspectj/lib/aspectjrt.jar
4还棱、編譯代碼示例
進(jìn)入 ${aspectJ 安裝路徑}/doc/examples
執(zhí)行 ajc -argfile telecom/billing.lst
5载慈、運(yùn)行示例
java telecom.BillingSimulation
6惭等、運(yùn)行結(jié)果
示例總結(jié)
簡(jiǎn)單總結(jié)下:
1)安裝配置aspectJ環(huán)境
2)使用 ajc -argfile 編譯類
3)運(yùn)行編譯后的字節(jié)碼
上述的例子是aspectJ 官方的例子 珍手,為了說(shuō)明如何使用aspectJ的使用,我們看另一個(gè)例子
另一個(gè)例子
知道如何運(yùn)行aspectJ給的官方示例后辞做,我們繼續(xù)學(xué)習(xí)官網(wǎng)給的另一個(gè)例子琳要,例子路徑
${aspectJ 路徑}/doc/examples/tjp/Demo.java
我們先來(lái)看看原始類
public class Demo {
static Demo d;
public static void main(String[] args){
new Demo().go();
}
void go(){
d = new Demo();
//調(diào)用foo方法
d.foo(1,d);
//調(diào)用bar方法
System.out.println(d.bar(new Integer(3)));
}
void foo(int i, Object o){
//打印foo
System.out.println("Demo.foo(" + i + ", " + o + ")\n");
}
String bar (Integer j){
System.out.println("Demo.bar(" + j + ")\n");
return "Demo.bar(" + j + ")";
}
}
再來(lái)看看一個(gè)隨著Demo.java一起被 ajc編譯的特殊的類GetInfo.java
:
package tjp;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.CodeSignature;
aspect GetInfo {
static final void println(String s){ System.out.println(s); }
pointcut goCut(): cflow(this(Demo) && execution(void go()));
pointcut demoExecs(): within(Demo) && execution(* *(..));
Object around(): demoExecs() && !execution(* go()) && goCut() {
println("Intercepted message: " +
thisJoinPointStaticPart.getSignature().getName());
println("in class: " +
thisJoinPointStaticPart.getSignature().getDeclaringType().getName());
printParameters(thisJoinPoint);
println("Running original method: \n" );
Object result = proceed();
println(" result: " + result );
return result;
}
// 打印參數(shù)
static private void printParameters(JoinPoint jp) {
println("Arguments: " );
Object[] args = jp.getArgs();
String[] names = ((CodeSignature)jp.getSignature()).getParameterNames();
Class[] types = ((CodeSignature)jp.getSignature()).getParameterTypes();
for (int i = 0; i < args.length; i++) {
println(" " + i + ". " + names[i] +
" : " + types[i].getName() +
" = " + args[i]);
}
}
}
這里和spring aop的使用很多方面非常類似,最后執(zhí)行的結(jié)果如下:
這里說(shuō)明一下幾點(diǎn):
GetInfo.java 含有的特殊關(guān)鍵詞
GetInfo.java含有很多特殊的關(guān)鍵詞秤茅,這些關(guān)鍵詞java是無(wú)法識(shí)別的稚补,需要通過(guò)ajc編譯才能織入到字節(jié)碼中
切入點(diǎn)表達(dá)式
spring的切入點(diǎn)表達(dá)式我們非常熟悉,使用aspectJ時(shí)框喳,也需要配置切入點(diǎn)课幕,需要配置有哪些方法需要被切入
//表示Demo類的go方法調(diào)用過(guò)程中的方法需要被增強(qiáng)
pointcut goCut(): cflow(this(Demo) && execution(void go()));
//每一個(gè)Demo類定義的方法
pointcut demoExecs(): within(Demo) && execution(* *(..));
// 由于 goCut() 這個(gè)切入點(diǎn)還包含go()方法自己的調(diào)用,因此需要把自己給排掉
Object around(): demoExecs() && !execution(* go()) && goCut();
可能會(huì)問(wèn)五垮,既然為啥要同時(shí)配置
demoExecs() 以及 !execution(* go()) && goCut() 單獨(dú)有一個(gè)不夠么乍惊?
1)demoExecs()表示攔截Demo類的所有方法,如果不加后面的限制放仗,會(huì)同時(shí)攔截main方法润绎,以及go()方法
2)如果只有!execution(* go()) && goCut(),直接編譯時(shí)候會(huì)報(bào)錯(cuò)(warning)執(zhí)行后直接會(huì)報(bào)java.lang.StackOverflowError
3)如果只有!execution(* go()) && goCut()诞挨,可以執(zhí)行莉撇,但是只會(huì)攔截go方法
thisJoinPointStaticPart 和 thisJoinPoint
thisJoinPoint 包含了關(guān)于當(dāng)前切入點(diǎn)的一些信息(通過(guò)反射獲取的)
thisJoinPointStaticPart 包含一些靜態(tài)信息,參考官方文檔
通過(guò)配置切入點(diǎn)惶傻,我們實(shí)現(xiàn)了不改變Demo.java源碼的前提下棍郎,往Demo.java方法的前后插入了一段代碼。
與Spring 切面寫(xiě)法的對(duì)比
對(duì)比下之前在學(xué)習(xí)Spring時(shí)候银室,配置的切面
//聲明這是一個(gè)組件
@Component
//聲明這是一個(gè)切面Bean
@Aspect
@Slf4j
public class ServiceAspect {
//配置切入點(diǎn),該方法無(wú)方法體,主要為方便同類中其他方法使用此處配置的切入點(diǎn)
@Pointcut("execution(* com.hdj.learn.spring.aop.service..*(..))")
public void aspect() {
}
/*
* 配置前置通知,使用在方法aspect()上注冊(cè)的切入點(diǎn)
* 同時(shí)接受JoinPoint切入點(diǎn)對(duì)象,可以沒(méi)有該參數(shù)
*/
@Before("aspect()")
public void before(JoinPoint joinPoint) {
log.info("before " + joinPoint);
}
//配置后置通知,使用在方法aspect()上注冊(cè)的切入點(diǎn)
@After("aspect()")
public void after(JoinPoint joinPoint) {
log.info("after " + joinPoint);
}
//配置環(huán)繞通知,使用在方法aspect()上注冊(cè)的切入點(diǎn)
@Around("aspect()")
public void around(JoinPoint joinPoint) {
long start = System.currentTimeMillis();
try {
((ProceedingJoinPoint) joinPoint).proceed();
long end = System.currentTimeMillis();
log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
} catch (Throwable e) {
long end = System.currentTimeMillis();
log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage());
}
}
//配置后置返回通知,使用在方法aspect()上注冊(cè)的切入點(diǎn)
@AfterReturning("aspect()")
public void afterReturn(JoinPoint joinPoint) {
log.info("afterReturn " + joinPoint);
}
//配置拋出異常后通知,使用在方法aspect()上注冊(cè)的切入點(diǎn)
@AfterThrowing(pointcut = "aspect()", throwing = "ex")
public void afterThrow(JoinPoint joinPoint, Exception ex) {
log.info("afterThrow " + joinPoint + "\t" + ex.getMessage());
}
}
其實(shí)非常類似坝撑,有切面、有通知粮揉、有目標(biāo)類等等巡李,切入點(diǎn)的表達(dá)式也非常類似。
總結(jié)下
初步了解了aspectJ的使用扶认,我們可以了解以下幾點(diǎn):
1)aspectJ的使用是在編譯期侨拦,通過(guò)特殊的編譯器可以在不改變代碼的前提下織入代碼(當(dāng)然能不能在運(yùn)行期
,我還沒(méi)有確認(rèn))
2)aspectJ的使用辐宾,也是配置切入點(diǎn)狱从、通知
問(wèn)題
到了這里基本了解了aspectJ的使用膨蛮,但是還有幾個(gè)問(wèn)題。
1)我們?cè)趕pring中并沒(méi)有看到需要aspectj之類的關(guān)鍵詞季研,而是使用java代碼就可以了敞葛,這是如何做到的
2)同樣,我們也沒(méi)有使用特殊的編譯器
3)Spring源碼中與aspectJ 相關(guān)的AjType究竟是啥与涡?
這些問(wèn)題惹谐,我們會(huì)在下一篇博客里解決
10月份加班比較多,耽擱了寫(xiě)博客驼卖,這周開(kāi)始回復(fù)更新氨肌,會(huì)盡量補(bǔ)上上個(gè)月的。= = 雖然沒(méi)啥人看哈哈哈酌畜。