spring框架——AspectJ動態代理實現AOP

1 前言

在 spring2.0 以上版本中,可以使用基於 AspectJ 註解配置 AOP,常用的配置 AOP 的註解如下:

  • @Aspect:標註一個類爲切面
  • @Order:標註切面優先級
  • @Pointcut:標註一個公共切入點
  • @Before:前置通知,作用於方法執行之前
  • @After:後置通知,作用於方法的finally語句塊,即不管方法有沒有異常都會執行,通常用於關閉資源
  • @AfterReturning:返回通知,作用於方法執行之後
  • @AfterThrowing:異常通知,作用於方法拋出異常時
  • @Around:環繞通知

注意:要想讓 IOC 容器管理切面,還需要給切面和被代理類標註 @Component,其用法見\to通過註解配置bean

需要導入的包如下:

其中,com 開頭的包爲 AspectJ 動態代理核心包,下載如下:

 com.springsource.net.sf.cglib-2.2.0com.springsource.org.aopalliance-1.0.0com.springsource.org.aspectj.weaver-1.7.2.RELEASE

2 案例

2.1 前置通知、後置通知、返回通知、異常通知

Comp.java

package com.compute;

public interface Comp {
	public int add(int a,int b);
	public int div(int a,int b);
}

CompImp.java

package com.compute;

import org.springframework.stereotype.Component;

@Component
public class CompImp implements Comp{

	@Override
	public int add(int a, int b) {
		return a+b;
	}

	@Override
	public int div(int a, int b) {
		return a/b;
	}
}

注意:CompImp類前加 @Component 註解,是爲了將 bean 交給 IOC 容器管理

Logger.java

package com.compute;

import java.util.Arrays;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class Logger {	
	
	//前置通知:作用於方法執行之前
	@Before(value="execution(* com.compute.*.*(..))")  //對com.compute包下的所有類的所有方法起作用
	public void beforeMethod(JoinPoint joinPoint) {
		Object[] args=joinPoint.getArgs(); //獲取方法參數
		String methodName=joinPoint.getSignature().getName(); //獲取方法名
		System.out.println("前置通知:method:"+methodName+", args:"+Arrays.toString(args));
	}
	
	//後置通知:作用於方法的finally語句塊,即不管方法有沒有異常都會執行,通常用於關閉資源
	@After(value="execution(* com.compute.*.*(..))")  //對com.compute包下的所有類的所有方法起作用
	public void afterMethod() {
		System.out.println("後置通知");
	}
	
	//返回通知:作用於方法執行之後
	@AfterReturning(value="execution(* com.compute.*.*(..))",returning="result")  //此處的result和下面方法的result必須同名
	public void afterReturning(JoinPoint joinPoint,Object result) {
		String methodName=joinPoint.getSignature().getName();
		System.out.println("返回通知:method:"+methodName+", result:"+result);
	}
	
	//異常通知:作用於方法拋出異常時
	@AfterThrowing(value="execution(* com.compute.*.*(..))",throwing="e")  //此處e和下面方法的e必須同名
	public void afterThrowing(Exception e) {
		System.out.println("異常通知:"+e);
	}
}

注意:Logger類前加 @Aspect 註解,是爲了標註此類爲切面;@Before、@After、@AfterReturning、@AfterThrowing中的 value 值用於指定該通知作用於具體類的具體方法,如果只需給 CompImp 類的 add 方法添加通知,如下:

@Before(value="execution(public int com.compute.CompImp.add(int, int))") //只對CompImp類的add方法起作用

comp.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:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
	<context:component-scan base-package="com.compute"></context:component-scan>
	<aop:aspectj-autoproxy />
</beans>

 注意:需要導入 context 和 aop 命名空間,context:component-scan 用於掃描組件,即對標有 @Compnent 的類生成 bean 交給 IOC 容器管理;aop:aspectj-autoproxy 用於開啓AspectJ的自動代理功能。

Test.java

package com.compute;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
	public static void main(String[] args) {
		ApplicationContext ac=new ClassPathXmlApplicationContext("comp.xml");
		Comp comp=ac.getBean("compImp",Comp.class);
		System.out.println(comp.getClass().getName()); //打印代理類的類名
		int result=comp.add(1,2);
		System.out.println(result);
		result=comp.div(2,0);
		System.out.println(result);
	}
}

