AOP底层原理:动态代理
动态代理
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
分类:
1)基于接口的动态代理
涉及的类:Proxy
提供者:JDK官方
创建代理对象:使用Proxy类中newProxyInstance方法
要求:被代理类最少实现一个接口,如果没有则不能使用
newProxyInstance方法的参数
- ClassLoader:类加载器
用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法 - Class[]:字节码数组
用于让代理对象和被代理对象有相同方法。固定写法 - InvocationHandler:用于提供增强的代码
一般都是写一个该接口的实现类,通常情况下都是匿名内部类 此接口的实现类都是谁用谁写
接口名 新对象名 = (接口名)Proxy.newProxyInstance(
被代理的对象.getClass().getClassLoader(), // 被代理对象的类加载器,固定写法
被代理的对象.getClass().getInterfaces(), // 被代理对象实现的所有接口,固定写法
new InvocationHandler() { // 匿名内部类,通过拦截被代理对象的方法来增强被代理对象
/* 被代理对象的任何方法执行时,都会被此方法拦截到
其参数如下:
proxy: 代理对象的引用,不一定每次都用得到
method: 被拦截到的方法对象
args: 被拦截到的方法对象的参数
返回值:
被增强后的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("方法名".equals(method.getName())) {
// 增强方法的操作
rtValue = method.invoke(被代理的对象, args);
// 增强方法的操作
return rtValue;
}
}
});
2)基于子类的动态代理
涉及的类:Enhancer
提供者:第三方cglib库
创建代理对象 :使用Enhancer类中create方法
要求:被代理类不能是最终类
方法的参数
- Class:字节码
用于指定被代理对象的字节码。 - Callback:用于提供增强的代码
一般都是写一个该接口的实现类,通常情况下都是匿名内部类,此接口的实 现类都是谁用谁写。
一般都是写该接口的子接口实现类:MethodInterceptor
AOP
相关术语
- Joinpoint(连接点): 被拦截到的方法
- Pointcut(切入点): 我们对其进行增强的方法
- Advice(通知/增强): 对切入点进行的增强操作
- 包括前置通知,后置通知,异常通知,最终通知,环绕通知
- Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。
- Aspect(切面): 是切入点和通知的结合
spring中基于XML的AOP配置步骤
1、把通知Bean也交给spring来管理
2、使用aop:config标签表明开始AOP的配置
3、使用aop:aspect标签表明配置切面
属性
- id :是给切面提供一个唯一标识
- ref:是指定通知类bean的Id。
4、在aop:aspect标签的内部使用对应标签来配置通知的类型
具体的通知类型:
aop:before: 配置前置通知
aop:after-returning: 配置后置通知
aop:after-throwing: 配置异常通知
aop:after: 配置最终通知
aop:around: 配置环绕通知,可以在代码中手动控制增强方法何时执行
属性
- method:用于指定通知类中哪个方法是增强方法
- pointcut:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
- ponitcut-ref: 指定切入点的表达式的id
5、aop:pointcut标签配置切入点表达式,指定对哪些方法进行增强
属性 - id: 指定切入点表达式的id
- expression: 指定切入点表达式
<aop:config>
<aop:aspect id="logAdvice" ref="logger">
<!--配置切入点表达式-->
<aop:pointcut expression="execution(* cn,maoritian.service.impl.*.*(..))" id="pt1"></aop:pointcut>
<!--配置各种类型的通知-->
<aop:before method="printLogBefore" pointcut-ref="pt1"></aop:before>
<aop:after-returning method="printLogAfterReturning" pointcut-ref="pt1"></aop:after-returning>
<aop:after-throwing method="printLogAfterThrowing" pointcut-ref="pt1"></aop:after-throwing>
<aop:after method="printLogAfter" pointcut-ref="pt1"></aop:after>
<!--环绕通知一般单独使用-->
<!-- <aop:around method="printLogAround" pointcut-ref="pt1"></aop:around> -->
</aop:aspect>
</aop:config>
6、切入点表达式的写法:
关键字:executinn(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名.类名.方法名(参数列表)
一般我们都是对业务层所有实现类的所有方法进行增强
因此切入点表达式写法通常为
* com.potato.service.impl.*.*(..)
访问修饰符可以省略
7、Spring框架为我们提供一个接口:ProceedingJoinPoint
该接口的proceed方法相当于明确调用切入点方法
该接口可以作为环绕通知的方法参数,程序执行时,spring会为我们提供该接口的实现类
ProceedingJoinPoint对象的getArgs()方法返回被拦截的参数
ProceedingJoinPoint对象的proceed()方法执行被拦截的方法
// 环绕通知方法,返回Object类型
public Object printLogAround(ProceedingJoinPoint pjp) {
Object rtValue = null;
try {
Object[] args = pjp.getArgs();
printLogBefore(); // 执行前置通知
rtValue = pjp.proceed(args);// 执行被拦截方法
printLogAfterReturn(); // 执行后置通知
}catch(Throwable e) {
printLogAfterThrowing(); // 执行异常通知
}finally {
printLogAfter(); // 执行最终通知
}
return rtValue;
}
使用注解配置AOP
半注解配置AOP
开启对注解AOP的支持
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
1 用于声明切面的注解
@Aspect: 声明当前类为通知类,该类定义了一个切面.相当于xml配置中的aop:aspect标签
2 用于声明通知的注解
@Before: 声明该方法为前置通知.相当于xml配置中的aop:before标签
@AfterReturning: 声明该方法为后置通知.相当于xml配置中的aop:after-returning标签
@AfterThrowing: 声明该方法为异常通知.相当于xml配置中的aop:after-throwing标签
@After: 声明该方法为最终通知.相当于xml配置中的aop:after标签
@Around: 声明该方法为环绕通知.相当于xml配置中的aop:around标签
属性:
value: 用于指定切入点表达式或切入点表达式的引用
3 用于指定切入点表达式的注解
@Pointcut: 指定切入点表达式,其属性如下:
value: 指定表达式的内容
@Pointcut注解没有id属性,通过调用被注解的方法获取切入点表达式
@Component("logger")
@Aspect //表示当前类是一个通知类
public class Logger {
// 配置切入点表达式
@Pointcut("execution(* cn.maoritian.service.impl.*.*(..))")
private void pt1(){}
// 通过调用被注解的方法获取切入点表达式
@Before("pt1()")
public void printLogBefore(){
System.out.println("前置通知Logger类中的printLogBefore方法开始记录日志了。。。");
}
// 通过调用被注解的方法获取切入点表达式
@AfterReturning("pt1()")
public void printLogAfterReturning(){
System.out.println("后置通知Logger类中的printLogAfterReturning方法开始记录日志了。。。");
}
// 通过调用被注解的方法获取切入点表达式
@AfterThrowing("pt1()")
public void printLogAfterThrowing(){
System.out.println("异常通知Logger类中的printLogAfterThrowing方法开始记录日志了。。。");
}
}
纯注解配置AOP
在Spring配置类前添加@EnableAspectJAutoProxy注解,可以使用纯注解方式配置AOP