不学无数——Java动态代理

简介: 动态代理1. 什么是动态代理在上一章节中,我们讲的是代理其实都是静态代理,动态代理是在运行阶段动态的创建代理并且动态的处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器中。

动态代理

1. 什么是动态代理

在上一章节中,我们讲的是代理其实都是静态代理,动态代理是在运行阶段动态的创建代理并且动态的处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器中。在现在很流行的Spring中有一个AOP(面向切面)的其中核心实现技术就是动态代理的技术。

2. 为什么要用动态代理

动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。例如我们想计算出每一个方法的执行时间,如果使用静态代理的话,那么就需要在每一个代理类中进行更改,但是如果使用了动态代理可以对类的所有方法进行统一的管理。一处添加,所有方法适用。

3. 动态代理的简单实现

3.1 静态代理的实现

我们先看一下静态代理的是如何实现的,关于静态代理详细的解释可以看不学无数——Java代理模式,这里只贴出关于静态代理的一些代码。

Homeowner接口如下:

interface Homeowner{
    public void LeaseHouse(Home home);
}

RealHomeowner类如下

class RealHomeowner implements Homeowner{

    @Override
    public void LeaseHouse(Home home) {
        System.out.println("房价是: "+ home.getPrice());
        System.out.println("房子颜色是: "+ home.getColor());
        System.out.println("房子出租成功");
    }
}

代理类HomeProxy的实现

class HomeProxy implements Homeowner{

    private Homeowner homeowner;
    public HomeProxy(Homeowner homeowner){
        this.homeowner = homeowner;
    }

    @Override
    public void LeaseHouse(Home home) {
        System.out.println("中介干预");
        homeowner.LeaseHouse(home);
        System.out.println("中介干预完成");
    }
}

在main方法中使用

public static void main(String[] args) {
    Home home = new Home("red",1000);
    RealHomeowner realHomeowner = new RealHomeowner();
    Homeowner homeowner = new HomeProxy(realHomeowner);
    homeowner.LeaseHouse(home);
}

打印的信息如下:

中介干预
房价是: 1000
房子颜色是: red
房子出租成功
中介干预完成

3.2 动态代理的实现

在动态代理中是不需要代理类的,就是不需要上面静态代理中的HomeProxy类,通过实现了InvocationHandler类,所有方法都由该'Handler'来处理了,意思就是所有被代理的方法都由InvocationHandler接管实际的处理任务。那么看实际的例子

DynamicPro

class DynamicPro implements InvocationHandler{

    //真实被代理的实例对象
    private Object object;

    public DynamicPro(Object object){
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("中介干预");
        Object result = method.invoke(object,args);
        System.out.println("中介干预完成");
        return result;
    }
}

在主方法如下的调用

public static void main(String[] args) {
        Home home = new Home("red",1000);
        //创建一个被代理的实例对象
        RealHomeowner realHomeowner = new RealHomeowner();
        //创建一个与被代理对象相关的InvocationHandler
        DynamicPro dynamicPro = new DynamicPro(realHomeowner);
        //创建一个类加载器
        ClassLoader classLoader = realHomeowner.getClass().getClassLoader();
        //被代理类的接口数组,里面的每一个方法都会执行InvocationHandler中的invoke方法
        Class<?>[] proxInterface = realHomeowner.getClass().getInterfaces();
        Homeowner homeowner = (Homeowner) Proxy.newProxyInstance(classLoader,proxInterface,dynamicPro);
        homeowner.LeaseHouse(home);
}

打印如下

中介干预
房价是: 1000
房子颜色是: red
房子出租成功
中介干预完成

4. 动态代理和静态代理的区别

img_79c210bbaf2b5dbf3e7d0ea06c472161.png
动态代理类图

上面是关于动态代理的类图,我们可以和静态代理的类图进行对比一下

img_1478834240b2907b448e93fb016fb81e.png
静态代理类图

可以看到在动态代理中不需要了实际的代理角色类,因为实际的代理角色在动态代理中时动态生成的。在动态代理中增加了InvocationHandler接口类,这个接口中只有一个方法,就是invoke()方法。我们可以实现InvocationHandler类然后在invoke()方法中对调用实际方法时的前置或者后置处理。

5. 动态代理原理简单分析

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        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);
            }

            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);
        }
    }

通过上面的代码的Debug发现最后是通过下面代码返回一个对象的

 //返回构造器生成的实例对象
 return cons.newInstance(new Object[]{h});

然后发现cons是从下面的代码获得的

//获得此代理类的构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);

cl是从下面的代码中获得的

