动态代理
特点:
字节码随用随创建,随用随加载
作用:
不修改源码的基础上对方法增强
分类:
1. 基于接口的动态代理
涉及的类:
Proxy
提供者:
jdk官方
如何创建代理对象:
使用Proxy类中的newProxyInstance方法
创建代理对象的要求:
被代理的类至少实现一个接口,如果没有则不能使用
newProxyInstance方法的参数:
ClassLoader:类加载器
它是用于加载代理对象字节码的,和被代理对象使用相同的类加载器,固定写法
Class[]:字节码数组
它是用于让代理对象和被代理对象有相同的方法,固定写法
InvocationHandler:用于提供增强的代码
它是用于让我们写如何代理,我们一般都是写一个该接口的实现类,通常是一个匿名内部类(不是必须的)
此接口的实现类都是谁用谁写
*存在的问题:当我们的类没有实现任何的接口的时候是不可以使用的
代理对象执行方法,其实就是代替被代理对象执行方法
被代理对象实现接口
生成代理对象
代理对象.被代理对象的方法
2. 基于子类的动态代理(cglib)
涉及的类:
Enhancer
提供者:
第三方cglib库
如何创建代理对象:
使用Enhancer类中的create方法
创建代理对象的要求:
被代理的类不是最终类,不能被final修饰
create方法的参数:
Class:字节码
它是用于指定被代理对象的字节码
Callback:用于提供增强的代码
它是用于让我们写如何代理,我们一般都是写一个该接口的实现类,通常是一个匿名内部类(不是必须的)
此接口的实现类都是谁用谁写
我们一般写的都是该接口的子接口实现类:MethodIntercepor
Spring中的AOP
概述:
AOP:全称是 Aspect Oriented Programming 即:面向切面编程。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的
基础上,对我们的已有方法进行增强
作用以及优势:
作用:
在程序运行期间,不修改源码对已有方法进行增强。
优势:
减少重复代码
提高开发效率
维护方便
实现方式:
使用动态代理技术
AOP相关术语:
连接点:
目标对象的所有方法(所有的切入点都是连接点)
切入点:
被增强的都是切入点
增强/通知:
关于增强的代码块(可以是抽取出来的类)
引介:
是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
Target:
被代理的对象
织入:
是指把增强应用到目标对象来创建新的代理对象的过程。
spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
代理(Proxy):
一个类被AOP织入增强后,产生的结果代理类
切面:
切入点与通知的结合
Spring在AOP开发中代理方式的选择
proxy-target-class
默认false: 自动选择
true: 使用cglib代理
1. 基于XML的
<aop:config proxy-target-class="true">
.....
</aop:config>
2. 基于注解
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
Spring中基于XML的AOP的配置
1. 把通知bean交给Spring来管理
2. 使用aop:config标签表明开始AOP的配置
3. 使用aop:aspect标签表明开始配置切面
id属性:给切面提供一个唯一标识
ref属性:是指定通知类bean的id
4. 在aop:aspect标签的内部使用对用的标签来配置通知的类型
前置通知: aop:before(表示配置前置通知)
method属性:用于指定类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层的哪些方法增强
切入点表达式的写法:
关键字: execution(表达式)
表达式:
访问修饰符 返回值 包名.包名...类名.方法名(参数列表)
标准表达式的写法:
execution(public void com.qin.service.impl.AccountServiceImpl.xxx(..))
访问修饰符可以省略
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
* *.*.*.*.AccountServiceImpl.saveAccount())
包名可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*来实现通配
* *..*.*()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
全通配写法:
* *..*.*(..)
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.itheima.service.impl.*.*(..)
5. 例
<!-- 配置目标类-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 配置通知类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
此标签写在aop:aspect标签内部只能当前切面使用。
它还可以写在aop:aspect外面,此时就变成了所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
<!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"
returning="val"></aop:after-returning>
<!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1" throwing="ex"></aop:after-throwing>
<!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
<!-- 配置环绕通知 -->
<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
Spring中基于注解的AOP
1. 目标对象
即被代理的对象
如:业务层的AccountServiceImpl
2. 通知对象
即散落在其他类中可以抽取成共性的代码块,可以提取成为一个类(比如日志记录,代码性能监控)
3. 在核心配置中开启对AOP的支持
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
4. 使用注解在通知上配置切面
4.0 表示当前类是一个切面类
@Aspect
4.1 配置切点
@Pointcut("execution(* com.itheima.service.impl.*.update(..))")
private void pt1(){}
@Pointcut("execution(* com.itheima.service.impl.*.save(..))")
private void pt2(){}
4.3 在方法上配置注解
@Before 前置通知
@After 最终通知
@AfterReturning 后置通知
@AfterThrowing 异常通知
@Around 环绕通知