增强
增强(advice):就是在原方法的基础之上,通过插入一段代码从而来增强原方法的功能
在 不同时机 插入一段额外代码
环绕增强=前置增强+后置增强+异常增强+最终增强
没有AOP导致的问题
1.责任不分离:业务方法只需要关系如何完成该业务功能,不用去关心事务管理/日志管理等
2.代码结构重复
静态代理
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问
特点:
1.代理对象完全包含真实对象,客户端使用的都是代理对象的方法,和真实对象没有直接关系
2.代理模式的职责:把不是真实对象该做的事情从真实对象上撇开——职责清晰
静态代理:在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行之前就确定了.
缺点:该代理类不够通用
动态代理
动态代理类是在程序运行期间由JVM通过反射等机制动态生成的,所以不存在代理类的字节码文件,代理对象和真实对象的关系是在程序运行时期才确定的
实现动态代理类:
1.针对有接口:使用JDK动态代理
2.针对无接口:使用CGLIB或javassist
字节码动态加载
JVM 通过字节码的二进制信息加载类的,如果我们在运行期系统中,遵循 Java 编译系统组织.
class 文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类.
java.lang.reflect.Proxy 类:Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法 来为一组接口动态地创建代理类及其对象
JDK 动态代理
主要方法:
1
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler hanlder)
原理图
通过 newProxyInstance 创建代理类对象,调用hanlder的invoke()方法,底层调用method.invoke()方法.代理类对象和真实类同时实现了实现类的接口.
CGLIB 动态代理
使用 JDK 的动态代理,只能针对于目标对象存在接口的情况,如果目标对象没有接口,此时可以考虑使用 CGLIB 的动态代理方式.Spring 已经自带了 cglib 库.
CGLIB 是通过生成代理类,然后继承于目标类,再对目标类中可以继承的方法做覆盖,并在该方法中做功能增强的,因为多态的关系,通过代理对象调用方法,实则调用的是代理类中的方法
动态代理总结
JDK 代理总结:
1.JAVA 动态代理是使用 java.lang.reflect 包中的 Proxy 类与 InvocationHandler 接口这两个来完成的.
2.要使用 JDK 动态代理,委托必须要定义接口.
3.JDK 动态代理将会拦截所有 pubic 的方法(因为只能调用接口中定义的方法),这样即使在接口中增加 了新的方法,不用修改代码也会被拦截.
4.动态代理的最小单位是类(所有类中的方法都会被处理),如果只想拦截一部分方法,可以在 invoke 方法 中对要执行的方法名进行判断.
CGLIB 代理总结:
1.CGLIB 可以生成委托类的子类,并重写父类非 final 修饰符的方法。
2.要求类不能是 final 的,要拦截的方法要是非 final、非 static、非 private 的.
3.动态代理的最小单位是类(所有类中的方法都会被处理).
AOP
AOP(Aspect Oritention Programming):
把一个个的横切关注点放到某个模块中去,称之为切面.就是把多个业务方法共同调用的某种功能的代码封装到一个类中.
切面的目的就是功能增强
AOP 把多个业务方法需要调用的代码封装到不同的模块中去(责任分离思想).
使用动态代理 机制来动态的增强业务功能,从而达到了代码的复用.也提高了维护性
AOP核心概念
where:哪一些方法:一般使用方法的全限定名称来表示
when:在方法体执行的什么时机:之前、之后、异常、最终、环绕
what:做什么功能的增强:不同的增强功能使用不同的模块或类来封装
Joinpoint:连接点.
程序执行过程中的某个特定位置,如类初始化之前/后,方法调用前/后,方法抛出异常后等.
这些特定的位置就称之为连接点,通过在连接点插入代码,从而增强功能.
相当于切面的增强时机:when.Spring 仅支持的五种连接点(五种增强时机).
Pointcut:切入点.
开发中,往往不需要对应用中所有的连接点都做增强,切点的作用就是缩小增强的范围,比如哪些包中的哪些类中的哪些方法.相当于切面的在何处增强:where.
Advice:增强.
拦截到连接点后具体做什么功能,相当于切面的增强什么:what.
Aspect:切面.
Pointcut+Advice,去哪些方法中+在方法执行的什么时机+做什么增强
Pointcot语法
execution(<修饰符>? <返回类型> <声明类型>? <方法名>(<参数>) <异常>?)
切入点表达式中的通配符:
*: 匹配任何部分,在包路径中只能表示一个包,不包括子包.
..: 可用于全限定名中和方法参数中,分别表示子包和 0 到 N 个参数.
应用场景:
增强方法参数配置
1.在增强方法中获取异常信息
XML 配置:1
<aop:after-throwing method="rollback" pointcut-ref="txPoint" throwing="ex" />
1 | public void rollback(Throwable ex) { |
2.获取被增强方法信息,并传递给增强方法
Spring AOP 提供 org.aspectj.lang.JoinPoint 类,作为增强方法的第一个参数.
1.JoinPoint:提供访问当前被增强方法的真实对象、代理对象、方法参数等数据.
2.ProceedingJoinPoint:JinPoint子类,只用于环绕增强中,可以处理被增强方法.
使用 JoinPoint 类:1
2
3
4
5
6
7
8public void begin(JoinPoint jp) {
System.out.println("代理对象:"+jp.getThis().getClass());
System.out.println("目标对象:"+jp.getTarget().getClass());
System.out.println("被增强方法参数:"+Arrays.toString(jp.getArgs()));
System.out.println("当前连接点签名:"+jp.getSignature());
System.out.println("当前连接点类型:"+jp.getKind());
System.out.println("开启事务");
}
使用 ProceedingJoinPoint 类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public Object around(ProceedingJoinPoint pjp) {
Object ret = null;
System.out.println("开启事务");
try {
//执行目标方法
ret = pjp.proceed(); System.out.println("提交事务");
}
catch (Throwable ex) {
System.out.println("回滚事务");
}
finally {
System.out.println("释放资源");
}
return ret;
}