一、配置文件开发注意点
【1】切入点表达式
within表达式(粗粒度表达式)
within(包名.类名)
execution表达式(细粒度表达式,常用)
execution(返回值类型 包名.类名.方法名(参数类型,参数类型…))
常用符号:
.*:当前包下的任意类/当前类中的任意方法
..*:当前包下以及子包下的所有类
*:任意返回值
..:接收任意参数
【2】五大通知
前置通知:在目标方法执行之前执行执行的通知。
前置通知方法,可以没有参数,也可以额外接收一个JoinPoint,Spring会自动将该对象传入,代表当前的连接点,通过该对象可以获取目标对象 和 目标方法相关的信息。注意,如果接收JoinPoint,必须保证其为方法的第一个参数,否则报错。
<aop:config>
<aop:pointcut expression="execution(* service..*.*(..))" id="pc1"/>
<aop:aspect ref="firstAspect">
<aop:before method="before" pointcut-ref="pc1"/>
</aop:aspect>
</aop:config>
public void before(JoinPoint jp){
//获取目标对象的字节码文件
Class clz = jp.getTarget().getClass();
//获取方法字节码文件
MethodSignature sign = (MethodSignature) jp.getSignature();
Method method = sign.getMethod();
System.out.println("前置通知");
}
jp.getTarget().getClass();便可以获得目标对象的字节码文件,从而获得自己所想要的类信息
MethodSignature sign = (MethodSignature) jp.getSignature();
Method method = sign.getMethod();可以获得方法的字节码文件,从而获得与方法相关的信息。
不过大家注意,如果,方法是目标对象实现的接口的方法,则该method对象是借口的方法对象,而不是类的方法对象。
环绕通知:在目标方法执行之前和之后都可以执行额外代码的通知。
在环绕通知中必须显式的调用目标方法,否则目标方法不会执行。这个显式调用是通过ProceedingJoinPoint来实现的,可以在环绕通知中接收一个此类型的形参,spring容器会自动将该对象传入,这个参数必须处在环绕通知的第一个形参位置。
环绕通知有控制目标方法是否执行、目标方法执行之前或之后执行额外代码、控制是否返回返回值、改变返回值的能力。但要慎用,不要破坏了软件分层的“高内聚 低耦合”的目标。
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕通知前");
Object obj = pjp.proceed();
System.out.println("环绕通知后");
return obj;
}
注意:环绕通知需要返回返回值,否则真正调用者将拿不到返回值,只能得到一个null。
后置通知:在目标方法执行之后执行的通知。
后置通知,可以获取方法返回值,加上returning便可以获得,“msg”变量名要和接收的变量名保持一致。
<aop:after-returning method="afterReturning" pointcut-ref="pc1" returning="msg"/>
public void afterReturning(JoinPoint jp,Object msg)
异常通知:在目标方法抛出异常时执行的通知。
异常通知,可以获取异常信息,加上throwing便可以获得,“e”变量名要和接收的变量名保持一致。
<aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/>
public void afterThrowing(JoinPoint jp,Throwable e)
最终通知:是在目标方法执行之后执行的通知。
和后置通知不同之处在于,后置通知是在方法正常返回后执行的通知,如果方法没有正常返-例如抛出异常,则后置通知不会执行。而最终通知无论如何都会在目标方法调用过后执行,即使目标方法没有正常的执行完成。另外,后置通知可以通过配置得到返回值,而最终通知无法得到。
【3】五大通知执行顺序
单切面执行,主要看配置文件的顺序
如果前置通知在环绕通知之前,则先前置通知,在环绕前通知。如果后置通知在环绕通知之前,则先后置通知,在环绕后通知。最终通知,最后执行。
多切面执行时,采用了责任链设计模式。
切面的配置顺序决定了切面的执行顺序,多个切面执行的过程,类似于方法调用的过程,在环绕通知的proceed()执行时,去执行下一个切面。然后执行完别的切面之后,在返回执行环绕通知中proceed()后的内容。有点像请求转发的过程。
二、注解开发
【1】开启切面注解配置
<aop:aspectj-autoproxy/>
在表示切面的类上加上@Aspect表示切面。
【2】五大通知
前置通知 | @Before |
环绕通知 | @Around |
后置通知 | @AfterReturning |
异常通知 | @AfterThrowing |
最终通知 | @After |
【3】如果一个切面中多个通知 重复使用同一个切入点表达式,则可以将该切入点表达式单独定义,后续引用,注意,在当前切面中通过注解定义的切入点只在当前切面中起作用,其他切面看不到。
@Pointcut("execution(* service.UserService.*(..)")
public void pc(){}
其他切入点只需要@Before("pc()")这样引用
【4】
在后置通知的注解中,也可以额外配置一个returning属性,来指定一个参数名接受目标方法执行后的返回值
@AfterReturning(value="pc()", returning="msg")
在异常通知的注解中,也可以额外配置一个throwing属性,来指定一个参数名接受目标方法抛出的异常对象
@AfterThrowing(value="pc()", throwing="e")
【5】SpringAOP实现原理
Spring在创建bean时,除了创建目标对象bean之外,会根据aop的配置,生成目标对象的代理对象,将其存储,之后获取bean时得到的其实是代理对象,在代理对象中,根据配置的切入点规则,决定哪些方法不处理直接执行目标方法,哪些方法拦截后进行增强,需要增强的方法拦截后根据配置调用指定切面中的指定通知执行增强操作。Spring自动为目标对象生成代理对象,默认情况下,如果目标对象实现过接口,则采用java的动态代理机制,如果目标对象没有实现过接口,则采用cglib动态代理。开发者可以可以在spring中进行配置,要求无论目标对象是否实现过接口,都强制使用cglib动态代理。
<aop:config proxy-target-class="true"></aop:config> 表示强制使用cglib代理