1. 程式人生 > >AspectJ和Spring與AOP的關係

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>