//查找或生成指定的代理类
Class<?> cl = getProxyClass0(loader, intfs);

而intfs是从下面的代码获得

//克隆一个接口类
final Class<?>[] intfs = interfaces.clone();

随后想进去看getProxyClass0生成的代理类是什么,但是发现进不去。后来查资料知道它由于是动态生成的,类是缓存在java虚拟机中的,可以通过下面的方法将类打印出来。

动态代理类的字节码在程序运行时由Java反射机制动态生成


    public static void main(String[] args) {
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Homeowner.class.getInterfaces());
        String path = "/Users/hupengfei/git/Test/src/main/java/Practice/Day06/Homeowner.class";
        try(FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("代理类class文件写入成功");
        } catch (Exception e) {
            System.out.println("写文件错误");
        }
    }

对生成的class文件进行反编译,在Idea中能直接查看


//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import Practice.Day06.Home;
import Practice.Day06.Homeowner;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Homeowner {
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    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});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void LeaseHouse(Home var1) throws  {
        try {
            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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 Homeowner getProxy() throws  {
        try {
            return (Homeowner)super.h.invoke(this, m3, (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);
        } 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"));
            m4 = Class.forName("Practice.Day06.Homeowner").getMethod("LeaseHouse", Class.forName("Practice.Day06.Home"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("Practice.Day06.Homeowner").getMethod("getProxy");
            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 $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    -----调用了父类的构造函数,而它的父类是Proxy类,父类的构造函数如下
    
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
    --在父类中的h的定义如下
    protected InvocationHandler h;

此时我们就知道为什么我们的动态代理都会执行传入的InvocationHandler中的invoke()方法了

在下面的静态代码块中我们发现了LeaseHouse()方法

 m4 = Class.forName("Practice.Day06.Homeowner").getMethod("LeaseHouse", Class.forName("Practice.Day06.Home"));

然后在上面会发现有我们的方法

public final void LeaseHouse(Home var1) throws  {
    try {
        super.h.invoke(this, m4, new Object[]{var1});
    } catch (RuntimeException | Error var3) {
        throw var3;
    } catch (Throwable var4) {
        throw new UndeclaredThrowableException(var4);
    }
}

此时我们再回想一下在InvocationHandler类中的invoke()方法中传入的参数有Method方法了,这样就可以将外部对于被代理对象的调用都转化为调用invoke()方法,再由invoke()方法中调用被代理对象的方法。

6. 参考文章

相关文章
|
5天前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
[Java]静态代理与动态代理(基于JDK1.8)
|
20天前
|
Java
深入理解Java动态代理
深入理解Java动态代理
16 1
|
8天前
|
Java
Java代码解释静态代理和动态代理的区别
### 静态代理与动态代理简介 **静态代理**:代理类在编译时已确定,目标对象和代理对象都实现同一接口。代理类包含对目标对象的引用,并在调用方法时添加额外操作。 **动态代理**:利用Java反射机制在运行时生成代理类,更加灵活。通过`Proxy`类和`InvocationHandler`接口实现,无需提前知道接口的具体实现细节。 示例代码展示了两种代理方式的实现,静态代理需要手动创建代理对象,而动态代理通过反射机制自动创建。
|
19天前
|
设计模式 缓存 Java
从源码学习Java动态代理|8月更文挑战
从源码学习Java动态代理|8月更文挑战
|
4月前
|
缓存 Java 测试技术
day27:Java零基础 - 动态代理
【7月更文挑战第27天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
31 2
day27:Java零基础 - 动态代理
|
3月前
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
161 0
|
3月前
|
设计模式 Java C++
揭秘!JDK动态代理VS CGLIB:一场关于Java代理界的‘宫心计’,你站哪队?
【8月更文挑战第24天】Java 动态代理是一种设计模式,允许在不改动原类的基础上通过代理类扩展功能。主要实现方式包括 JDK 动态代理和 CGLIB。前者基于接口,利用反射机制在运行时创建代理类;后者采用继承方式并通过字节码技术生成子类实现类的代理。两者在实现机制、性能及适用场景上有明显差异。JDK 动态代理适用于有接口的场景,而 CGLIB 更适合代理未实现接口的类,尽管性能更优但存在一些限制。开发者可根据需求选择合适的代理方式。
156 0
|
4月前
|
开发框架 Java Android开发
Java中的类反射与动态代理详解
Java中的类反射与动态代理详解
|
4月前
|
Java 数据安全/隐私保护
Java中的动态代理机制详解
Java中的动态代理机制详解
|
4月前
|
Java
Java中的反射与动态代理机制详解
Java中的反射与动态代理机制详解