深入理解 Java 动态代理机制 java的动态代理机制详解
Java 有两种代理方式,一种是静态代理,另一种是动态代理。对于静态代理,其实就是通过依赖注入,对对象进行封装,不让外部知道实现的细节。很多 API 就是通过这种形式来封装的。 代理模式结构图(图片来自《大话设计模式》) 下面看下两者在概念上的解释: 静态代理
动态代理
动态代理的好处Java动态代理的优势是实现无侵入式的代码扩展,也就是方法的增强;让你可以在不用修改源码的情况下,增强一些方法;在方法的前后你可以做你任何想做的事情(甚至不去执行这个方法就可以)。此外,也可以减少代码量,如果采用静态代理,类的方法比较多的时候,得手写大量代码。 动态代理实例静态代理的实例这里就不说了,比较简单。在 java 的 java.lang.reflect 包下提供了一个 Proxy 类和一个 InvocationHandler 接口,通过这个类和这个接口可以生成 JDK 动态代理类和动态代理对象。下面讲讲动态代理的实现。 先定义一个接口: public interface Person { void setName(String name); } 再定义一个学生 Student 类来实现 Person 接口,每一个学生都有一个自己的名字: class Student implements Person { private String mName; public Student(String name) { mName = name; } setName(String name) { mName = name; } } ?Student 类中,定义了一个私有变量 mName,用来表示?Student 的名字。接下去定义一个代理 handler,就是用来帮我们处理代理的 : class PersonHandler<T> InvocationHandler { // 代理的目标对象 T mTarget; PersonHandler(T target) { mTarget = target; } @Override public Object invoke(Object o,Method method,Object[] objects) throws Throwable { 调用开始前的操作 ProxyUtil.start(); 调用方法,通过反射的形式来调用 mTarget 的 method Object result = method.invoke(mTarget,objects); 打印改名前的名字 ProxyUtil.log(objects[0].toString()); 调用结束后的操作 ProxyUtil.finish(); return result; } } 可以发现,我们在调用代码前后都做了一些操作,甚至可以直接拦截该方法,不让其运行。其中定义了一个 ProxyUtil 类,方便我们做一些操作: class ProxyUtil { private static final String TAG = "ProxyUtil"; start() { Log.d(TAG,"start: " + System.currentTimeMillis()); } finish() { Log.d(TAG,"finish: " + log(String name) { Log.d(TAG,"log: " + name); } } 接下去开始编写代理的实现: class MainActivity extends AppCompatActivity { @Override protected onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); 创建一个实例对象,这个对象是被代理的对象 Person zhangsan = new Student("张三"); 创建一个与代理对象相关联的 InvocationHandler PersonHandler stuHandler = new PersonHandler<>(zhangsan); 创建一个代理对象 stuProxy 来代理 zhangsan,代理对象的每个执行方法都会替换执行 Invocation 中的 invoke 方法 Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),new Class<?>[]{Person.},stuHandler); 代理执行 setName 的方法,代理实例内部又会调用 stuHandler 的 invoke方法,在通过反射调用了调用了被代理对象的方法 stuProxy.setName("王五"); } 看下打印输出: 可以发现代理成功了。并且我们在调用方式的之前之后,都做了一些操作。Spring 的 AOP 其就是通过动态代理的机制实现的。 ? 其中,我们将?stuProxy 的类名打印出来: Log.d(TAG,"onCreate: " + stuProxy.getClass().getCanonicalName()); ?发现其名字竟然是 $Proxy0。具体原因下面会解释。 源码分析上面我们利用 Proxy 类的 newProxyInstance 方法创建了一个动态代理对象,查看该方法的源码:? /** * Returns an instance of a proxy class for the specified interfaces * that dispatches method invocations to the specified invocation * handler. * * <p>{@code Proxy.newProxyInstance} throws * { IllegalArgumentException} for the same reasons that * { Proxy.getProxyClass} does. * * @param loader the class loader to define the proxy class * interfaces the list of interfaces for the proxy class * to implement * h the invocation handler to dispatch method invocations to * @return a proxy instance with the specified invocation handler of a * proxy class that is defined by the specified class loader * and that implements the specified interfaces * @throws IllegalArgumentException if any of the restrictions on the * parameters that may be passed to { getProxyClass} * are violated * SecurityException if a security manager,<em>s</em>,is present * and any of the following conditions is met: * <ul> * <li> the given { loader} is { null} and * the caller's class loader is not { null} and the * invocation of {@link SecurityManager#checkPermission * s.checkPermission} with * { RuntimePermission("getClassLoader")} permission * denies access;</li> * <li> for each proxy interface,{ intf},* the caller's class loader is not the same as or an * ancestor of the class loader for { intf} and * invocation of { SecurityManager#checkPackageAccess * s.checkPackageAccess()} denies access to { intf};</li> * <li> any of the given proxy interfaces is non-public and the * caller class is not in the same {@linkplain Package runtime package} * as the non-public interface and the invocation of * { SecurityManager#checkPermission s.checkPermission} with * { ReflectPermission("newProxyInPackage.{package name}")} * permission denies access.</li> * </ul> * NullPointerException if the { interfaces} array * argument or any of its elements are { null},or * if the invocation handler,1)"> h},is * { null} */ @CallerSensitive static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) IllegalArgumentException { ?最后发现会对生成的代理类进行缓存,有了,就不直接返回,没有的,还得生成代理类,我们继续往下走: proxyClassCache = new WeakCache<>(new KeyFactory(),1)">new ProxyClassFactory()); ?关键点在于??ProxyClassFactory 这个类,从名字也可以猜出来这个类的作用。看看代码: * A factory function that generates,defines and returns the proxy class given * the ClassLoader and array of interfaces. final ProxyClassFactory implements BiFunction<ClassLoader,Class<?>[],Class<?>> { prefix for all proxy class names 定义前缀 final String proxyClassNamePrefix = "$Proxy"; next number to use for generation of unique proxy class names 原子操作,适用于多线程 final AtomicLong nextUniqueNumber = AtomicLong(); @Override public Class<?> apply(ClassLoader loader,Class<?>[] interfaces) { Map<Class<?>,Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { * Verify that the class loader resolves the name of this * interface to the same Class object. Class<?> interfaceClass = ; { 这里会通过反射获取接口的各种修饰符,包名等,然后根据规则命名代理类,最后调用?ProxyGenerator.generateProxyClass 生成了代理类。 ?ProxyGenerator.generateProxyClass 具体实现在 eclipse 上打开后,说是找不到源码: ?不过,从其他地方找到了部分代码: byte[] generateProxyClass(final String name,Class[] interfaces) { ProxyGenerator gen = ProxyGenerator(name,interfaces); 这里动态生成代理类的字节码,由于比较复杂就不进去看了 byte[] classFile = gen.generateClassFile(); 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上 if (saveGeneratedFiles) { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() { Void run() { { FileOutputStream file = new FileOutputStream(dotToSlash(name) + ".class"); file.write(classFile); file.close(); ; } (IOException e) { InternalError( "I/O exception saving generated file: " + e); } } }); } 返回代理类的字节码 classFile; } 我们可以自己试试 ProxyGenerator.generateProxyClass 的功能。 ProxyGeneratorUtils { * 把代理类的字节码写到硬盘上 * path 保存路径 writeProxyClassToHardDisk(String path) { 获取代理类的字节码 byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11",Student..getInterfaces()); FileOutputStream out = ; { out = FileOutputStream(path); out.write(classFile); out.flush(); } (Exception e) { e.printStackTrace(); } finally { out.close(); } (IOException e) { e.printStackTrace(); } } } } main 方法里面进行调用 : Main { main(String[] args) { ProxyGeneratorUtils.writeProxyClassToHardDisk("$Proxy0.class"); } } 可以发现,在根目录下生成了一个??$Proxy0.class 文件,文件内容反编译后如下: import java.lang.reflect.InvocationHandler; java.lang.reflect.Method; java.lang.reflect.Proxy; java.lang.reflect.UndeclaredThrowableException; proxy.Person; class $Proxy0 extends Proxy Person { Method m1; Method m2; Method m3; Method m0; *注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白 *为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个 *被代理对象的实例,不禁会想难道是....? 没错,就是你想的那样。 * *super(paramInvocationHandler),是调用父类Proxy的构造方法。 *父类持有:protected InvocationHandler h; *Proxy构造方法: * protected Proxy(InvocationHandler h) { * Objects.requireNonNull(h); * this.h = h; * } * */ $Proxy0(InvocationHandler paramInvocationHandler) { (paramInvocationHandler); } 这个静态块本来是在最后的,我把它拿到前面来,方便描述 { { 看看这儿静态块儿里面有什么,是不是找到了giveMoney方法。请记住giveMoney通过反射得到的名字m3,其他的先不管 m1 = Class.forName("java.lang.Object").getMethod("equals",1)">new Class[] { Class.forName("java.lang.Object") }); m2 = Class.forName("java.lang.Object").getMethod("toString",1)">new Class[0]); m3 = Class.forName("proxy.Person").getMethod("giveMoney",1)">]); m0 = Class.forName("java.lang.Object").getMethod("hashCode",1)">]); ; } (NoSuchMethodException localNoSuchMethodException) { NoSuchMethodError(localNoSuchMethodException.getMessage()); } (ClassNotFoundException localClassNotFoundException) { NoClassDefFoundError(localClassNotFoundException.getMessage()); } } * *这里调用代理对象的giveMoney方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。 *this.h.invoke(this,m3,null);这里简单,明了。 *来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象, *再联系到InvacationHandler中的invoke方法。嗯,就是这样。 giveMoney() this.h.invoke(this,1)">); catch (Error|RuntimeException localError) { localError; } (Throwable localThrowable) { UndeclaredThrowableException(localThrowable); } } 注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。 } jdk 为我们的生成了一个叫 $Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。 我们可以对 InvocationHandler 看做一个中介类,中介类持有一个被代理对象,在 invoke 方法中调用了被代理对象的相应方法,而生成的代理类中持有中介类,因此,当我们在调用代理类的时候,就是再调用中介类的 invoke 方法,通过反射转为对被代理对象的调用。 代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的 invoke 方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。 生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了 Proxy 类,所以也就决定了 java 动态代理只能对接口进行代理,Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理。 ? 参考文章: 1、java的动态代理机制详解 2、彻底理解JAVA动态代理 3、java 1.8 动态代理源码分析 4、java动态代理实现与原理详细分析 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |