Java动态代理—JDK DynamicProxy
Posted Moon&&Dragon
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java动态代理—JDK DynamicProxy相关的知识,希望对你有一定的参考价值。
动态代理—JDK DynamicProxy
文章目录
1 概念
代理的概念,在静态代理中已经有写到,那么理解了静态代理后,静态代理有什么弊端呢?其实也是显而易见的,当业务逻辑代码需要添加前置或者后置功能时,我们就需要去书写一个静态代理类,如果业务代码很多,那么代理类又是翻上一番,就会造成代码的臃肿,也会增加我们的重复劳动。那么,这时候,动态代理就来了。
动态代理的实现有俩种方式,可以通过JDK实现动态代理,也可以使用cglib来实现动态代理。动态代理解决了静态代理需要写很多类的弊端。
2 实现动态代理
使用JDK实现动态代理,需要了解俩个核心类:
- Proxy: 该类就是JDK提供的用来创建一个动态代理的类,它有很多方法,其中最常用的就是
newProxyInstance()
- InvocationHandler: 这个是一个接口,我们需要来实现这个接口,重写里面的invoke()方法,这个方法就是去处理我们需要代理类的方法。
2.1 前期准备entity类
结婚接口:
public interface Marry {
/**
* 结婚方法
*/
void marry();
}
人类接口:
public interface Person extends Marry{
/**
* 说话方法
*/
void talk();
}
男人类:
public class Man implements Person{
/**
* 姓名
*/
private String name;
public void setName(String name) {
this.name = name;
}
/**
* 重写结婚方法
*/
@Override
public void marry() {
System.out.println(name+"在结婚");
}
/**
* 重写说话方法
*/
@Override
public void talk() {
System.out.println(name+"在说话");
}
}
2.2 实现InvocationHandler接口
我们创建一个类,来实现InvocationHandler接口,该类的核心方法就是重写的invoke方法,在代理对象中会调用该方法。
/**
* @author 晓龙
* @version 1.8.271
* @ProtectName ProxyDemo
* @ClassName ProxyHandler
* @Description 动态代理处理器
* @createTime 2021年05月23日 12:52:00
*/
public class ProxyHandler implements InvocationHandler {
// 代理的对象
private Person person;
// 代理增强方法名
private String methodName = null;
public void setPerson(Person person) {
this.person = person;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
/**
* @param proxy 代理的对象
* @param method 代理对象的方法,默认的话会把所有方法都代理
* @param args 对象参数
* @return 返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke;
if (methodName != null) {
// 判断代理方法名
if (methodName.equals(method.getName()) || "*".equals(methodName)) {
// 前置方法
beforeMarry();
// 执行,拿到返回值
invoke = method.invoke(person, args);
// 后置方法
afterMarry();
} else {
invoke = method.invoke(person, args);
}
} else {
System.out.println("请设置方法");
invoke = method.invoke(person, args);
}
return invoke;
}
/**
* 前置方法
*/
private void beforeMarry() {
System.out.println("结婚前准备");
}
/**
* 后置方法
*/
private void afterMarry() {
System.out.println("结婚后准备");
}
}
2.3 书写动态代理工具类
该类其实也可以不要,直接在上面实现InvocationHandler接口的类中书写代理方法,这里为了方便理解,就分开来写。
/**
* @author 晓龙
* @version 1.8.271
* @ProtectName ProxyDemo
* @ClassName DynamicProxy
* @Description 动态代理工具类
* @createTime 2021年05月23日 13:01:00
*/
public class DynamicProxy {
private DynamicProxy() {
}
/**
* 获取动态代理对象
*
* @param clazz 类
* @param person 代理对象
* @param methodName 代理对象的方法
* @return 代理对象
*/
public static Person getProxyObject(Class<?> clazz, Person person, String methodName) {
// 实例代理调度器
ProxyHandler proxyHandler = new ProxyHandler();
// 设置代理对象
proxyHandler.setPerson(person);
// 设置代理方法名
proxyHandler.setMethodName(methodName);
// 返回jdk动态代理对象
// 本质:会去修改obj代理对象的class文件,存在内存中,在该class文件中会去调用调度器的invoke方法
return (Person) Proxy.newProxyInstance(clazz.getClassLoader(), person.getClass().getInterfaces(), proxyHandler);
}
}
2.4 测试类
这里测试类对代理前对象和代理后对象进行了比较,因为动态代理默认的话会把类中所有的方法进行代理处理,所以在invoke方法中也简单的设置了方法名判断,这里也进行比较。
/**
* @author 晓龙
* @version 1.8.271
* @ProtectName ProxyDemo
* @ClassName Test
* @Description 测试JDK动态代理
* @createTime 2021年05月23日 12:59:00
*/
public class Test {
public static void main(String[] args) {
// 正常的对象
Man man = new Man();
man.setName("jack");
// 正常结婚
System.out.println("=========正常结婚============");
man.marry();
// 代理对象和增强方法名
System.out.println("=========代理增强结婚=========");
Person proxyMan =DynamicProxy.getProxyObject(Test.class, man,"marry");
// 代理对象结婚---该方法被代理
proxyMan.marry();
// 代理对象其他方法----没有被代理
System.out.println("=========代理未增强============");
proxyMan.talk();
}
}
2.5 结果
3 测试动态代理底层是如何实现的
3.1 动态代理是怎么实现的
其实动态代理底层的实现很好理解,在我们debug的时候就可以发现,我们正常的对象已经被变成了Proxy#的对象。在Proxy类中,其实是把我们的类的class文件进行了修改,然后再去通过新的class实例对象,返回给我们。
- 代理名字的由来:
Proxy类
// 下面这些代码是Proxy类中的源码,我们可以看出代理类的名字来由
private static final String proxyClassNamePrefix = "$Proxy";
...
// 如果包名是空的话,也是有默认包路径,也是在反射工具类中定义好了
if (proxyPkg == null) {
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
long num = nextUniqueNumber.getAndIncrement();
// 这里就是代理类的名字,是有代理类的包+$Proxy+数字组成
String proxyName = proxyPkg + proxyClassNamePrefix + num;
ReflectUtil类
public static final String PROXY_PACKAGE = "com.sun.proxy";
-
修改class类
知道了名字的由来,继续跟着往下看,会发现,创造出了一个byte数组
Proxy类
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
通过ProxyGenerator.generateProxyClass()方法,把代理名和接口等信息传入后,生成了一个byte数组,那么这个数组是什么呢?其实这个数组就是新的class文件,也就是代理后的class文件。
有了新的class文件,那么返回的对象也就很简单了。
3.2 为什么JDK动态代理对象必须有接口?
在使用JDK动态代理的时候,我们会发现,我们想要代理一个类的时候,我们必须要确认这个类有接口,我们只能去代理接口,这是为什么呢。通过下面一个小测试就可以很清晰的了解到了。
3.2.1 获得代理对象的class文件
动态代理的底层是去生产一个新的类文件,但是这个类文件是存在在内存中,我们无法直观的看到这个类文件做了什么事情。那么我们通过IO把生成的类文件写到磁盘来进行查看。
IO写出class文件
/**
* @author 晓龙
* @version 1.8.271
* @ProtectName ProxyDemo
* @ClassName WriteClass
* @Description 写出代理生成class文件
* @createTime 2021年05月23日 16:08:00
*/
public class WriteClass {
public static void main(String[] args) throws Exception {
// 正常的对象
Man man = new Man();
man.setName("jack");
// 通过和Proxy类中一样的方法,模拟一个class文件的生成
byte[] proxyClassFile = ProxyGenerator.generateProxyClass("Jack",man.getClass().getInterfaces(),1);
// 通过IO写出到磁盘
FileOutputStream out = new FileOutputStream("/Users/xiaolong/Desktop/Jack.class");
out.write(proxyClassFile);
out.flush();
out.close();
}
}
运行上述代码后,我们在桌面发现了生成的class文件,但是该class文件我们是无法直接打开,那么我们可以通过IDEA的反编译功能进行查看,我们把它拷贝到项目的target目录下。
3.2.2 分析class文件
我们打开该class文件
import com.moon.dynamicProxy.pojo.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
/*这里的代理对象是默认继承了Proxy类,然后去实现我们的接口,因为java的单继承多实现,继承已经被Proxy占用,那么我们必须要使用接口*/
public class Jack extends Proxy implements Person {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
private static Method m4;
public Jack(InvocationHandler var1) throws {
super(var1);
}
......
// 这里主要看我们的代理的方法
public final void marry() throws {
try {
/*
我们发现我们的代理方法默认会去调用父类的h对象的invoke方法,并且把原来的方法m4传进去了
那么这个super.h.invoke()是什么?在Proxy类中有这样的一段属性
protected InvocationHandler h;
在我们调用Proxy类的newProxyInstance()方法时,我们传入的参数是
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h){...}
这里我们在最后传入了我们实现的InvocationHandler接口类,所以说,这里调用的父类的h的invoke方 法,其实就是调用了我们写的invoke方法。
*/
super.h.invoke(this, m4, (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.moon.dynamicProxy.pojo.Person").getMethod("talk");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m4 = Class.forName("com.moon.dynamicProxy.pojo.Person").getMethod("marry");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
以上是关于Java动态代理—JDK DynamicProxy的主要内容,如果未能解决你的问题,请参考以下文章