運行結果:

com.sun.proxy.$Proxy11
前置通知:method:add, args:[1, 2]
後置通知
返回通知:method:add, result:3
3
前置通知:method:div, args:[2, 0]
後置通知
異常通知:java.lang.ArithmeticException: / by zero

拓展延伸

當有很多通知作用於同一切入點時,可以通過 @Pointcut 標註公共切入點,如下:

Logger.java

package com.compute;

import java.util.Arrays;
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.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class Logger {
	
	@Pointcut(value="execution(* com.compute.*.*(..))")   //對com.compute包下的所有類的所有方法起作用
	public void pointcut() {} //方法命名可以隨意
	
	//前置通知:作用於方法執行之前
	@Before(value="pointcut()")
	public void beforeMethod(JoinPoint joinPoint) {
		Object[] args=joinPoint.getArgs();
		String methodName=joinPoint.getSignature().getName();
		System.out.println("前置通知:method:"+methodName+", args:"+Arrays.toString(args));
	}
	
	//後置通知:作用於方法的finally語句塊,即不管方法有沒有異常都會執行,通常用於關閉資源
	@After(value="pointcut()")
	public void afterMethod() {
		System.out.println("後置通知");
	}
	
	//返回通知:作用於方法執行之後
	@AfterReturning(value="pointcut()",returning="result")
	public void afterReturning(JoinPoint joinPoint,Object result) {
		String methodName=joinPoint.getSignature().getName();
		System.out.println("返回通知:method:"+methodName+", result:"+result);
	}
	
	//異常通知:作用於方法拋出異常時
	@AfterThrowing(value="pointcut()",throwing="e")
	public void afterThrowing(Exception e) {
		System.out.println("異常通知:"+e);
	}
}

2.2 環繞通知

本節僅介紹 Logger.java 其他類和配置文件見2.1節

Logger.java

package com.compute;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class Logger {

	//環繞通知:作用於方法拋出異常時
	@Around(value="execution(* com.compute.*.*(..))")  //對com.compute包下的所有類的所有方法起作用
	public Object aroundMethod(ProceedingJoinPoint joinPoint) {
		Object result=null;
		try {
			System.out.println("前置通知");
			result=joinPoint.proceed(); //執行方法
			System.out.println("返回通知");
			return result;
		} catch (Throwable e) {
			System.out.println("異常通知");
			e.printStackTrace();
		}finally {
			System.out.println("後置通知");
		}
		return -1;
	}
}

運行結果:

com.sun.proxy.$Proxy7
前置通知
返回通知
後置通知
3
前置通知
異常通知
java.lang.ArithmeticException: / by zero
	at com.compute.CompImp.div(CompImp.java:15)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
	at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85)
	at com.compute.Logger.aroundMethod(Logger.java:55)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
	at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:68)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
	at com.sun.proxy.$Proxy7.div(Unknown Source)
	at com.compute.Test.main(Test.java:13)
後置通知
-1

 2.3 切面優先級

當有多個切面時,可以通過 @Order 註解定義切面的優先級。

本節僅介紹LoggerA.java、LoggerB.java以及Test.java,Comp.java、CompImp.java、comp.xml見2.1節。

LoggerA.java

package com.compute;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Order(2) //值越小,優先級越高,默認值爲2147483647(2^31-1)
public class LoggerA {
	
	@Before(value="execution(* com.compute.*.*(..))")
	public void beforeMethod() {
		System.out.println("前置通知:LoggerA");
	}
}

LoggerB.java

package com.compute;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Order(1) //值越小,優先級越高,默認值爲2147483647(2^31-1)
public class LoggerB {
	
	@Before(value="execution(* com.compute.*.*(..))")
	public void beforeMethod() {
		System.out.println("前置通知:LoggerB");
	}
}

Test.java

package com.compute;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
	public static void main(String[] args) {
		ApplicationContext ac=new ClassPathXmlApplicationContext("comp.xml");
		Comp comp=ac.getBean("compImp",Comp.class);
		int result=comp.add(1,2);
		System.out.println(result);
	}
}

運行結果:

前置通知:LoggerB
前置通知:LoggerA
3

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章