彻底理解Java反射以及动态代理中对反射的应用
2021/8/5 14:06:41
本文主要是介绍彻底理解Java反射以及动态代理中对反射的应用,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
反射 (Reflection) 是 Java 的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。
简而言之,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
反射有什么用,其实最主要的用处就两个:
- 根据类名在运行时创建实例(类名可以从配置文件读取,不用new)
- 通过Method.invoke()执行方法
但是这些不难理解,反射的整个流程比较难理解。
假如你写了一段代码:
Object o = new Object();
运行了起来,首先JVM会启动,你的代码会编译成一个.class文件,然后被类加载器加载进JVM的内存中,在方法区创建了Object类的Class对象,注意这个不是new出来的对象,而是类的类型对象,每个类都只有一个Class对象,作为方法区类的数据结构的接口。JVM创建对象前,会先检查类是否加载,寻找类对应的Class对象,若已经加载好,则为你的对象分配内存,初始化也就是代码执行Object类的无参构造函数。
上面的流程就是我们自己写好了创建实例的代码扔给JVM去跑,跑完了,实例也就生成成功了。那如果出现以下场景:服务器上遇到某个请求,这个请求要求运行过程中遇到某种情况时才创建某个类的实例,难道要停下来自己写代码new出这个对象吗?当然不行,反射是什么呢,当我们的程序在运行时,可能需要动态的加载一些类并创建其实例,这些类可能最初用不到,只有在需要时才加载到JVM,即运行时按需加载,这样的好处对于服务器来说不言而喻。例如Spring,各种各样的bean是以配置文件的形式配置的,需要用到哪些bean就加载哪些并生成实例配,Spring容器就会根据需求去动态加载,程序能健壮地运行。
由于反射本身比较抽象,所有需要从类加载、Class对象创建、实例创建整个过程来分析。
JVM是如何构建一个实例的
假设main方法中有以下代码:
Person a = new Person();
很多初学者会以为整个创建对象的过程是下面这样的
javac Person.java java Person
用图表达就是下面的过程,main线程中创建一个Person实例,线程的方法栈中保存一个指向堆中对象的指针(引用)。
以上过程过于粗放,更细致的过程如下:
无论是通过new还是反射创建实例,都离不开.class文件及Class对象。找到一个.class文件,用sublime text以UTF-8编码形式打开,是下面这样的一片。
.java源码是给人类读的,而.class字节码是给计算机读的。JVM对.class文件也有一套自己的读取规则,这些“乱码”JVM能够愉快的识别。
类加载器
.class文件是由类加载器加载到JVM中的,类加载器非常复杂。但是其核心方法只有loadClass(),传入要加载的类的全限定名即可。抽象类ClassLoader中定义的loadClass()方法的源码如下:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { //互斥锁,防止多个线程同时加载一个类 synchronized (getClassLoadingLock(name)) { //先从缓存查找该class对象,找到就不用重新加载 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
加载一个类可以分为3步:
- 检查是否已经加载过了,findLoadedClass方法做的事,避免重复;
- 优先交给自己的父加载器加载
- 以上两步均失败,就自己加载
ClassLoader是抽象类,无法通过new创建其实例,其findClass()方法的源码如下:
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
由此可见,findClass方法必须交给子类去重写,在里面自定义寻找类的逻辑。
defineClass()是ClassLoader定义的方法,其根据从.class文件中读取到的字节数组byte[] 构造出一个Class对象。其底层最终调用的是多个native方法:
private native Class<?> defineClass0(String name, byte[] b, int off, int len, ProtectionDomain pd); private native Class<?> defineClass1(String name, byte[] b, int off, int len, ProtectionDomain pd, String source); private native Class<?> defineClass2(String name, java.nio.ByteBuffer b, int off, int len, ProtectionDomain pd, String source);
综合以上描述,ClassLoader加载.class文件并生成Class对象的大致流程如下:
Class类
现在,.class文件被类加载器加载到内存中,类加载器也根据其字节数组创建了对象的Class对象。Class对象是Class类的实例。Class是类的类型对象,用来描述类。通常一个类至少会包括以下信息:
- 方法或字段的权限修饰符
- 类名
- 字段
- 方法
- 构造器
- 注解
- 父类或实现的接口
所以想要定义一个Class类来完整描述任意一个类,以上信息必然要在Class类中封装好。查看java.lang.Class的源码,可以发现以上信息都通过静态内部类封装好了:
private static class ReflectionData<T> { volatile Field[] declaredFields; volatile Field[] publicFields; volatile Method[] declaredMethods; volatile Method[] publicMethods; volatile Constructor<T>[] declaredConstructors; volatile Constructor<T>[] publicConstructors; // Intermediate results for getFields and getMethods volatile Field[] declaredPublicFields; volatile Method[] declaredPublicMethods; volatile Class<?>[] interfaces; // Value of classRedefinedCount when we created this ReflectionData instance final int redefinedCount; ReflectionData(int redefinedCount) { this.redefinedCount = redefinedCount; } }
由于字段、方法、构造器的信息比较多,例如字段有字段类型、访问权限符,方法有方法名、参数、返回值、访问权限符等信息。所以专门创建了Field类、Method类及Constructor类来描述类的字段、方法及构造器。
一个Person类中的所有信息,最终都被解析成一个Class对象,查看Class类的定义可知,一个Class对象可以完整地彻底地描述任意一个类。
再来看Class类的方法,构造函数源码如下:
private Class(ClassLoader loader) { // Initialize final field for classLoader. The initialization value of non-null // prevents future JIT optimizations from assuming this final field is null. classLoader = loader; }
Class类的构造器是私有的,我们无法手动new一个Class对象,只能由JVM创建。JVM在构造Class对象时必须传入一个类加载器。
Class.forName()方法源码如下,还得靠类加载器。
@CallerSensitive public static Class<?> forName(String className) throws ClassNotFoundException { Class<?> caller = Reflection.getCallerClass(); return forName0(className, true, ClassLoader.getClassLoader(caller), caller); }
newInstance()方法源码如下:
@CallerSensitive public T newInstance() throws InstantiationException, IllegalAccessException { if (System.getSecurityManager() != null) { checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false); } // NOTE: the following code may not be strictly correct under // the current Java memory model. // Constructor lookup if (cachedConstructor == null) { if (this == Class.class) { throw new IllegalAccessException( "Can not call newInstance() on the Class for java.lang.Class" ); } try { Class<?>[] empty = {}; //获取无参构造函数,getConstructor0函数可能抛出NoSuchMethodException异常 final Constructor<T> c = getConstructor0(empty, Member.DECLARED); // Disable accessibility checks on the constructor // since we have to do the security check here anyway // (the stack depth is wrong for the Constructor's // security check to work) java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() { public Void run() { c.setAccessible(true); return null; } }); cachedConstructor = c; //如果找不到无参构造函数,就会进入catch } catch (NoSuchMethodException e) { throw (InstantiationException) new InstantiationException(getName()).initCause(e); } } Constructor<T> tmpConstructor = cachedConstructor; // Security check (same as in java.lang.reflect.Constructor) int modifiers = tmpConstructor.getModifiers(); if (!Reflection.quickCheckMemberAccess(this, modifiers)) { Class<?> caller = Reflection.getCallerClass(); if (newInstanceCallerCache != caller) { Reflection.ensureMemberAccess(caller, this, null, modifiers); newInstanceCallerCache = caller; } } // Run constructor try { //通过Constructor对象的newInstance方法来创建实例 return tmpConstructor.newInstance((Object[])null); } catch (InvocationTargetException e) { Unsafe.getUnsafe().throwException(e.getTargetException()); // Not reached return null; } }
查看源码可知,newInstance()底层就是在调用无参构造器方法。
拿到一个类的Class对象后,要想通过该Class对象去创建类的实例,其实最终是通过Constructor对象来做的,如果该类没有无参构造函数(即找不到符合条件的contructor对象)就无法使用clazz.newInstance()。
反射API
反射API日常开发中反射的最终目的主要有两个:
- 创建实例
- 调用方法
创建实例的难点在于,很多人不知道clazz.newInstance()底层还是调用Contructor对象的newInstance()方法。所以,要想通过调用clazz.newInstance()方法生成某个类的实例,必须保证该类的时候有个无参构造函数。
为什么根据Class对象获取Method时,需要传入方法名+参数的Class类型
@CallerSensitive public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException { checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true); Method method = getMethod0(name, parameterTypes, true); if (method == null) { throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes)); } return method; }
因为.class文件中有多个方法Method[],方法是可以同名的。要想唯一定位一个方法必须明确地给出方法名 + 参数(包括参数类型及顺序)。在同一个类中出现多个同名方法叫重载,来复习下重载与覆盖的区别。
- 重载:如果在一个类中定义了多个同名的方法,但它们有不同的参数(包含三方面:参数个数、参数类型和参数顺序),则称为方法的重载。其中,不能通过访问权限、返回类型和抛出异常进行重载。
- 覆盖:子类中定义的某个方法与其父类中某个方法具有相同的方法签名(包含相同的名称和参数列表),则称为方法的覆盖。子类对象使用这个方法时,将调用该方法在子类中的定义,对它而言,父类中该方法的定义被屏蔽了。
总的来说,重载和覆盖是Java多态性的不同表现。前者是一个类中多态性的一种表现,后者是父类与子类之间多态性的一种表现。
那参数parameterTypes为什么要用Class类型,为什么不能传变量名呢,因为我们无法根据变量名区分方法。
Person getPerson(String userName, int age); Person getPerson(String nick, int old)
这不叫重载,上面就是同一个方法,IDE中不可能编译通过。
那么为不什么不能传String和int呢?原因是这些都是基本类型和引用类型,类型不能用来传递,能传递的要么值,要么是对象的引用。而java.lang.String.class以及int.class是对象。
调用method.invoke(obj, args);时为什么要传入一个目标对象
上面分析过,.class文件通过IO被加载到内存后,对象的本质就是用来存储数据的。而方法作为一种行为描述,是所有对象共有的,不属于某个对象独有,比如两个Person实例:
Person zhangsan = new Person(); Person lisi = new Person();
对象zhangsan保存的名字及年龄分别为“zhangsan”和18,而对象lisi保存的名字及年龄分别为“lisi”和20,两个对象都有changeAge()方法,但是每个对象里面存一份太浪费。既然是共性行为,可以抽取出来,放在方法区共用。
但这又产生了一个棘手的问题,类的方法(指非static方法)是共用的,JVM如何保证zhangsan调用changeAge()时,该方法不会跑去把lisi的数据改掉呢?
所以JVM设置了一种隐性机制,每次对象调用方法时,都会隐性传递当前调用该方法的对象参数(C++也有类似机制,传递的是this指针),方法可以根据这个对象参数知道当前调用本方法的是哪个对象!
同样的,在通过反射调用方法时,本质还是希望方法处理某个对象的数据,所以必须传入对象的引用。如果要invoke的是一个静态方法,就不需要传入具体的对象了,因为静态方法并不能处理对象中保存的数据。
Java反射在实际开发中应用
很多Java框架中都有反射的影子,例如Spring、Mybatis等等,Jdbc利用反射将数据库的表字段映射到Java对象的getter/setter方法。Jackson、GSO、Boon等类库也是利用反射将JSON文件的属性映射到Java对的象getter/setter方法。可见只要使用java,反射就无处不在。
最典型的,动态代理就使用了反射在运行时创建接口的动态实现,java.lang.reflect.Proxy类提供了创建动态实现的功能。我们把运行时创建接口的动态实现称为动态代理。 动态代理可以用于许多不同的目的,例如数据库连接和事务管理、用于单元测试的动态模拟对象以及其他类似AOP的方法拦截等。
代理模式就是在接口和实现之前加一层,用于剥离接口的一些额外的操作。
简单的代理模式的典型示例代码如下:
public interface Subject { void doSomething(); } public class RealSubject implements Subject { @Override public void doSomething() { System.out.println(this.getClass().getName() + "_doSomething"); } } public class SubjectInvocationHandler implements InvocationHandler { //被代理对象 private Object proxyObject; public SubjectInvocationHandler(Object proxyObject) { this.proxyObject = proxyObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在执行真正的方法前,可以做一些事情 System.out.println(this.getClass().getName() + "_invocationHandler"); return method.invoke(proxyObject, args); }@CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { //声明h不能为空 Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. * 查找或生成代理类 */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. * 使用指定的调用处理程序调用它的构造函数 */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } //代理类cl已经有了,通过反射获取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; } }); } //反射生成代理对象并返回,这里的对象就是调用Contructor的newInstance方法生成的 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); } } }
怎样在运行时动态生成接口的代理类的实例
动态代理就是动态的创建代理对象并动态地处理对其所代理的方法的调用。
对于一个普通的 Java 动态代理,其实现过程可以简化成为:
- 提供一个基础的接口,作为被调用类型(如com.test.MyInterfaceImpl)和代理类(如com.test.MyInterface)之间的统一入口;
- 通过实现InvocationHandler接口来自定义自己的MyInvocationHandler,对代理对象方法的调用,会被分派到其 invoke 方法来真正实现动作;
- 调用java.lang.reflect.Proxy类的newProxyInstance 方法,生成一个实现了相应基础接口的代理类实例;
JDK1.8中newProxyInstance方法源码如下:
@CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { //声明h不能为空 Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. * 查找或生成代理类 */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. * 使用指定的调用处理程序调用它的构造函数 */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } //代理类cl已经有了,通过反射获取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; } }); } //反射生成代理对象并返回,这里的对象就是调用Contructor的newInstance方法生成的 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); } }
newProxyInstance帮我们执行了生成代理类----获取构造器----生成代理对象这三步;newProxyInstance()方法有三个参数: 目标接口、目标接口的类加载器以及InvocationHandler。
我们重点分析生成代理类getProxyClass0()方法
/** * a cache of proxy classes:动态代理类的弱缓存容器 * KeyFactory:根据接口的数量,映射一个最佳的key生成函数,其中表示接口的类对象被弱引用;也就是key对象被弱引用继承自WeakReference(key0、key1、key2、keyX),保存接口密钥(hash值) * ProxyClassFactory:生成动态类的工厂 * 注意,两个都实现了BiFunction<ClassLoader, Class<?>[], Object>接口 */ private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); /** * Generate a proxy class. Must call the checkProxyAccess method * to perform permission checks before calling this. * 生成代理类,调用前必须进行 checkProxyAccess权限检查,所以newProxyInstance进行了权限检查 */ private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { //实现接口的最大数量<65535;谁写的类能实现这么多接口 if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory // 如果缓存中有,就直接返回,否则会生成 return proxyClassCache.get(loader, interfaces); }
proxyClassCache.get()方法中,如果没有从缓存中找到,最终会调用
ProxyGenerator.generateProxyClass()方法去生成字节码。
public static byte[] generateProxyClass(final String name, Class<?>[] interfaces, int accessFlags) { ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags); //真正生成字节码的方法 final byte[] classFile = gen.generateClassFile(); //如果saveGeneratedFiles为true 则生成字节码文件,所以在开始我们要设置这个参数 //当然,也可以通过返回的bytes自己输出 if (saveGeneratedFiles) { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() { public Void run() { try { int i = name.lastIndexOf('.'); Path path; if (i > 0) { Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar)); Files.createDirectories(dir); path = dir.resolve(name.substring(i+1, name.length()) + ".class"); } else { path = Paths.get(name + ".class"); } Files.write(path, classFile); return null; } catch (IOException e) { throw new InternalError( "I/O exception saving generated file: " + e); } } }); } return classFile; }
以上方法执行成功后就返回了字节码的byte数组。然后,就可以进入我们熟知的类加载过程了,我就不再赘述了。
以下两行就能为任意一个接口生成一个代理类实例。
InvocationHandler handler = new MyInvocationHandler(); MyInterface proxy = (MyInterface) Proxy.newProxyInstance( MyInterface.class.getClassLoader(), new Class[] { MyInterface.class }, handler);
运行上面代码后,proxy这个实例就包含了对MyInterface接口的动态实现。等等,我们并没有写出MyInterface的实现类代码,这样就直接生成了一个MyInterface实现类的实例?很神奇吧,难道new一个Person实例之前,不是要先给出Person类的定义吗。所谓“动态”代理的动态就体现在这里,这里直接生成了类的字节码(就是没有Person.java文件,直接搞出了Person.class文件,Javac的过程都直接省略了),类加载器才不管字节码是怎么来的呢,只要是合乎规范的字节码即合规的byte[],它都能加载。
总结
一个典型的动态代理创建对象过程可分为以下四个步骤:
- 通过实现InvocationHandler接口创建自己的调用处理器;
- 通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类,即Proxy类的getProxyClass0()方法;
- 通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型即对上一步获得的clazz对象调用getConstructor()
- 通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入;
为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。
如何查看动态生成的代理类的.class文件
在调用Proxy.newProxyInstance()方法时,最终会调用到
ProxyGenerator.generateProxyClass()方法,该方法的作用就是生成代理对象的class文件,返回值是一个byte[]数组。所以我们可以将生成的byte[]数组通过输出流,将内容写出到磁盘。
public static void main(String[] args) throws IOException { String proxyName = "com.test.$Proxy0"; Class[] interfaces = new Class[]{Subject.class}; int accessFlags = Modifier.PUBLIC; byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); // 将字节数组写出到磁盘 File file = new File("/Users/Downloads/test/$Proxy0.class"); OutputStream outputStream = new FileOutputStream(file); outputStream.write(proxyClassFile); }
运行完main()方法后,从目录中找到生成的文件,由于是一个.class文件,所以我们需要把它有反编译器编译一下,例如:在idea中,将文件放到target目录下,打开文件就能看到反编译后的代码了。
以下为将上面生成的.class文件放入target目录中查看的结果
$Proxy0的源码如下
public class $Proxy0 extends Proxy implements Subject { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue(); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void doSomething() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } 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)).intValue(); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")}); m3 = Class.forName("com.test.proxy.Subject").getMethod("doSomething", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
通过源码我们发现,$Proxy0类继承了Proxy类,同时实现了Subject接口。到这里,我们就能解释为什么JDK的动态代理只能基于接口实现,不能基于继承来实现?因为Java中不支持多继承,而JDK的动态代理在创建代理对象时,默认让代理对象继承了Proxy类,所以JDK只能通过接口去实现动态代理。
$Proxy0类实现了Subject接口,重写了doSomething()方法,它也同时
重写了Object类中的几个方法。所以当我们调用doSomething()方法时,先是调用到$Proxy0.doSomething()方法,在这个方法中,直接调用了super.h.invoke()方法,父类是Proxy,父类中的h就是我们定义的InvocationHandler,所以这儿会调用到
SubjectInvocationHandler.invoke()方法。因此当我们通过代理对象去执行目标对象的方法时,会先经过InvocationHandler的invoke()方法,然后在通过反射method.invoke()去调用目标对象的方法。
public static void main(String[] args) throws IOException { Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[]{Subject.class}, new SubjectInvocationHandler(new SubjectImpl())); proxySubject.doSomething(); }
运行结果如下:
com.test.proxy.SubjectInvocationHandler_invocationHandler com.test.proxy.impl.SubjectImpl_doSomething
诚然,Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。
动态代理在Mybatis中的应用
举一个Mybatis中使用动态代理的例子。在开始使用Mybatis这个框架的时候,我很不能理解的一点就是定义了一个访问数据库的接口进行了一些配置之后,然后通过sqlSession.getMapper()方法就可以获得这个接口的一个实现类的对象。大致是这样的:
public interface PersonDao { PersonDO getPersonById(long id); } public void testGetPersonById(){ PersonDao personDao = sqlSession.getMapper(PersonDao.class); personDao.getPersonById(12); }
这里的sqlSession.getMapper()方法其实是为PersonDao接口生成了一个动态代理对象。来看DefaultSqlSession类中对getMapper()方法的具体实现:
@Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
它直接调用的configuration的getMapper()方法,来看下这个方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
mapperRegistry.getMapper()方法:
@SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { //产生代理对象的方法 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
mapperProxyFactory.newInstance()方法:
//调用Proxy.newProxyInstance()来创建的反射对象 @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
可以看到底层是通过Proxy.newProxyInstance()创建的代理对象,然后返回的。而且因为这里使用了泛型,所以对Proxy.newProxyInstance()创建返回的Object对象进行了强制类型转换,返回的对象就是BranchDao接口类型了。
这篇关于彻底理解Java反射以及动态代理中对反射的应用的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-28知识管理革命:文档软件的新玩法了解一下!
- 2024-11-28低代码应用课程:新手入门全攻略
- 2024-11-28哪些办公软件适合团队协作,且能够清晰记录每个阶段的工作进展?
- 2024-11-28全栈低代码开发课程:零基础入门到初级实战
- 2024-11-28拖动排序课程:轻松掌握课程拖动排序功能
- 2024-11-28如何高效管理数字化转型项目
- 2024-11-28SMART法则好用吗?有哪些项目管理工具辅助实现?
- 2024-11-28深度剖析:6 款办公软件如何构建设计团队项目可视化管理新生态?
- 2024-11-28HTTP缓存课程:新手入门指南
- 2024-11-28实战丨证券 HTAP 混合业务场景的难点问题应对