一、AspectJ
1、介绍
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,也可以说 AspectJ 是一个基于 Java 语言的 AOP 框架。通常我们在使用 Spring AOP 的时候,都会导入 AspectJ 的相关 jar 包。
2、案例(xml)
定义目标对象(被代理的对象)(与上一章相同)
编写一个切面类(通知)
1 // 创建切面类(包含各种通知) 2 public class MyAspect { 3 4 // JoinPoint 能获取目标方法的一些基本信息 5 public void myBefore(JoinPoint joinPoint) { 6 System.out.println("前置通知:方法增强myBefore()" + " , -->" + joinPoint.getSignature().getName()); 7 } 8 9 // object:目标方法的返回值 10 public void myAfterReturning(JoinPoint joinPoint, Object object) { 11 System.out.println("后置通知:方法增强myAfterReturning()" + " , -->" + joinPoint.getSignature().getName() + " , -->" + object); 12 } 13 14 public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable { 15 System.out.println("============环绕前=============="); 16 Object obj = joinPoint.proceed(); // 手动执行目标方法 17 System.out.println("============环绕后=============="); 18 return obj; 19 } 20 21 public void myAfterThrowing(JoinPoint joinPoint, Throwable e) { 22 System.out.println("抛出异常通知:" + e.getMessage()); 23 } 24 25 public void myAfter() { 26 System.out.println("最终通知:方法增强myAfter()"); 27 } 28 29 }
编写配置文件 application.xml
1 <!-- 配置目标对象 --> 2 <bean id="teacher" class="com.lx.spring.common.Teacher"/> 3 <!-- 配置切面对象(通知) --> 4 <bean id="myAspect" class="com.lx.spring.day3.MyAspect"/> 5 6 <aop:config> 7 <!-- 切入点表达式,指明了在哪里引入通知 --> 8 <aop:pointcut id="myPointcut" expression="execution(* com.lx.spring.common.ITeacher.*(..))"/> 9 10 <!-- 方法增强,指明了引入一个什么样的通知 --> 11 <aop:aspect ref="myAspect"> 12 <aop:before method="myBefore" pointcut-ref="myPointcut"/> 13 <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="object"/> 14 <aop:around method="myAround" pointcut-ref="myPointcut"/> 15 <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="e"/> 16 <aop:after method="myAfter" pointcut-ref="myPointcut"/> 17 </aop:aspect> 18 </aop:config>
1 // 测试类 2 public class Main { 3 public static void main(String[] args) { 4 ApplicationContext app = new ClassPathXmlApplicationContext("app3.xml"); 5 ITeacher iTeacher = app.getBean(ITeacher.class); 6 7 iTeacher.add(11, 24); 8 } 9 } 10 11 // 结果一:未配置环绕通知时.注意:此时 最终通知 在 后置通知后面 12 前置通知:方法增强myBefore() , -->add 13 执行目标方法:老师正在做加法,结果为:35 14 后置通知:方法增强myAfterReturning() , -->add , -->35 15 最终通知:方法增强myAfter() 16 17 18 // 结果二:配置环绕通知时.注意:此时 最终通知 在 后置通知前面 19 前置通知:方法增强myBefore() , -->add 20 ============环绕前============== 21 执行目标方法:老师正在做加法,结果为:35 22 最终通知:方法增强myAfter() 23 ============环绕后============== 24 后置通知:方法增强myAfterReturning() , -->add , -->35
说明:这里有很多细节需要补充一下。深刻理解通知,重点思想在于:①在哪里(切点,或者说方法)引入?②引入一个什么样的通知?针对这两个问题,则不难理解AOP。
①切入点表达式:指明了在哪里(切点,在哪个方法)引入一个通知(即对目标方法的增强),也就是在哪些方法进行增强。execution 是 AspectJ 框架定义的一个切入点函数,其语法形式如下:
1 execution(modifiers-pattern? ref-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) 2 类修饰符 返回值 方法所在的包 方法名(参数) 方法抛出的异常
那么不难理解,对满足以下规则的方法进行增强。也就是对这些方法引入一个通知。
1 <aop:pointcut id="myPointcut" expression="execution(* com.lx.spring.common.ITeacher.*(..))"/> 2 选择方法 任意返回值 此包下.此接口 任意方法名(任意参数)
②通知(方法增强):指明了对满足切点表达式的方法引入一个什么样的通知。
1 <!-- 指明引入的切面(通知) --> 2 <aop:aspect ref="myAspect"> 3 <!-- 对满足上面切入点表达式的方法配置一个前置通知 --> 4 <!-- 即在目标方法前执行方法 myBefore --> 5 <aop:before method="myBefore" pointcut-ref="myPointcut"/> 6 7 <!-- 对满足上面切入点表达式的方法配置一个后置通知 --> 8 <!-- 即在目标方法后执行方法 myAfterReturning --> 9 <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="object"/> 10 </aop:aspect>
3、切入点表达式
上一节中已经介绍过切入点表达式的相关语法,且理解不难。再补充几点,如果切入点表达式有多个不同目录呢?可以通过 || 来表示或的关系。
1 <!--表示匹配com.lx.aop包下的,以Service结尾或者以Facade结尾的类的任意方法。--> 2 <aop:pointcut id="myPointcut" expression="execution(* com.lx.aop.*Service.*(..)) || execution(* com.lx.aop.*Facade.*(..))"/>
AOP 切入点表达式支持多种形式的定义规则:
1 1、execution:匹配方法的执行(常用) 2 execution(public *.*(..)) 3 2、within:匹配包或子包中的方法(了解) 4 within(com.ys.aop..*) 5 3、this:匹配实现接口的代理对象中的方法(了解) 6 this(com.ys.aop.user.UserDAO) 7 4、target:匹配实现接口的目标对象中的方法(了解) 8 target(com.ys.aop.user.UserDAO) 9 5、args:匹配参数格式符合标准的方法(了解) 10 args(int,int) 11 6、bean(id):对指定的bean所有的方法(了解) 12 bean('userServiceId')
4、通知类型
通知类型
|
接口
|
描述
|
前置通知
(before)
|
org.springframework.aop.aspectj.AspectJMethodBeforeAdvice
|
在目标方法前调用,如果通过抛出异常,阻止方法运行。
应用:各种校验。
|
后置通知
(afterReturning)
|
org.springframework.aop.aspectj.AspectJAfterReturningAdvice
|
在目标方法后调用,可以获得目标方法返回值,若目标方法抛出异常,通知无法执行。
应用:常规数据处理。
|
环绕通知
(around)
|
org.springframework.aop.aspectj.AspectJAroundAdvice
|
在目标方法前后调用,可以阻止方法的执行,必须手动执行目标方法。
应用:十分强大,可以做任何事情。
|
异常通知
(afterThrowing)
|
org.springframework.aop.aspectj.AspectJAfterThrowingAdvice
|
目标方法抛出异常时调用,若目标方法没有抛出异常,无法执行。
应用:包装异常信息
|
最终通知
(after)
|
org.springframework.aop.aspectj.AspectJAfterAdvice
|
目标方法执行完毕后执行,无论方法中是否出现异常
应用:清理现场
|
这里最重要的是around,环绕通知,它可以代替上面的任意通知。
在程序中表示的意思如下:
1 public class Main { 2 public static void main(String[] args) { 3 try { 4 // 前置 before 5 // 手动执行目标方法 6 // 后置 afterReturning 7 } catch (Exception e) { 8 // 抛出异常通知 afterThrowing 9 } finally { 10 // 最终 after 11 } 12 } 13 }
源码:
5、小结
使用 <aop:config>进行配置,proxy-target-class="true",声明时使用cglib代理;如果不声明,Spring 会自动选择cglib代理还是JDK动态代理。
SpringAOP 的具体加载步骤:
①当 spring 容器启动的时候,加载 spring 的配置文件。
②为配置文件中的所有 bean 创建对象。
③spring 容器会解析 aop:config 的配置,解析切入点表达式,用切入点表达式和纳入 spring 容器中的 bean 做匹配,如果匹配成功,则会为该 bean 创建代理对象,代理对象的方法 = 目标方法 + 通知;如果匹配不成功,不会创建代理对象。
④在客户端利用 context.getBean() 获取对象时,如果该对象有代理对象,则返回代理对象;如果没有,则返回目标对象
说明:如果目标类没有实现接口,则 spring 容器会采用 cglib 的方式产生代理对象,如果实现了接口,则会采用 jdk 的方式。