Java1.8动态代理:
- JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的 业务实现类对象 以及 方法名 ,动态地创建了一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。我们需要做的,只需指定代理类的预处理、调用后操作即可。
- 其中动态代理分为两种:JDK 动态代理 和CGLIB动态代理, 本文主要讲述 JDK代理,CGLIB代码下一篇会讲到
简单实现:
- 计算一个 Sql 调用的总时间
代码逻辑:
- 创建一个SqlService 接口类:
public interface SqlService {
void executeSql() throws InterruptedException;
}
- 创建 SqlServiceImpl 实现 SqlService 接口:
/**
* Describe: SqlService 的实现
*/
public class SqlServiceImpl implements SqlService {
@Override
public void executeSql() throws InterruptedException {
System.out.println("Sql 开始执行.....");
Thread.sleep(1000);
System.out.println("Sql 执行结束.....");
}
}
- 创建 SqlInvocationHandler 并实现 InvocationHandler 接口, 其中 invoke() 中计算 业务逻辑执行的时间。
Object result = method.invoke(target, args); 此处是调用业务逻辑关键。
public class SqlInvocationHandler<T> implements InvocationHandler {
// 需要代理的对象
private T target;
// 接受需要代理对象
public SqlInvocationHandler(T target) {
this.target = target;
}
// 代理方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 开始执行时间
Long startTime = System.currentTimeMillis();
// 此处是调用业务逻辑 关键
Object result = method.invoke(target, args);
// 执行结束
Long endTime = System.currentTimeMillis();
System.out.println(target.getClass().getName()+"执行executeSql耗时"+(endTime-startTime)+"ms");
return result;
}
}
- 创建 Test 方法:
public class Test {
public static void main(String[] args) throws InterruptedException {
// 创建一个实例对象,这个对象是被代理的对象
SqlService target = new SqlServiceImpl();
// 创建一个与代理对象相关联的 InvocationHanler
InvocationHandler handler = new SqlInvocationHandler<SqlService>(target);
/**
* 创建一个代理对象 SqlService 来代理 target, 代理对象的每个执行方法都会执行 Invovrtion 中的 invoke方法
* ClassLoader loader : 类加载器
* Class<?>[] interfaces : 被代理对象的 接口数组
* InvocationHandler h : 代理类
*/
SqlService sqlService = (SqlService) Proxy.newProxyInstance(SqlService.class.getClassLoader(), new Class<?>[]{SqlService.class}, handler);
// 代理执行方法
sqlService.executeSql();
}
}
执行结果:
Sql 开始执行.....
Sql 执行结束.....
com.lot.jdkproxy.SqlServiceImpl执行executeSql耗时1000ms
代理类写入成功
动态代理逻辑分析:
- SqlService 和 SqlServiceImpl 就是正常的业务逻辑,不详聊
- 重点在 SqlInvocationHandler 类中, 首先接收到 需要代理的 对象并且 赋值到 target ,并实现java.lang.reflect.InvocationHandler 中 invoke() 方法,其中三个参数:
Object proxy : 动态代理后的对象。
Method method :方法
Object[] args : 执行方法时的参数。
其中 method.invoke(target, args) 则是执行业务逻辑代码。 - 在 Test 中 第一步,先实例化需要被代理的对象(target),然后传入到代理类中(SqlInvocationHandler), 最重要的是 : Proxy.newProxyInstance(SqlService.class.getClassLoader(), new Class<?>[]{SqlService.class}, handler) 这行代码,其来生成代理类,具体功能是由JDK 中 java.lang.reflect.Proxy 来实现的,原理下面讲,最后则是用代理类来执行业务逻辑
Proxy.newProxyInstance源码逻辑拆分:
- 当我们通过 java.lang.reflect.Proxy#newProxyInstance() 生成的 sqlService 对象 是 SqlServiceImpl 类实例化的吗,其实不是,当 Debug 运行main 方法时我们可以在IDEA 中 Debuggeer栏目 看到 sqlService 的类是 Proxy0:
- 我们在Test 类main 方法后面加入以下代码,讲Proxy0 反编译到 Proxy0.class 中:
// 代理执行方法
sqlService.executeSql();
// -------------------- 将代理类反编译到文件中 ------------------------
byte[] classFile = ProxyGenerator.generateProxyClass(sqlService.getClass().getSimpleName() , sqlService.getClass().getInterfaces());
// Path: 为反编译后保存的文件路径
String path = "F:/mycode/Git/spring-mvc/learn/design-pattern-learn/src/main/java/com/lot/jdkproxy/Proxy0.class";
try {
FileOutputStream fos = new FileOutputStream(path);
fos.write(classFile);
fos.flush();
System.out.println("代理类写入成功");
} catch (Exception e) {
e.printStackTrace();
}
- 通过 Idea 打开 Proxy0.class 如下 :
/**
* 代理类 其实是被代理类 的接口另一个实现
*/
public final class $Proxy0 extends Proxy implements SqlService {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1){
super(var1);
}
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.lot.jdkproxy.SqlService").getMethod("executeSql");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
/**
* 代理的方法
*/
public final void executeSql() throws InterruptedException {
try {
// super.h : 是main方法中实例化的InvocationHandler。
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | InterruptedException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
/**
* 省略 equals 方法....
* 省略 toString 方法....
* 省略 hashCode 方法....
*/
}
- 通过查看 Proxy 原码就一目了然了,以上面的代码为例,整个动态代理的流程如下:
Proxy.newProxyInstance : 生成代理类Proxy0并实例化 赋值sqlService,在执行sqlService.executeSql()时 : 先 $Proxy0.executeSql() ,再SqlInvocationHandler.invoke(),最后则是SqlServiceImpl.executeSql() 结束整个流程
$Proxy0 生成过程:
- 下面的代码是由 java.lang.reflect.Proxy#newProxyInstance 复制出来,加一些我自己的理解,大家有兴趣可以看一下:
/**
* @param loader 类加载器
* @param interfaces 接口数组
* @param h 代理类
* @return 生成的代理对象
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
// 生成代理类 ($Proxy0)
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 获取代理类的构造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 返回实例化的对象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
总结:
使用JDK 动态代理,该类必须要实现一个接口,然后通过动态代理生成一个改接口的代理类Proxy0 (数字依次变大),实际调用的是 Proxy 类,并在其内部 执行InvocationHandler Invoke()方法,最后调用业务类(兄弟类)的方法。
后续:
1、静态代理: https://blog.csdn.net/zhangyong01245/article/details/90519769
2、Java 动态代理实现: https://blog.csdn.net/zhangyong01245/article/details/90598309
3、CGLIB动态代理以及逻辑: https://blog.csdn.net/zhangyong01245/article/details/90644933
以上是博主自己的理解与想法, 如有异议请大家留言,博主一定会第一时间解决