Java动态代理学习笔记

21 篇文章 1 订阅
6 篇文章 0 订阅

摘要

Java动态代理是很多其他框架和技术(如Spring AOP)的基础。学习JAVA动态代理的技术对于理解Spring AOP的底层原理很有帮助。学习一项技术,要弄清楚这个技术解决了什么问题,它的使用示例及它的工作原理。这篇学习笔记也沿着这个思路,分三个方面介绍:

  • 代理是什么,它是干什么用的,动态代理是什么;
  • JAVA 动态代理使用示例
  • JAVA 动态代理实现

1 代理及动态代理

1.1 代理的使用场景

代理提供一种间接访问对象的方式。软件开发中需要用代理的场景很多,常见的如

  • 远程代理(Remote Proxy)
    远程主机有更好的处理性能,但不能让客户端直接访问远程对象,可以通过远程代理访问。
    在这里插入图片描述
    本地代理对象负责网络通信和对远程业务对象的访问。
  • 虚拟代理(Virtual Proxy)
    对于一些资源消耗大或加载时间较长的对象,可以设置虚拟代理。在真实对象创建成功之前,由虚拟对象作为真实对象替身。用于减少用户等待时长。
  • 保护代理(Protect Proxy)
    对于需要权限才能访问的业务对象,可以使用保护代理对客户端请求拦截,判断权限
  • 缓存代理(Caching Proxy)
    对真实业务对象的查询结果提供临时存储空间,以便后续操作可以共享这个结果,避免业务对象方法重复执行

1.2 代理模式

遇到需要代理的场景,开发时可以参考下面这种最简单的代理模式结构
在这里插入图片描述
这一结构包含3个角色:

  • Subject(抽象主题): 声明真实主题和代理主题共同的接口,客户端需要针对抽象主题角色编程,这样可以方便增加和替换代理主题,符合开闭原则
  • Proxy(代理主题): 包含了一个对真实主题对象的引用,因此代理主题中可以约束对真实主题的使用,可以在真实主题操作前后自定义操作。代理主题与真实主题实现同一接口,因此可以替代真实主题被客户端调用
  • RealSubject(真实主题)
    目标对象,提供客户端需要的业务方法

1.3 什么是动态代理

动态代理是指在运行时(runtime)动态生成代理对象以增强原对象的功能。JVM可以在运行时动态生成类的字节码,可以通过这种方式生成代理类。

2 JAVA动态代理使用示例

2.1 示例代码

首先我们定义了一个Subject类型的接口,为其声明了两个方法:

public interface Subject
{  
    public void hello(String str);
}

接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject类:

public class RealSubject implements Subject
{
    @Override
    public void hello(String str)
    {
        System.out.println("hello: " + str);
    }
}

下一步,我们就来定义调用处理器(InvocationHandler)中增加的额外逻辑

public class DynamicProxy implements InvocationHandler
{
    // 这个就是我们要代理的真实对象
    private Subject subject;
    
    public DynamicProxy(Subject subject)
    {
        this.subject = subject;
    }
    
    // 这里需要三个参数,第一个参数是代理类的实例,第二个是被代理真是对象的方法,第三个是方法的参数
    @Override
    public Object invoke(Object object, Method method, Object[] args) throws Throwable
    {
        // 在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before say hello");
        
        System.out.println("Method:" + method);
        
        //  当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        Object res = method.invoke(subject, args);
        
        // 在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after say hello");
        
        return res;
    }
}

最后看下客户端代码

public class Client
{
    public static void main(String[] args)
    {
        // 我们要代理的真实对象
        Subject realSubject = new RealSubject();

        // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler handler = new DynamicProxy(realSubject);

        // 创建代理对象
        Subject subject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);
        
        // 打印代理对象的类名
        System.out.println(subject.getClass().getName());
        subject.hello("world");
    }
}

执行Client类的main方法,控制台打印结果:

$Proxy0

