AspectJ和Spring與AOP的關係
前面介紹過AOP面向切面程式設計是一種程式設計思想,是對OOP面向物件程式設計的一種補充。對於AOP這種程式設計思想,很多框架都進行了實現。Spring就是其中之一,可以完成面向切面程式設計。而AspectJ也實現了AOP的功能,且其實現方式更為簡捷,使用更為方便,而且還支援註解式開發。所以,Spring又將AspectJ的對於AOP的實現也引入到了自己的框架中。後面使用AOP程式設計都是在Spring環境下使用AspectJ來進行的。
AspectJ的五種常用通知型別:
(1)前置通知
(2)後置通知
(3)環繞通知
(4)異常通知
(5)最終通知
其中最終通知是指,無論程式執行是否正常,該通知都會執行。類似於try...catch中的finally程式碼塊。
AspectJ的切入點表示式
AspectJ定義了專門的表示式用於指定切入點。表示式的原型如下:
execution([modifiers-pattern] 訪問許可權型別
ret-type-pattern 返回值型別
[declaring-type-pattern] 全限定性類名
name-pattern(param-pattern) 方法名(引數名)
[throws-pattern] 丟擲異常型別
)
切入點表示式要匹配的物件就是目標方法的方法名。表示式中加[]的部分表示可省略的部分,紅色部分表示不可省略的部分,各部分之間用空格分開。在其中可以使用一下符號:
舉幾個例子:
execution(public * *(..)) 指定切入點為:任意公共方法
execution(* *.service.*.*(..)) 指定只有一級包下的service子包下所有類(介面)中所有方法為切入點
execution(* *..service.*.*(..)) 指定所有包下的service子包下所有類(介面)中所有方法為切入點
execution(* *.SomeService.*(..)) 指定只有一級包下的SomeService類(介面)中所有方法為切入點
execution(* *..SomeService.*(..)) 指定所有包下的SomeService類(介面)中所有方法為切入點
AspectJ的開發環境
引入AOP聯盟的jar包,這是AOP的規範
引入AspectJ的jar包,這是對上面AOP規範的實現
引入整合Spring和AspectJ的jar包
引入Spring的AOP jar包,因為上面Spring和AspectJ的整合jar包用到了Spring的AOP jar包
注意:這裡只列舉了需要額外引入的jar包,還需要Spring的四個基礎包和日誌包
基於註解的實現:
前置通知:
首先定義好要增強的介面和實現類
package com.hnu.service;
public interface SomeService {
void doFirst();
String doSecond();
void doThird();
}
package com.hnu.service;
public class SomeServiceImpl implements SomeService {
@Override
public void doFirst() {
System.out.println("執行doFirst()方法");
}
@Override
public String doSecond() {
System.out.println("執行doSecond()方法");
return "abcde";
}
@Override
public void doThird() {
System.out.println("執行doThird()方法");
}
}
編寫切面類(是一個POJO類)
package com.hnu.service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect //表示當前類是切面
public class MyAspect {
//@Before表示這是一個後置通知 execution表示匹配的連線點
@Before("execution(* *..SomeService.doFirst(..))")
public void myBefore(){
System.out.println("執行前置通知方法");
}
@Before("execution(* *..SomeService.doFirst(..))")
public void myBefore(JoinPoint jp){ //JoinPoint表示連線點
System.out.println("執行前置通知方法 jp = " + jp);
}
}
配置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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 註冊切面 -->
<bean id="myAspect" class="com.hnu.service.MyAspect"/>
<!-- 註冊目標物件 -->
<bean id="someService" class="com.hnu.service.SomeServiceImpl"/>
<!-- 註冊AspectJ的自動代理 ,使用到了AOP約束-->
<aop:aspectj-autoproxy/>
</beans>
測試類
package com.hnu.service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
//建立容器物件,載入Spring配置檔案
String resource = "com/hnu/service/applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
SomeService service = (SomeService) ac.getBean("someService");
service.doFirst();
System.out.println("--------------------------");
service.doSecond();
System.out.println("--------------------------");
service.doThird();
}
}
執行結果:
後置通知,環繞通知,異常通知的使用和前置通知相同,唯一需要改變的就是切面類,這裡就只列出切面類的寫法
package com.hnu.service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect //表示當前類是切面
public class MyAspect {
@Before("execution(* *..SomeService.doFirst(..))")
public void myBefore(){
System.out.println("執行前置通知方法");
}
@Before("execution(* *..SomeService.doFirst(..))")
public void myBefore(JoinPoint jp){
System.out.println("執行前置通知方法 jp = " + jp);
}
@AfterReturning("execution(* *..SomeService.doSecond(..))")
public void myAfterReturning(){
System.out.println("執行後置通知方法");
}
//注意這裡雖然能獲取方法返回值,但是不能修改
@AfterReturning(value="execution(* *..SomeService.doSecond(..))",returning="result")
public void myAfterReturning(Object result){
System.out.println("執行後置通知方法 result= " + result);
}
@Around("execution(* *..SomeService.doSecond(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("執行環繞通知方法,目標方法執行之前");
Object result = pjp.proceed();
System.out.println("執行環繞通知方法,目標方法執行之後");
if(result != null){ //可以修改目標方法的返回結果
result = ((String)result).toUpperCase();
}
return result;
}
@AfterThrowing("execution(* *..SomeService.doThird(..))")
public void myAfterThrowing(){
System.out.println("執行異常通知方法");
}
@AfterThrowing(value="execution(* *..SomeService.doThird(..))",throwing="ex")
public void myAfterThrowing(Exception ex){
System.out.println("執行異常通知方法 ex = " + ex.getMessage());
}
@After("execution(* *..SomeService.doThird(..))")
public void myAfter(){
System.out.println("執行最終通知方法");
}
}
定義切入點
通過上面的程式碼我們可以看到名稱表示式execution()有大量冗餘,可以通過定義切入點解決。
package com.hnu.service;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect //表示當前類是切面
public class MyAspect {
@After("doThirdPointcut()") //第二部:直接使用這個切入點
public void myAfter(){
System.out.println("執行最終通知方法");
}
//第一步:定義了一個切入點,叫doThirdPointcut()
@Pointcut("execution(* *..SomeService.doThird(..))")
public void doThirdPointcut(){}
}
基於XML的實現:
編寫切面類(是一個POJO類),把上面的切面類刪除所有的註解即可
package com.hnu.service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
public void myBefore(){
System.out.println("執行前置通知方法");
}
public void myBefore(JoinPoint jp){
System.out.println("執行前置通知方法 jp = " + jp);
}
public void myAfterReturning(){
System.out.println("執行後置通知方法");
}
//注意這裡雖然能獲取方法返回值,但是不能修改
public void myAfterReturning(Object result){
System.out.println("執行後置通知方法 result= " + result);
}
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("執行環繞通知方法,目標方法執行之前");
Object result = pjp.proceed();
System.out.println("執行環繞通知方法,目標方法執行之後");
if(result != null){ //可以修改目標方法的返回結果
result = ((String)result).toUpperCase();
}
return result;
}
public void myAfterThrowing(){
System.out.println("執行異常通知方法");
}
public void myAfterThrowing(Exception ex){
System.out.println("執行異常通知方法 ex = " + ex.getMessage());
}
public void myAfter(){
System.out.println("執行最終通知方法");
}
}
下面是關鍵的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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 註冊切面 -->
<bean id="myAspect" class="com.hnu.service.MyAspect"/>
<!-- 註冊目標物件 -->
<bean id="someService" class="com.hnu.service.SomeServiceImpl"/>
<!-- AOP配置 -->
<aop:config>
<aop:pointcut expression="execution(* *..SomeService.doFirst())" id="doFirstPointcut"/>
<aop:pointcut expression="execution(* *..SomeService.doSecond())" id="doSecondPointcut"/>
<aop:pointcut expression="execution(* *..SomeService.doThird())" id="doThirdPointcut"/>
<aop:aspect ref="myAspect">
<aop:before method="myBefore" pointcut="execution(* *..SomeService.doFirst())"/> <!-- 不使用自定義切入點寫法 -->
<aop:before method="myBefore(org.aspectj.lang.JoinPoint)" pointcut-ref="doFirstPointcut"/>
<aop:after-returning method="myAfterReturning" pointcut-ref="doSecondPointcut"/>
<aop:after-returning method="myAfterReturning(java.lang.Object)" pointcut-ref="doSecondPointcut" returning="result"/>
<aop:around method="myAround" pointcut-ref="doSecondPointcut"/>
<aop:after-throwing method="myAfterThrowing" pointcut-ref="doThirdPointcut"/>
<aop:after-throwing method="myAfterThrowing(java.lang.Exception)" pointcut-ref="doThirdPointcut" throwing="ex"/>
<aop:after method="myAfter" pointcut-ref="doThirdPointcut"/>
</aop:aspect>
</aop:config>
</beans>