一、动态代理(Dynamic agent):
1、特点:
字节码随用随创建,随用随加载。
2、作用:
在不修改源码的基础上对方法增强。
3、分类:
<1>、基于接口的动态代理:
(1)、涉及到的类:
Proxy
(2)、提供者:
JDK官方
(3)、创建方法:
使用Proxy类中的newProxyInstance方法。
(4)、对代理对象的要求:
被代理对象至少实现一个接口,否则不可用。
(5)、newProxyInstance方法的参数:
①、ClassLoader:
类加载器
用于加载代理对象的字节码,和被代理对象使用相同的类加载器。
固定写法
②、Class[]:
字节码数组
用于让代理对象和被代理对象拥有相同方法。
固定写法
③、InvocationHandler:
用于提供增强的代码
用于让我们代理,一般是一个该接口的实现类。通常是匿名内部类,但不是必须的。
作用:
执行被代理对象的任何方法都会经过该方法。
函数:
proxy:代理对象的引用。
method:当前执行的方法。
args:当前执行方法所需参数。
返回值:和被代理对象拥有相同的返回值。
<2>、基于子类的动态代理:
(1)、涉及到的类:
Enhancer
(2)、提供者:
第三方cglib库
(3)、创建方法:
使用Enhancer中的create方法。
(4)、对代理对象的要求:
被代理类不能是最终类。
(5)、create方法的参数:
①、class:
字节码
指定被代理类的字节码,要代理谁,就用谁.class
②、callback:
用于提供增强的代码
一般使用该接口的一个子接口实现类:MethodInterceptor
函数:
proxy:代理对象的引用。
method:当前执行的方法。
args:当前执行方法所需参数。
methodProxy:当前执行方法的代理对象。
返回值:和被代理对象拥有相同的返回值。
二、面向切面编程(AOP):
1、作用:
在程序运行期间,不修改源码,对已有方法进行增强。
2、优势:
<1>、减少重复代码。
<2>、提高开发效率。
<3>、维护方便。
3、相关术语:
<1>、Joinpoint(连接点):
指被拦截到的点。在spring中,这些点指的是方法。
因为spring只支持方法类型的连接点。也就是业务层的每一个方法。
<2>、Pointcut(切入点):
指的是要对哪些Joinpoint进行拦截的定义。也就是业务层中被增强的方法。
<3>、Advice(通知):
定义:
指的是拦截到Joinpoint之后要做的事情。
分类:
前置通知、后置通知、异常通知、最终通知、环绕通知
分类原理:
以调用业务层方法(被try……catch包裹起来)为界限,在其上面就是前置通知;下面就是后置通知;
在catch内就是异常通知;在finally内就是最终通知;整个invoke方法在执行时就是环绕通知。
<4>、Introduction(引介):
在不修改类代码的前提下,Introduction可以在运行期为类动态的添加一些方法或Field。
<5>、Target(目标对象):
代理的目标对象。
<6>、Weaving(织入):
指把增强应用到目标对象来创建新的代理对象的过程。
spring采用的是动态代理织入,而AspectJ采用的是编译器织入和类加载期织入。
<7>、Proxy(代理):
一个类被AOP织入增强后,就产生一个新的结果代理类。
<8>、Aspect(切面):
切入点和通知(引介)的结合。
4、spring中AOP的配置:
<1>、基于XML的配置:
(1)、把通知Bean交给spring管理
(2)、使用aop:config标签表明开始AOP的配置
(3)、使用aop:aspect标签表明配置切面
id属性:给切面提供一个唯一标识
ref属性:指定通知类bean的标签
(4)、在aop:aspect标签内部使用对应标签来配置通知
前置通知:aop:before
method属性:用于指定Logger类中哪个方法是前置通知。
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层哪些方法增强。
后置通知:aop:after-returning
method属性:用于指定Logger类中哪个方法是前置通知。
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层哪些方法增强。
异常通知:aop:after-throwing
method属性:用于指定Logger类中哪个方法是前置通知。
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层哪些方法增强。
最终通知:aop:after
method属性:用于指定Logger类中哪个方法是前置通知。
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层哪些方法增强。
切入点表达式:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名...包名.类名.方法名(参数列表)
eg:
标准写法:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略:
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以采用通配符*表示返回任意值
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以采用通配符*表示任意包;注意:有几集包就需要写几个*,中间以.隔开。
* *.*.*.*.AccountServiceImpl.saveAccount()
包名也可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名可以采用通配符*实现通配:
* *..*.saveAccount()
方法名可以采用通配符*实现通配:
* *..*.*()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.long.String
可以使用通配符*表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
通配符写法:
* *..*.*(..)
实际开发中,切入点表达式的通常写法:
切入到业务层实现类下的所有方法。
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置前置通知,在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut="execution(* *..*.*(..))"></aop:before>
<!--配置后置通知,在切入点方法执行之后执行-->
<aop:after-returning method="afterReturnPrintLog" pointcut="execution(* *..*.*(..))"></aop:after-returning>
<!--配置异常通知,在切入点方法执行产生异常之后执行-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* *..*.*(..))"></aop:after-throwing>
<!--配置最终通知,无论切入点方法是否执行,它都会在其后执行-->
<aop:after method="afterPrintLog" pointcut="execution(* *..*.*(..))"></aop:after>
</aop:aspect>
</aop:config>
<!--配置AOP-->
<aop:config>
<!--配置切入点表达式,id属性用于指定表达式的唯一标识,expression属性用于指定表达式内容。
此标签写在aop:pointcut标签外部时,只能写在<aop:pointcut>标签上面,<aop:config标签下面;可以在所有切面使用。
-->
<aop:pointcut id="pt1" expression="execution(* *..*.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置前置通知,在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
<!--配置后置通知,在切入点方法执行之后执行,它和异常通知两者只会执行一个-->
<aop:after-returning method="afterReturnPrintLog" pointcut-ref="pt1"></aop:after-returning>
<!--配置异常通知,在切入点方法执行产生异常之后执行,它和后置通知两者只会执行一个-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<!--配置最终通知,无论切入点方法是否执行,它都会在其后执行-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
(5)、配置环绕通知:
环绕通知:aop:before
它是spring框架提供的一种可以在代码中手动控制增强方法何时执行的方式。
问题:当配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
分析:对比动态代理中的环绕通知代码,发现动态代理的环绕通知中有明确的
切入点方法调用,而此处的代码中没有。
解决:Spring框架提供了一个接口:ProceedingJoinPoint。该接口中有一个
方法proceed(),此方法就相当于明确调用切入点方法。该接口可以作为
环绕通知的方法参数,在程序执行时,Spring框架会为我们提供该接口的实现类供我们使用。
<aop:config>
<!--配置切入点表达式,id属性用于指定表达式的唯一标识,expression属性用于指定表达式内容。
此标签写在aop:pointcut标签外部时,只能写在<aop:pointcut>标签上面,<aop:config标签下面;可以在所有切面使用。
-->
<aop:pointcut id="pt1" expression="execution(* *..*.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置环绕通知-->
<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
public class Logger {
/**
* 环绕通知
*/
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方法开始记录日志!!!最终通知");
}
}
}
(5)、五种通知的分类依据:
<1>、基于注解的配置:
(1)、相关约束:
<!--配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--配置Spring开启注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
(2)、相关注解:
①、@Aspect:
将"<aop:aspect id="logAdvice" ref="logger"></aop:aspect>"替换成
实现类上的注解@Aspect,表示当前类是一个切面。
②、@Pointcut:
将"<aop:pointcut id="pt1" expression="execution(* *..*.*(..))"></aop:pointcut>"
替换成方法上的注解
@Pointcut("execution(* *..*.*(..))")
private void pt1(){}
③、@Before("pt1()"):
前置通知,注解在前置通知的实现方法上,替换
"<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>"
注解的参数指的是切入点表达式注解的方法的方法名。
④、@AfterReturning("pt1()"):
后置通知,注解在后置通知的实现方法上,替换
"<aop:after-returning method="afterReturnPrintLog" pointcut-ref="pt1"></aop:after-returning>"
注解的参数指的是切入点表达式注解的方法的方法名。
⑤、@AfterThrowing("pt1()"):
异常通知,注解在异常通知的实现方法上,替换
"<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>"
注解的参数指的是切入点表达式注解的方法的方法名。
⑥、@After("pt1()"):
最终通知,注解在最终通知的实现方法上,替换
"<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>"
注解的参数指的是切入点表达式注解的方法的方法名。
⑦、@Around("pt1()"):
环绕通知,注解在环绕通知的实现方法上,替换
"<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>"
注解的参数指的是切入点表达式注解的方法的方法名。
⑧、特别注意:
Spring基于注解的通知,有顺序调用的问题,采用注解时,
后置通知/异常通知会在最终通知之后执行;而采用环绕通知时,就不会有顺序问题。