before say hello
Method:public abstract void com.dynamicproxy.Subject.hello(java.lang.String)
hello: world
after say hello

根据打印结果,推断动态生成的代理类定义应该如下。有兴趣的可以反编译$Proxy0的class文件,看源代码有什么区别

public class $Proxy0 {
    //自定义的InvocationHandler接口实现
    DynamicProxy handler;
    
    //代理类的内部方法
    
    //每调用一下,invoke方法就执行一次
    void hello(Object args) throws Exception{
        //内部调用DynamicProxy 的invoke方法
        handler.invoke(this,this.getClass().getMethod("hello", null), new Object[]{args});
    }
}

总结下这个实例的类图如下
在这里插入图片描述

3 JAVA动态代理实现

JDK中java.lang.reflect包里的Proxy类和InvocationHandler接口主要用来实现动态代理功能。其中Proxy类主要用于获取代理实例(proxy instance),InvacationHandler接口主要用来为被代理对象的方法增加约束。

Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。 被它创建的动态代理类有如下特性:
1,代理类被public final修饰,不会被abstract修饰,即不会是抽象类
2,类名以字符串 “$Proxy” 开头
3,代理类扩展 java.lang.reflect.Proxy
4,如果代理类实现了非公共接口,那么它将在与该接口相同的包中定义。否则,代理类的包也是未指定的

回到java.lang.reflect.Proxy类,主要定义以下方法

//含参构造函数
//Proxy维护一个InvocationHandler接口(调用处理器)引用
protected  Proxy(InvocationHandler h)
 
 //获取指定代理实例的调用处理器
static InvocationHandler getInvocationHandler(Object proxy) 

 //根据类加载器和接口数组返回代理类的 java.lang.Class 对象
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)

 //当且仅当参数cl是通过 getProxyClass 方法或 newProxyInstance 方法动态生成的代理类时,返回 true。 
static boolean isProxyClass(Class<?> cl)

 //获取一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 

InvocationHandler接口中定义了invoke方法,它的具体实现负责调用被代理的真实对象的方法,并增加额外的逻辑

Object invoke(Object proxy, Method method, Object[] args)

Proxy类中主要通过newProxyInstance方法生成动态代理对象,我们看下这个方法的实现

public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces, InvocationHandler h) throws IllegalArgumentException { 
    // 检查 h 不为空,否则抛异常
    if (h == null) { 
        throw new NullPointerException(); 
    } 

    // 获得与指定类装载器和一组接口相关的代理类类型对象
    Class cl = getProxyClass(loader, interfaces); 

    // 通过反射获取构造函数对象并生成代理类实例
    try { 
        Constructor cons = cl.getConstructor(constructorParams); 
        return (Object) cons.newInstance(new Object[] { h }); 
    } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); 
    } catch (IllegalAccessException e) { throw new InternalError(e.toString()); 
    } catch (InstantiationException e) { throw new InternalError(e.toString()); 
    } catch (InvocationTargetException e) { throw new InternalError(e.toString()); 
    } 
}

其中getProxyClass方法调用ProxyGeneratorgenerateProxyClass方法产生动态代理类的二进制数据。

public static byte[] generateProxyClass(final String name, Class[] interfaces)

再后面的就不展开了,感兴趣的可以自己去看这个方法的源码。

4 JDK动态代理与CGLIB动态代理

JVM生成的动态类必须实现一个或多个接口,所以,JDK动态代理只能用作具有相同接口的目标代理类。
如果有一个目标类,这个目标类本身没有实现接口。那通过什么样的方式来生成代理类呢?CGLIB可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。

参考资料

1,design pattern java
https://gof.quanke.name/#
2,Java Proxy官方API文档
https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html
3,java Proxy 动态代理
https://www.jianshu.com/p/28286f460f1e
4, JDK 动态代理深入解析
https://www.cnblogs.com/duanxz/archive/2012/12/03/2799504.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值