深入浅出java反射应用一一动态代理
Java高级之反射
反射应用之动态代理
问题的起源
- 适逢学生暑期,现在驾校里有许多学生趁着假期开始学车,目前正在练习科目二,整体流程固定,如下:
/**
* 驾校学生接口
*/
interface DrivingStudent{
//准备科目二的考试
void prepare();
}
/**
* 正常驾校学生的考试流程
*/
class CommonStudents implements DrivingStudent{
@Override
public void prepare() {
System.out.println("在驾校正常上班时间点,听教练讲要点、然后练习,熟练后去考试!");
}
}
-
一切都在按部就班的进行着。这天,新加入了一个学员,她是驾校老板的女儿,作为老板的女儿,教练为了让老板开心好给自己加薪,自然要给她VVIP的练车待遇:在
prepare()
方法执行之前,先好好的跟她讲一些注意事项,prepare()
方法执行之后,也就是驾校下班后,单独指导她练车技巧以及传授经验。 -
现在,科二整体流程固定,可以比喻为类
CommonStudents
已经加载到内存,成为运行时的类,众所周知,java在运行时的类是不允许被修改的,那么教练该如何实现给老板女儿开小灶的功能?
问题的扩展
- 回到
java
编程世界中,Spring
框架在当下非常热门,AOP
、IOC
这些耳熟能详的词在java
编程世界中经常出现,那么AOP解决了码农的什么样需求? - 联想到开发应用程序过程中,我们或多或少都会遇到类似于这样的需求:比如为方便排查问题,要在某些函数(例如上面的:
prepare()
函数)的调用前后加上相应的日志记录;为了保持数据的安全性,要给某些函数加上事务的支持等等。xml
和注解的盛行,使我们程序员可以在xml
和注解中声明要在哪一类函数前后加上日志以及日志等,但是这些类已经是运行状态,我们声明了要在这些运行中的函数前后加功能,但是运行时的类java
是不允许被修改的,那么该如何实现此需求呢?
问题的思路
分析
- 在上篇反射1中我们提到,
java
世界中有这么一个类java.lang.Class
,它是描述类的类,它对应的便是那一个个运行时的类,我们可以通过它去拿到运行时的类的所有类型信息,包括方法、属性等。也可以通过它去创建一个运行时的类的对象。 - 回到驾校的问题:教练希望能给老板女儿开小灶,但是对外宣称也必须是走的是驾校的正常流程,也就是调用的是
prepare()
方法;回到java
编程世界,比如有个添加数据的函数add()
,我们希望在add()
函数前后加上日志以打印出入参出参。但是在调用方看来,他应该只是调用了add()
函数,他并不知道额外做了添加日志其他的操作。 - 换句话说,我们通过
Class
获取到了运行时的prepare()
、add()
方法,并在这些方法前后添加了自己的额外功能,而调用方还认为自己只是调用的是正常的方法。
方案整理
- 为了实现了新的功能,我们可以在类运行时动态的去创建一个新的类,这个新的类便是我们要实现某个函数的类(即被代理类也是目标对象)的代理类。
- 以教练为例,创建目标对象
CommonStudents
的代理类的需要我们考虑两个问题:CommonStudents
被加载到内存中,成为运行时的类,如何通过它创建一个代理类?- 当
CommonStudents
类中的prepare()
方法被调用时,如何让它去调用代理类中在prepare()
方法的基础上添加了额外功能的方法?
问题的解决
解决流程
-
解决问题1:Proxy 是专门完成代理的操作类,通过该类为一个或多个接口动态地生成实现类。它是所有动态代理类的父类;创建一个代理对象的方法源码如下:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException{ ********** }
-
解决问题2:创建一个类实现接口InvocationHandler的类,在该类中实现
invoke()
方法,在该方法中添加上我们需要添加的逻辑。这样在调用目标对象的方法时,调用的是代理类中的invoke()
方法。
具体实现
-
解决问题2的代码,也就是当调用'目标对象'
CommonStudents
的prepare()
方法时,如何使其重定向到一个新的方法invoke()
,也就是有额外功能的方法。class MyInvocationHandler implements InvocationHandler{ //目标对象,可以通过反射来调用目标对象的方法 private Object target; public MyInvocationHandler(Object target){ this.target = target; } //重写invoke方法,添加上自己的功能 @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; } }
-
主方法,内含解决问题1的代码。
public static void main(String[] args) { System.out.println("========正常学生流程开始==========="); DrivingStudent student = new CommonStudents(); //正常学生的流程 student.prepare(); System.out.println("========正常学生流程结束==========="); System.out.println("*********************************"); System.out.println("=======老板女儿练车流程开始=========="); //老板女儿的练车流程 //当CommonStudents方法被调用时,走invoke()方法,因此要将CommonStudents当作参数传入。 InvocationHandler handler = new MyInvocationHandler(student); //java提供了动态代理来解决在运行时动态创建一个代理类的方案 DrivingStudent bossStudent = (DrivingStudent)Proxy.newProxyInstance(student.getClass().getClassLoader(), student.getClass().getInterfaces(), handler); bossStudent.prepare(); System.out.println("=======老板女儿练车流程结束=========="); }
-
运行结果
问题解决的思考
-
jdk动态代理为什么需要接口?
-
简单理解下源码:
Proxy.newProxyInstance
中几行重要代码:
// public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); ...... //这一步是查找并生成代理类的Class Class<?> cl = getProxyClass0(loader, intfs); ...... //生成的代理类中存在参数为InvocationHandler的构造函数 final Constructor<?> cons = cl.getConstructor(constructorParams); ...... //将实现了InvocationHandler的类当作参数传入到newProxyInstance方法后,这一行是利用这个参数创建一个代理类 return cons.newInstance(new Object[]{h}); ...... }
getProxyClass0(loader, intfs)
中逻辑:
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { ...... //重点在:ProxyClassFactory return proxyClassCache.get(loader, interfaces); }
ProxyClassFactory
的主要操作:
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { ...... { //验证参数、接口等 ...... //生成byte类型的代理类,并将其返回 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); ...... } }
-
重点理解一下生成的
byte[]
类型的代理类。将byte[]类型的代理类写出到文件上,并且进行反编译,代码如下:byte[] classFile = ProxyGenerator.generateProxyClass( "$Proxy0", new Class[]{DrivingStudent.class}); //自定义的目录 String path = "*****"+ "$Proxy0" + ".class"; try (FileOutputStream fos = new FileOutputStream(path)) { fos.write(classFile); fos.flush(); System.out.println("byte[]类型代理类class文件写入成功"); } catch (Exception e) { System.out.println("byte[]类型代理类发生错误"); }
-
写入成功后,对生成的代理类进行反编译后得到的类如下:
public final class $Proxy0 extends Proxy implements DrivingStudent { private static Method m1; private static Method m2; private static Method m3; private static Method m0; //看到这里我们能看到,自动生成的代理类对象有一个入参是InvocationHandler的构造函数,这样在创建代理类时,通过该构造方法便将被代理类(实现了DrivingStudent)和InvocationHandler联系了起来。 public $Proxy0(InvocationHandler paramInvocationHandler) { super(paramInvocationHandler); } //省略的是equals和toString方法 ...... //当调用接口中的prepare()方法时,实际上走的是invoke()方法。invoke()方法中有我们自己加的逻辑 public final void prepare() { try { this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException error) { throw null; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } //省略的是hashCode方法 ...... static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("com.practice.reflect.DrivingStudent").getMethod("prepare", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; } catch (NoSuchMethodException noSuchMethodException) { throw new NoSuchMethodError(noSuchMethodException.getMessage()); } catch (ClassNotFoundException classNotFoundException) { throw new NoClassDefFoundError(classNotFoundException.getMessage()); } } }
-
回到最初的问题:jdk生成的动态代理类是继承于
Proxy
类的,而java类是不允许多继承的,为了使代理类和目标对象建立联系,就必须实现一个接口。
结论
java
动态代理是根据被代理对象动态的创建了一个新类,当调用被代理对象的方法时,会跳转到调用InvocationHandler
中的invoke()
方法,在创建代理类方法newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
的入参我们看到,它是根据字节码文件自动生成的代理类,并且传入了InvocationHandler
参数。- 通过反编译生成的动态代理类得到的代码,我们看到代理类继承了
Proxy
类以及实现了目标对象实现的接口,并且该代理类中的构造函数用到了上面传入的InvocationHandler
参数,这样便将目标对象和InvocationHandler
联系了起来,InvocationHandler
中的invoke()
方法中定义了我们需要新添加的功能,当调用目标对象的方法时,使其跳转到调用invoke()
方法,这样便实现了在运行时创建新的类以满足我们新增功能的需求。
原创不易,欢迎转载,转载时请注明出处,谢谢!
作者:潇~萧下
原文链接:https://www.cnblogs.com/manongxiao/p/13449480.html
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步