JoinPoint(连接点):
连接点是指那些拦截到的点。在spring中指的是方法,因为spring只支持方法类型的连接点。
PonitCut(切入点):
所谓切入点,是指要对哪些JoinPoint(连接点)进行拦截定义,即那些被加强、定义的连接点。
注意:一般来说,连接点个数≥切入点个数,即连接点包含切入点。 所以,连接点不一定是切入点,但是切入点一定是连接点。
Advice(通知/增强):
通知,是指拦截到JoinPoint(连接点)后所要作的事情就是通知。
通知类型分为:前置通知,后置通知,异常通知,最终通知,环绕通知。
注意:在切入点方法正常执行之后,后置通知和异常通知永远只能执行一个。
Introduction(引介):
引介是一种特殊的通知。在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field。
Target(目标对象/被代理对象):
代理的目标对象,即被代理对象。
Weaving(织入):
指把增强应用到目标对象来创建新的代理对象的过程。也可以狭义的理解为:将通知加到代理对象里面的那个动作。
Proxy(代理对象):
一个类(目标对象)被AOP织入增强后产生的对象,即为代理对象。
Aspect(切面):
是切入点和通知(引介)的结合。
下面举例子来说明:
1.使用CgLib动态代理加XML配置的方式:比如在使用CgLib动态代理,将以一个业务层IAccountService的一些事务性方法(比如transfer()转账)进行加强的时候,涉及到的前置通知、后置通知、异常通知、最终通知如图中注释部分已标明,而环绕通知指的是这些通知组合在一起的一个整体。由这些切入点和通知(引介)组成的结合,就称为切面。关于这个切面的XML配置,在此不是题目重点,就不配了。
/** * 获取Service代理对象 * @return */ public IAccountService getAccountService() { return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { /** * 添加事务的支持 * * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("test".equals(method.getName())){ return method.invoke(accountService,args); } Object rtValue = null; try { //1.开启事务 txManager.beginTransaction();//前置通知(名词化,称为前置通知。动词化,可以理解为方法织入) //2.执行操作 rtValue = method.invoke(accountService, args);//调用原始方法(比如transfer()转账方法),此时transfer()方法也被称为PonitCut(切入点) //3.提交事务 txManager.commit();//后置通知(名词化,称为后置通知。动词化,可以理解为方法织入) //4.方法返回结果 return rtValue; } catch (Exception e) { //5.回滚操作 txManager.rollback();//异常通知(名词化,称为异常通知。动词化,可以理解为方法织入) throw new RuntimeException(e); } finally { //6.释放连接 txManager.release();//最终通知(名词化,称为最终通知。动词化,可以理解为方法织入) } } }); }
2.使用spring注解的方式:代码如下,相关概念也标明在注释中了。但是,使用spring注解方式的时候,建议直接使用环绕通知,而不建议使用前置通知、后置通知、异常通知、最终通知配合的方式。因为后者,低版本spring框架中还有一点bug:即后置通知和异常通知执行的顺序好像有点问题。
package com.cookiewu.utils; import org.aspectj.lang.ProceedingJoinPoint; /** * 用于记录日志的工具类,它里面提供了公共的代码 */ @Component("logger") @Aspect//表示当前类是一个切面类 public class Logger { //定义切入点 @Pointcut("execution(* cn.qut.service.impl.*.*(..))") private void pt1(){} /** * 前置通知 */ // @Before("pt1()") public void beforePrintLog(){ System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。"); } /** * 后置通知 */ // @AfterReturning("pt1()") public void afterReturningPrintLog(){ System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。"); } /** * 异常通知 */ // @AfterThrowing("pt1()") public void afterThrowingPrintLog(){ System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。"); } /** * 最终通知 */ // @After("pt1()") public void afterPrintLog(){ System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。"); } /** * 环绕通知 * 问题: * 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。 * 分析: * 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。 * 解决: * Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。 * 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。 * * spring中的环绕通知: * 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。 */ @Around("pt1()") public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs();//得到方法执行所需的参数 System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常"); throw new RuntimeException(t); }finally { System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终"); } } }