上一篇:【Mybatis】一、SQL映射框架MyBatis源码分析
文章目录
一、 代理模式
- 代理模式是23种设计模式中的一种;
代理模式就是给一个对象提供一个代理,就像我们生活中的中介(租房、留学、相亲),由代理对象控制对原目标对象的访问,所以在代理对象中就可以实现对目标对象功能的增强、扩展、甚至改写; - 为什么需要代理?
因为一个良好的设计不应该轻易的修改,这正是开闭原则的体现:一个良好的设计应该对修改关闭,对扩展开放。而代理正是为了扩展类而存在的,它可以控制对现有类(也就是被代理的类)的访问,通俗来说就是可以拦截对现有类方法的调用并做相应处理;
代理模式应用场景:aop、权限控制、服务监控、缓存、日志、限流、事务、拦截过滤 等;
代理模式分为 静态代理 和 动态代理;
二、静态代理
静态代理就是我们自己手写代理类; aspectJ静态代理(编译期生成代理类)
静态代理可以实现在不修改目标对象代码的前提下,对目标对象的功能进行扩展;
静态代理:(目标接口、目标接口实现、代理类)
- 优点:可以实现不对目标对象进行修改的前提下,对目标对象进行功能的扩展和增强,也就是扩展原功能,不污染原代码。
- 缺点:因为代理对象,需要实现与目标对象一样的接口,如果目标接口类繁多,也会导致代理类繁多,另外一旦目标接口增加新方法,则代理类也需要维护;
三、动态代理
动态代理四种方案:JDK、CGLIB、Javassist、ASM(编码特别复杂,比较少用);
1、JDK动态代理
动态代理,是指在运行期动态的为指定的类生成其代理类;
JDK动态代理会在程序运行时生成一个$Proxy0.Class,该class类在代码中看不到,在磁盘中也没有保存,是程序运行时候在jvm内存中生成的,所以我们感觉动态代理很抽象,要想保存该class文件,我们可以在代码中设置:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
这样就会在运行时候将该class文件保存到我们磁盘上;
动态代理在运行时将接口中声明的所有方法都转移到一个集中的**InvocationHandler.invoke()**方法进行处理,这样在接口方法数量比较多的时候,我们可以进行灵活处理,而静态代理需要在每一个方法进行中转;
- 动态代理是AOP思想的底层实现,当然JDK的动态代理,目标类必须实现某个接口,如果某个类没有实现接口则不能生成代理对象;
/**
* 实现jdk提供的InvocationHandler接口
*
* 实现该接口是为了实现jdk的动态代理
*
* 此类不是真正的代理类,真正的代理的类在jvm内存中,我们看不见摸不着的,这个真正的代理类名字一般是以$Proxy.
*
*/
public class TargetProxy implements InvocationHandler {
//持有目标接口的引用,动态代理为了适配各种目标类型,把引用使用Object
private Object target;
/**
* 使用构造方法对目标接口的引用实现初始化
*
* @param target
*/
public TargetProxy(Object target) {
this.target = target;
}
/**
* 获取真正的代理类
*
* @param interfaces
* @param <T>
* @return
*/
@SuppressWarnings("unchecked")
public <T> T getProxy(Class interfaces) {
//1、jvm内存中生成一个class类;
//2、根据该class类反射创建一个代理对象 $Proxy@564546548
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] {interfaces},
this);
}
/**
* 覆盖InvocationHandler接口的方法
* 该方法会对目标接口的方法进行拦截
*
* @param proxy 这个就是我们那个代理类,就是jdk生成的那个叫$Proxy.代理类
* @param method 就是目标接口的方法,比如 sayhi(), work()的反射对象Method;
* @param args 就是目标接口的方法,比如 sayhi(), work()的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置增强(通知)......");
//中间是调用目标接口的方法
Object result = method.invoke(target, args);
System.out.println("后置增强(通知)......");
return result;
}
}
2、Cglib动态代理
官方Github:https://github.com/cglib
- CGLib (Code Generation Library) 是一个强大、高性能、高质量的代码生成库,它可以在运行时扩展JAVA类并实现接口;
字节码生成库是生成和转换Java字节码的高级API,它被AOP、测试、数据访问框架用来生成动态代理对象和拦截字段访问;
Rafael Winterhalter
来自德国,软件顾问,居住挪威的奥斯陆,也是hibernate的成员之一
CGLib 比 Java 的 java.lang.reflect.Proxy 类更强大的地方在于它不仅可以接管接口类的方法,还可以接管普通类的方法,为JDK的动态代理提供了很好的补充,通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口时,那么CGLIB是一个更好的选择;
在一些开源框架中都采用了cglib:
Hibernate
Spring
Guice
CGLib 的底层是Java字节码操作框架 —— ASM https://asm.ow2.io
3.1 、CGLIB类库结构
- net.sf.cglib.core: 底层字节码处理类,他们大部分与ASM有关;
- net.sf.cglib.transform: 编译期或运行期类和类文件的转换
- net.sf.cglib.proxy: 实现创建代理和方法拦截器的类
- net.sf.cglib.reflect: 实现快速反射类
- net.sf.cglib.util: 集合排序等工具类
- net.sf.cglib.beans: javabean相关的工具类
3.2 、CGLIB实现动态代理
使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK8之前CGLib动态比JDK的动态代理(使用Java反射)效率要高。
唯一需要注意的是,如果被代理的类被final修饰,那么它不可被继承,即不可被代理,同样,如果被代理的类中存在final修饰的方法,那么该方法也不可被代理;
因为CGLib原理是动态生成被代理类的子类;
final类不能被继承,final方法不能被复写;
使用CGLIB,需要两个jar包:
cglib-3.3.0.jar
asm-7.1.jar
cglib-nodep-3.3.0.jar:使用nodep包不需要关联asm的jar包,jar包内部已经包含了asm的类.
cglib-3.3.0.jar:使用此jar包需要关联asm的jar包,否则运行时报错;
package com.xiaojialin.proxy.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* TargetProxy类还不是一个真正的代理类,它是代理类的一部分
*
*/
public class TargetProxy implements MethodInterceptor {
/**
* 获取真正的代理类
*
* //1、jvm内存中生成一个class类;
* //2、根据该class类反射创建一个代理对象 $Proxy@564546548
* return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
* new Class<?>[] {interfaces},
* this);
*
* @param clazz
* @param <T>
* @return
*/
public <T> T getProxy(Class<T> clazz) {
//字节码增强的一个类
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(clazz);
//enhancer.setInterfaces(new Class[] {clazz});
//设置回调类
enhancer.setCallback(this);
//创建代理类
return (T)enhancer.create();
}
/**
* 既可以 sayHello,也可以拦截 sayThanks
*
* @param obj
* @param method
* @param args
* @param proxy
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println(method.getName() + "数据缓存start..........");
//调用目标方法
Object result = proxy.invokeSuper(obj, args);
//就像mybatis一样, 需要自己实现接口
//System.out.println("sayHello...................");
System.out.println(method.getName() + "数据缓存end..........");
return result;
}
}
3.3 、CGLIB实现动态代理CGLib动态代理原理是什么?
-
CGLIB原理: 动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑;
通俗点讲就是,自己生成一个类继承目标类,然后重写要代理的类不是final的方法,自己要代理的逻辑就是在cglib中新写的,这样及就完成了代理。 -
CGLIB底层:使用字节码处理框架ASM,来转换字节码并生成新的类。但不推荐直接使用ASM编码开发,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉,编码比较复杂繁琐;
-
CGLIB缺点:对于final类和final方法,无法进行代理;
四、JDK动态代理与CGLib代理的区别?
1、原理区别:
JDK动态代理是利用反射机制生成一个实现代理接口的类(这个类看不见摸不着,在jvm内存中有这个类),在调用具体方法前调用InvokeHandler来处理。核心是实现InvocationHandler接口,使用invoke()方法进行面向切面的处理,调用相应的拦截和处理;
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理,核心是实现MethodInterceptor接口,使用intercept()方法进行面向切面的处理,调用相应的拦截和处理;
2、 各自局限:
- 1、JDK的动态代理机制只能代理实现了接口的类,而没有实现接口的类就不能实现JDK的动态代理。
- 2、CGLib的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
3、JDK Proxy 的优势:
最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠;
可以平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
4、 CGLib 的优势:
从某种角度看,必须要求调用者实现接口是有些侵入性的实践,CGLib 动态代理就没有这种限制,只操作我们关心的类,而不必为其他相关类增加工作量,一个普通的类也可以实现代理;
五、Javassist动态代理
自1999年以来的Java字节码工程工具包,Javassis使Java字节码操作变得简单,它是用于在Java中编辑字节码的类库,由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的,它加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架;
官网:http://www.javassist.org/
Github:https://github.com/jboss-javassist/javassist
/**
* 不是真正的代理类,是生成代理类的其中一个环节
*
*/
public class TargetProxy implements MethodHandler {
/**
* 获取代理对象
*
* @param clazz
* @return
* @throws InstantiationException
* @throws IllegalAccessException
*/
public Object getProxy(Class<?> clazz) throws InstantiationException, IllegalAccessException {
// 代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
// 设置需要创建子类的父类
proxyFactory.setSuperclass(clazz);
// 通过字节码技术动态创建子类实例
proxyFactory.writeDirectory = "D:/aaa";
Object proxy = proxyFactory.createClass().newInstance();
//在调用目标方法时,Javassist会回调MethodHandler接口方法拦截,
//来实现你自己的代理逻辑,类似于JDK中的InvocationHandler接口
((ProxyObject)proxy).setHandler(this);
// 返回代理类对象
return proxy;
}
@Override
public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
System.out.println("开启事务-------");
//调用目标类的方法
Object result = proceed.invoke(self, args);
System.out.println("提交事务-------");
return result;
}