1. 动态代理相关概念
- 目标类:程序员自己写的、普通的业务类,是需要被代理的类;
- 目标方法:目标类中的实现业务的具体方法,为了精简,只写核心业务代码,因此需要代理类来增强功能;
- 增强器:是给目标方法增强功能的类,需要继承InvocationHandler接口,实现invoke(Object proxy, Method method, Object[] args)方法;
- 代理类:由JDK相关类在程序执行过程中、为了给目标类的相关目标方法增强功能而生成的、存在于内存中的类;
- 动态代理:在程序执行过程中,由JDK相关类来创建代理对象,通过代理对象来执行相关方法,在不改变目标类的业务代码的前提下,达到给目标类相关方法增强功能的目的,使得程序的可扩展性大大增强;
2. 动态代理实现方式
- jdk动态代理:使用jdk的Proxy、InvocationHandler来创建代理对象,
- 原理:通过实现目标类的父接口,重写目标类的相关方法
- 要求:目标类必须实现接口
- cglib动态代理:第三方的工具库,创建动态代理,
- 原理:继承,通过继承目标类,创建具有增强功能的子类,
- 要求:目标类不能是final类型,目标增强方法不能是final
3. JDK动态代理实例演示
- 我现在要科普一下水的作用,核心类是Water,水可以饮用、清洗;
public interface Drinkable{
void drink();
}
public interface Washable{
void wash();
}
- 在核心类Water中,我们只说核心功能:
public class Water implements Drinkable, Washable{
@Override
public void drink(){
System.out.println("喝水能够解渴");
}
@Override
public void wash(){
System.out.println("水能用来清洗衣服");
}
}
- 但是科普讲解还需要把事情的来龙去脉讲清楚,增加的讲解放在增强器中进行:
public class DrinkHandler implements InvocationHandler{
private Object target = null;//目标对象,通过构造器传入
public DrinkHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println("身体内产生能量的化学反应要消耗水...");
method.invoke(target, args);
System.out.println("身体产生的废物会溶解在水中排出体外...");
return null;//目标方法没有返回值的话,就返回null
}
}
public class WashHandler implements InvocationHandler{
private Object target = null;
public WashHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println("衣服穿过就脏了...");
method.invoke(target, args);
System.out.println("洗过的衣服需要在太阳下晾晒才会干的...");
return null;
}
}
- 要生成代理对象需要的步骤:
1> 创建目标对象
2> 创建增强器
3> 根据JDK相关类来创建代理对象(底层机制是反射)
4> 调用代理对象的方法,实现功能增强
public class WaterScience{
public static void main(String[] args){
//1.创建目标对象
Water water = new Water();
//2.创建增强器
DrinkHandler drinkHandler = new DrinkHandler(water);
WashHandler washHandler = new WashHandler(water);
//3.根据JDK相关类来创建代理对象(底层机制是反射)、
Drinkable drinkProxy = (Drinkable) Proxy.newProxyInstance(
water.getClass().getClassLoader(),
water.getClass().getInterfaces(),
drinkHandler);
Washable washProxy = (Washable) Proxy.newProxyInstance(
water.getClass().getClassLoader(),
water.getClass().getInterfaces(),
washHandler);
//4.调用代理对象的方法,实现功能增强
drinkProxy.drink();
System.out.println("========================");
washProxy.wash();
}
}
//以下是输出结果:
身体内产生能量的化学反应要消耗水...
喝水能够解渴
身体产生的废物会溶解在水中排出体外...
========================
衣服穿过就脏了...
水能用来清洗衣服
洗过的衣服需要在太阳下晾晒才会干的...
4. 源码解析
1> 创建目标对象
2> 创建增强器
3> 根据JDK相关类来创建代理对象(底层机制是反射)
4> 调用代理对象的方法,实现功能增强
- 四个步骤中,有难点的是2、3两步
- 增强器要实现InvocationHandler必须实现的invoke方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
return null;
}
- 创建代理对象的JDK相关API:
Proxy.newProxyInstance(
water.getClass().getClassLoader(),
water.getClass().getInterfaces(),
drinkHandler);
- 先进入
Proxy.newProxyInstance
方法(只保留核心语句):
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException{
//记住此处参数列表是目标类的类加载器、目标类的父接口、增强器对象
Class<?> cl = getProxyClass0(loader, intfs);
//此方法,能够得到代理对象的类对象,是个核心方法,进入该方法
- 进入
getProxyClass0(loader, intfs)
方法:
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
return proxyClassCache.get(loader, interfaces);
}
- 进入
proxyClassCache.get(loader, interfaces)
方法(只保留最核心语句):
public V get(K key, P parameter) {
// create subKey and retrieve the possible Supplier<V> stored by that subKey from valuesMap
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
//此处仍然没有到最底层,记住key是目标类的类加载器,parameter数组内存放目标类的所有父接口
- 进入
subKeyFactory.apply(key, parameter)
方法(注意此处subKeyFactory是BiFunction接口类型的,要到它的实现类ProxyClassFactory中寻找核心方法apply):
//这个是核心方法,涉及【代理类名称】、【代理类字节码文件】、【代理类对象的生成】
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 所有代理类名的前缀
private static final String proxyClassNamePrefix = "$Proxy";
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
long num = nextUniqueNumber.getAndIncrement();
/*【代理类名称】拼接:这就是为什么代理类后面会有数字*/
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*【代理类字节码文件】:这个字节码以数组的形式存在于内存,没在磁盘盘上,
后续我们可以把这个字节数组写入磁盘,以便直观查看代理类的字节码文件*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
/*【代理类对象的生成】:这是一个native方法,得到代理类的类对象,
就是类加载中把磁盘的字节码读入方法区的那一步*/
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
- 为了直观感受到代理类,我们把代理类的字节码文件输出到磁盘上:
//把上文中:JDK动态代理实例中的代理类字节码文件输出到磁盘上:
public static void createProxyClass() throws IOException{
String proxyName = "$proxy0";
/*生成指定的代理类字节码:*/
Class<?>[] interfaces = Water.class.getInterfaces();
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
/*指定字节码存放路径*/
String path = "E:\\IDEA_2020_2_4\\IdeaProjects\\springLearn\\aop-leadin\\src\\com\\cpf\\proxyClass\\";
File file = new File(path + proxyName + ".class");
if(!file.exists()){
file.createNewFile();
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(proxyClassFile);
fileOutputStream.flush();
fileOutputStream.close();
}
- 我们来解析一下这个代理类字节码:
import com.cpf.service.Drinkable;
import com.cpf.service.Washable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
/*代理类继承了Proxy,唯一的用处是复用了Proxy中的InvocationHandler增强器对象,
但其实完全可以在代理类中出现增强器对象,这就是cglib的思路*/
/*代理类实现了目标类的所有父接口,这就是为什么要求目标类一定要有接口,因为代理类已经有父类了,只能实现接口*/
public final class $proxy0 extends Proxy implements Drinkable, Washable {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
private static Method m0;
/*代理类的构造器,传入增强器对象,这在Proxy.newProxyInstance()方法的后续代码中会用到,
你或许已经发现,创建代理类的类对象只使用了【目标类加载器】【目标类父接口】这两个参数,
第三个参数【增强器】就是通过这个构造器赋值给Proxy的属性h的,由三个参数来形成代理对象*/
public $proxy0(InvocationHandler var1) throws {
super(var1);
}
/*这就是目标类中需要增强的两个目标方法drink wash,是从两个父接口中得到的信息;
由动态绑定机制,我们在用代理对象调用方法时,实际上会执行这里的drink方法,而不是目标类的drink方法*/
public final void drink() throws {
try {
/*super就是Proxy,h就是那第三个参数【增强器对象】,invoke就是我们在增强器中写的方法;
第一个参数this,表明invoke的第一个参数就是代理对象
第二个参数m3,查看后面的静态代码块可知,就是通过反射获得的drink的方法对象
第三个参数null,这是因为drink()方法没有参数
*/
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void wash() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
/*系统顺手帮忙生成了类中最常用的三个方法equals toString hasnCode*/
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
/**静态代码块,使用反射,获得目标类的目标方法,这些信息,是通过第一个参数【目标类加载器】获得的/
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.cpf.service.Drinkable").getMethod("drink");
m4 = Class.forName("com.cpf.service.Washable").getMethod("wash");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
- 至此,我们就搞清楚了代理类的类对象的来源,也弄明白了代理类的基本结构,
接下来继续回到第一步中newProxyInstance()
的核心代码,继续分析:
@CallerSensitive
//记住这个Proxy类中的属性,他就是增强器的类对象,用来获得代理类的构造器对象
private static final Class<?>[] constructorParams = { InvocationHandler.class };
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Class<?> cl = getProxyClass0(loader, intfs);
//通过代理类的类对象得到构造器对象,就是那个需要传入增强器h的构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
//由于Constructor类的newInstance方法时可变长参数列表,因此此处把增强器对象伪装进一个数组中,
//实际上就是把这个增强器对象传入代理类的构造器中来构造器代理对象...
return cons.newInstance(new Object[]{h});
}
- 至此,我们理解了【代理类的字节码】【代理类对象的由来】【代理类与invoke的互动】,接下来解析一下增强器中的invoke()方法:
public class DrinkHandler implements InvocationHandler{
private Object target = null;//目标对象,通过构造器传入
/*目标对象必须传入增强器,因为目标方法还必须由目标对象来执行,
增强器只负责增强的功能*/
public DrinkHandler(Object target){
this.target = target;
}
/**
* @param proxy 回忆一下代理类字节码文件中代码: super.h.invoke(this, m3, (Object[])null);
* 这个super就是Proxy类,h就是它的InvocationHandler属性,
* this就是代理类对象,所以此处的名字就是proxy,实至名归.
* @param method 代理类字节码中静态代码块:m3 = Class.forName("com.cpf.service.Drinkable").getMethod("drink");
* 可以知道这个就是反射得到的方法对象.
* @param args 由于drink()方法没有参数列表,因此字节码文件中是null,args代表了目标方法的参数类对象,
* 注意是参数类对象,不是参数对象,如果原参数是一个String,此处就应该是String.class
* @return 由于目标方法可能有返回值,此处有必要设置一个返回值,如此例中drink没有返回值,invoke就返回null.
* @throws Throwable 目标方法的执行是通过反射,反射有可能找不到对应的方法,因此要抛出异常.
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println("身体内产生能量的化学反应要消耗水...");
method.invoke(target, args);
System.out.println("身体产生的废物会溶解在水中排出体外...");
return null;//目标方法没有返回值的话,就返回null
}
}
- 至此,动态代理底层分析完毕
5. 结语
- 动态代理是一种技术,更是一个思想,在不修改业务层代码的前提下,增强业务层的功能,增强器改名换姓,摇身一变就形成了面向切面编程;
- 由于动态代理的代码写法很多,步骤也可以有变动,为了规范动态代理,就形成了aop(面向切面编程)技术,可以这样说:aop是动态代理的规范;
- spring本身实现了aop技术,但是非常的冗余和繁琐,业内公认的aop实现框架是aspectJ,spring也支持aspectJ框架,只是需要另外引入aspectJ的依赖;