用 AspectJ 注解声明切面:
- 要在 Spring 中声明 AspectJ 切面 , 只需要在 IOC 容器中将切面声明为 bean 实例。当在 Spring IOC 容器中初始化 AsjectJ 切面之后 , Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 bean 创建代理。
- 在 ApectJ 注解中 , 切面只是一个带有 @Asject 注解的 Java 类。
- 通知是标注有某种注解的简单的 Java 方法。
- AspectJ 支持 5 种类型的通知注解:
- @Before:前置通知 , 在方法执行之前执行。
- @After:后置通知 , 在方法执行之后执行。
- @AfterReturning:返回通知 , 在方法返回结果之后执行。
- @AfterThrowing:异常通知 , 在方法抛出异常之后。
- @Around:环绕通知 , 围绕着方法执行。
前置通知:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:aop="http://www.springframework.org/schema/aop" 5 xmlns:context="http://www.springframework.org/schema/context" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd 8 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> 9 10 <!-- IOC 扫描包 --> 11 <context:component-scan base-package="com.itdoc.spring.aop.impl"></context:component-scan> 12 <!-- 使 AspectJ 注解起作用, 自动为匹配的类生成代理对象 --> 13 <aop:aspectj-autoproxy></aop:aspectj-autoproxy> 14 15 </beans>
注意:一定要添加 aop 命名空间。
1 package com.itdoc.spring.aop.impl; 2 3 import org.springframework.stereotype.Component; 4 5 /** 6 * http://www.cnblogs.com/goodcheap 7 * 8 * @author: Wáng Chéng Dá 9 * @create: 2017-03-03 19:34 10 */ 11 @Component 12 public interface Arithmetic { 13 14 int add(int i, int j); 15 16 int sub(int i, int j); 17 18 int mul(int i, int j); 19 20 int div(int i, int j); 21 22 }
1 package com.itdoc.spring.aop.impl; 2 3 import org.springframework.stereotype.Component; 4 5 /** 6 * http://www.cnblogs.com/goodcheap 7 * 8 * @author: Wáng Chéng Dá 9 * @create: 2017-03-03 19:35 10 */ 11 @Component("arithmetic") 12 public class ArithmeticImpl implements Arithmetic { 13 @Override 14 public int add(int i, int j) { 15 int result = i + j; 16 return result; 17 } 18 19 @Override 20 public int sub(int i, int j) { 21 int result = i - j; 22 return result; 23 } 24 25 @Override 26 public int mul(int i, int j) { 27 int result = i * j; 28 return result; 29 } 30 31 @Override 32 public int div(int i, int j) { 33 int result = i / j; 34 return result; 35 } 36 }
1 package com.itdoc.spring.aop.impl; 2 3 4 import org.aspectj.lang.JoinPoint; 5 import org.aspectj.lang.annotation.Aspect; 6 import org.aspectj.lang.annotation.Before; 7 import org.springframework.stereotype.Component; 8 9 import java.util.Arrays; 10 11 /** 12 * http://www.cnblogs.com/goodcheap 13 * 声明为一个切面的类: 需要把该类放到 IOC 容器中, 再声明为一个切面。 14 * @author: Wáng Chéng Dá 15 * @create: 2017-03-03 21:37 16 */ 17 @Aspect 18 @Component 19 public class LoggingAspect { 20 21 //声明该方法为前置通知: 在目标方法执行之前开始执行。 22 @Before("execution(* com.itdoc.spring.aop.impl.*.*(..))") 23 public void beforeMethod(JoinPoint joinPoint) { 24 Object methodName = joinPoint.getSignature().getName(); 25 Object args = Arrays.asList(joinPoint.getArgs()); 26 System.out.println("The method " + methodName + " begins with " + args); 27 } 28 }
@Before("execution(* com.itdoc.spring.aop.impl.*.*(..))")
- 第一颗 * :代表任意修饰符 , 任意返回值。
- 第二颗 * :代表任意对象。
- 第三颗 * :代表任意方法。
- 最后的 .. :代表任意参数。
注意:在 AspectJ 中 , 切点表达式可以通过操作符 && , || , ! 结合起来。
1 package com.itdoc.spring.aop.impl; 2 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 6 /** 7 * http://www.cnblogs.com/goodcheap 8 * 9 * @author: Wáng Chéng Dá 10 * @create: 2017-03-03 21:32 11 */ 12 public class Main { 13 14 public static void main(String[] args) { 15 16 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); 17 Arithmetic arithmetic = (Arithmetic) ctx.getBean("arithmetic"); 18 System.out.println("result = " + arithmetic.add(9, 5)); 19 System.out.println("result = " + arithmetic.sub(9, 5)); 20 } 21 }
控制台输出:
The method add begins with [9, 5] |
后置通知:
- 后置通知是在连接点完成之后执行的 , 即连接点返回结果或者抛出异常的时候。
- 一个切面可以包括一个或者多个通知。
1 package com.itdoc.spring.aop.impl; 2 3 4 import org.aspectj.lang.JoinPoint; 5 import org.aspectj.lang.annotation.After; 6 import org.aspectj.lang.annotation.Aspect; 7 import org.aspectj.lang.annotation.Before; 8 import org.springframework.stereotype.Component; 9 10 import java.util.Arrays; 11 12 /** 13 * http://www.cnblogs.com/goodcheap 14 * 声明为一个切面的类: 需要把该类放到 IOC 容器中, 再声明为一个切面。 15 * @author: Wáng Chéng Dá 16 * @create: 2017-03-03 21:37 17 */ 18 @Aspect 19 @Component 20 public class LoggingAspect { 21 22 //声明该方法为前置通知: 在目标方法执行之前开始执行。 23 @Before("execution(* com.itdoc.spring.aop.impl.*.*(..))") 24 public void beforeMethod(JoinPoint joinPoint) { 25 Object methodName = joinPoint.getSignature().getName(); 26 Object args = Arrays.asList(joinPoint.getArgs()); 27 System.out.println("The method " + methodName + " begins with " + args); 28 } 29 30 //声明该方法为后置通知: 在目标方法执行之后(无论是否发生异常)开始执行。 31 @After("execution(* com.itdoc.spring.aop.impl.*.*(..))") 32 public void afterMethod(JoinPoint joinPoint) { 33 Object methodName = joinPoint.getSignature().getName(); 34 Object args = Arrays.asList(joinPoint.getArgs()); 35 System.out.println("The method " + methodName + " ends with " + args); 36 } 37 }
1 package com.itdoc.spring.aop.impl; 2 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 6 /** 7 * http://www.cnblogs.com/goodcheap 8 * 9 * @author: Wáng Chéng Dá 10 * @create: 2017-03-03 21:32 11 */ 12 public class Main { 13 14 public static void main(String[] args) { 15 16 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); 17 Arithmetic arithmetic = (Arithmetic) ctx.getBean("arithmetic"); 18 System.out.println("result = " + arithmetic.add(9, 5)); 19 System.out.println("result = " + arithmetic.div(9, 0)); 20 } 21 }
控制台输出:
Exception in thread "main" java.lang.ArithmeticException: / by zero |
返回通知:
无论连接点是正常返回还是抛出异常 , 后置通知都会执行。如果只想在连接点返回的时候记录日志 , 应使用返回通知代替后置通知。
1 package com.itdoc.spring.aop.circular; 2 3 import org.aspectj.lang.JoinPoint; 4 import org.aspectj.lang.annotation.AfterReturning; 5 import org.aspectj.lang.annotation.Aspect; 6 import org.springframework.stereotype.Component; 7 8 /** 9 * 通知 10 * http://www.cnblogs.com/goodcheap 11 * 12 * @author: Wáng Chéng Dá 13 * @create: 2017-03-04 9:50 14 */ 15 @Aspect 16 @Component 17 public class AsjectLogging { 18 19 /** 20 * 在方法正常结束后执行代码。 21 * 返回通知是可以访问到方法的返回值的。 22 */ 23 @AfterReturning(value = "execution(* com.itdoc.spring.aop.circular.*.*(..))", returning = "result") 24 public void afterReturning(JoinPoint joinPoint, Object result) { 25 Object methodName = joinPoint.getSignature().getName(); 26 System.out.println("The method " + methodName + " ends with " + result); 27 } 28 }
异常通知:
1 package com.itdoc.spring.aop.circular; 2 3 import org.aspectj.lang.JoinPoint; 4 import org.aspectj.lang.annotation.AfterReturning; 5 import org.aspectj.lang.annotation.AfterThrowing; 6 import org.aspectj.lang.annotation.Aspect; 7 import org.springframework.stereotype.Component; 8 9 /** 10 * 通知 11 * http://www.cnblogs.com/goodcheap 12 * 13 * @author: Wáng Chéng Dá 14 * @create: 2017-03-04 9:50 15 */ 16 @Aspect 17 @Component 18 public class AsjectLogging { 19 20 /** 21 * 在目标方法出现异常时候执行的代码。 22 * 可以访问到异常对象, 且可以指定在出现特定异常时再执行的代码。 23 */ 24 @AfterThrowing(value = "execution(* com.itdoc.spring.aop.circular.*.*(..))", throwing = "e") 25 public void afterThrowing(JoinPoint joinPoint, Exception e) { 26 Object methodName = joinPoint.getSignature().getName(); 27 System.out.println("The method " + methodName + " exception with " + e); 28 } 29 }
环绕通知:
1 package com.itdoc.spring.aop.circular; 2 3 import org.aspectj.lang.ProceedingJoinPoint; 4 import org.aspectj.lang.annotation.Around; 5 import org.aspectj.lang.annotation.Aspect; 6 import org.springframework.stereotype.Component; 7 8 import java.util.Arrays; 9 10 /** 11 * 通知 12 * http://www.cnblogs.com/goodcheap 13 * 14 * @author: Wáng Chéng Dá 15 * @create: 2017-03-04 9:50 16 */ 17 @Aspect 18 @Component 19 public class AsjectLogging { 20 21 /** 22 * 环绕通知需携带 ProceedingJoinPoint 类型的参数。 23 * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型参数可以决定是否执行目标方法。 24 * 环绕通知必须有返回值, 返回值即目标方法的返回值。 25 * 26 * @param point 27 * @return 28 */ 29 @Around("execution(* com.itdoc.spring.aop.circular.*.*(..))") 30 public Object around(ProceedingJoinPoint point) { 31 Object methodName = point.getSignature().getName(); 32 Object[] args = point.getArgs(); 33 Object result = null; 34 try { 35 //前置通知 36 System.out.println("The method " + methodName + " begins with" + Arrays.asList(args)); 37 //执行方法 38 result = point.proceed(); 39 //返回通知 40 System.out.println("The method " + methodName + " ends with " + result); 41 } catch (Throwable e) { 42 e.printStackTrace(); 43 //异常通知 44 System.out.println("The method " + methodName + " exception with " + e); 45 } finally { 46 //后置通知 47 System.out.println("The method " + methodName + " ends"); 48 } 49 return result; 50 } 51 }