【Java核心技术】动态代理的原理

编程语言通常有各种不同的分类角度,动态类型和静态类型就是其中一种分类角度,简单区分就是语言类型信息是在运行时检查,还是编译期检查

与其近似的还有一个对比,就是所谓强类型和弱类型,就是不同类型变量赋值时,是否需要显式地(强制)进行类型转换

那么,如何分类Java语言呢?通常认为,Java是静态的强类型语言,但是因为提供了类似反射等机制,也具备了部分动态类型语言的能力

言归正传,接下来的问题是:

  • 谈谈Java反射机制
  • 动态代理是基于什么原理?

一、典型回答

1.1 反射机制

反射机制是Java语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力

通过反射可以直接操作类或者对象,比如

  • 获取某个对象的类定义
  • 获取类声明的属性和方法
  • 调用方法或者构造对象
  • 运行时修改类定义

1.2 动态代理

动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类似机制做到的,比如用来包装RPC调用、面向切面的编程(AOP)

动态代理,就是通过同性质的中间人去处理请求,同时也被代理对象赋予了操作权限的一种存在

实现动态代理的方式很多,比如:

  • JDK自身提供的动态代理,主要利用了上面提到的反射机制
  • 利用更高性能的字节码操作机制(类似ASM、cglib(基于ASM)、Javassist等),使用其他的实现方式

二、考点分析

关于Java反射机制及动态代理的原理,下意识地以为动态代理就是利用反射机制实现的,这么说也不算错但稍微有些不全面。功能才是目的,实现的方法有很多

总的来说,这主要考察的是Java语言的另外一种基础机制:反射

  • 反射

反射就像是一种魔法,引入运行时自省能力,赋予了Java语言令人意外的活力,通过运行时操作元数据或对象,Java可以灵活地操作运行时才能确定的信息

  • 动态代理

动态代理则是延伸出来的一种广泛应用于产品开发中的技术,很多繁琐的重复编程,都可以被动态代理机制优雅地解决

从考察知识点的角度,这道题涉及的知识点:

  • 考察对反射机制的了解和掌握程度
  • 动态代理解决了什么问题,在业务系统中的应用场景是什么?
  • JDK动态代理在设计和实现上与cglib等方式有什么不同,进而如何取舍?

三、知识扩展

3.1 反射机制及其演进

对于Java语言的反射机制本身,查看java.langjava.lang.reflect包下的相关抽象,可以有一个很直观的印象。ClassFieldMethodConstructor等完全就是去操作类和对象的元数据对应。需要掌握基本场景编程,可以官方提供的参考文档

关于反射,有一点需要特别注意,就是反射提供的AccessibleObject.setAccessible​(boolean flag),它的子类也大都重写了这个方法,这里的accessible可以理解成修饰成员的publicprotectedprivate,这意味着可以在运行时修改成员访问限制

setAccessible的应用场景非常普遍,遍布日常开发、测试、依赖注入等各种框架中。比如,在 O/R Mapping框架中,为一个Java实体对象运行时自动生成settergetter的逻辑,这是加载或者持久化数据非常必要的,框架通常可以利用反射做这个事情,而不需要开发者手动写类似的重复代码

另一个典型场景就是绕过API访问控制。日常开发时可能被迫要调用内部API去做些事情,比如,自定义的高性能NIO框架需要显式地释放DirectBuffer,使用反射绕开限制是一种常见办法

3.2 动态代理

前面的问题问到了动态代理,接下来看看它到底是解决什么问题?

首先,它是一个代理机制
如果熟悉设计模式中的代理模式,可以知道代理可以看作是对调用目标的一个包装,这样对目标代码的调用不是直接发生的,而是通过代理完成
其实很多动态代理场景,认为也可以看作是装饰器(Decorator)模式的应用

通过代理可以让调用者与实现者之间解耦
比如进行RPC调用,框架内部的寻址、序列化、反序列化等,对于调用者往往是没有太大意义的,通过代理可以提供更加友善的界面

代理的发展经历了静态到动态的过程,源于静态代理引入的额外工作
类似早期的RMI之类古董技术,还需要rmic之类工具生成静态stub等各种文件,增加了很多繁琐的准备工作,而这又和业务逻辑没有关系
利用动态代理机制,相应的stub等类可以在运行时生成,对应的调用操作也是动态完成,极大地提高了生产力。改进后的RMI已经不再需要手动去准备这些了,虽然它仍然是相对古老落后的技术,未来也许会逐步被移除

可以看JDK动态代理的一个简单例子更加直观的理解一下
下面只是加了一句print,在生产系统中,可以轻松扩展类似逻辑进行诊断、限流等

public class MyDynamicProxy {
    public static  void main (String[] args) {
        HelloImpl hello = new HelloImpl();
        MyInvocationHandler handler = new MyInvocationHandler(hello);
        // 构造代码实例
        Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), handler);
        // 调用代理方法
        proxyHello.sayHello();
    }
}

interface Hello {
    void sayHello();
}

class HelloImpl implements  Hello {
    @Override
    public void sayHello() {
        System.out.println("Hello World");
    }
}

class MyInvocationHandler implements InvocationHandler {
   private Object target;
   public MyInvocationHandler(Object target) {
       this.target = target;
   }
   @Override
   public Object invoke(Object proxy, Method method, Object[] args)
           throws Throwable {
       System.out.println("Invoking sayHello");
       Object result = method.invoke(target, args);
       return result;
   }
}

上面的JDK Proxy例子,非常简单地实现了动态代理的构建和代理操作

  • 首先,实现对应的InvocationHandler
  • 然后,以接口Hello为纽带,为被调用目标构建代理对象
  • 进而应用程序就可以使用代理对象间接运行调用目标的逻辑,代理为应用插入额外逻辑(比如println)提供了便利的入口

从API设计和实现的角度,这种实现仍然有局限性,因为它是以接口为中心的,相当于添加了一种对于被调用者没有太大意义的限制。实例化的是Proxy对象,而不是真正的被调用类型,这在实践中还是可能带来各种不便和能力退化

JDK Proxy是实现InvocationHandler,使用invoke方法进行调用,实质上使用了JDK的反射原理 Cglib使用的是继承目标类,相当于代理类是子类代理类父类是Proxy类,通过jdk代理生成的类都继承Proxy类: 且因为Java是单继承的,而代理类又必须继承自Proxy类,所以通过jdk代理的类必须实现接口

如果被调用者没有实现接口,但还是希望利用动态代理机制,那么可以考虑其他方式
Spring AOP支持两种模式的动态代理,JDK Proxy或者cglib,如果选择cglib方式会发现对接口的依赖被克服了

动态代理应用非常广泛,虽然最初多是因为RPC等使用,但是动态代理的使用场景远远不仅如此,它完美符合Spring AOP等切面编程
AOP简单来说它可以看作是对OOP的一个补充,因为OOP对于跨越不同对象或类的分散、纠缠逻辑表现力不够,比如在不同模块的特定阶段做一些事情,类似日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等,可以参考下面这张图

在这里插入图片描述
AOP通过(动态)代理机制可以让开发者从这些繁琐事项中抽身出来,大幅度提高了代码的抽象程度和复用度。从逻辑上来说,在软件设计和实现中的类似代理,如Facade、Observer等很多设计目的,都可以通过动态代理优雅地实现

四、补充说明

4.1 反射

反射最大的作用之一在于可以不在编译时知道某个对象的类型,而在运行时通过提供完整的"包名+类名.class"得到

注:不是在编译时,而是在运行时

4.1.1 功能
  • 在运行时能判断任意一个对象所属的类
  • 在运行时能构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法

直白一些就是,利用Java反射机制可以加载一个运行时才得知名称的class,获悉其构造方法,并生成其对象实体,能对其fields设值并唤起其methods

4.1.2 应用场景

反射技术常用在各类通用框架开发中
因为为了保证框架的通用性,需要根据配置文件加载不同的对象或类,并调用不同的方法,这个时候就会用到反射——运行时动态加载需要加载的对象

4.1.3 特点

由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射
另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题

4.2 动态代理

为其他对象提供一种代理以控制对这个对象的访问
在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在两者之间起到中介的作用(可类比房屋中介,房东委托中介销售房屋、签订合同等)

所谓动态代理,就是实现阶段不用关心代理谁,而是在运行阶段才指定代理哪个一个对象(不确定性)。如果是自己写代理类的方式就是静态代理(确定性)

4.2.1 组成要素

动态代理模式主要涉及三个要素:

  • 其一:抽象类接口
  • 其二:被代理类(具体实现抽象接口的类)
  • 其三:动态代理类(实际调用被代理类的方法和属性的类)
4.2.2 实现方式

实现动态代理的方式很多,比如:
JDK自身提供的动态代理,就是主要利用了反射机制
ASM、CGLIB(基于ASM)、Javassist等,利用字节码操作机制

举例,常可采用的JDK提供的动态代理接口InvocationHandler来实现动态代理类
其中:

  • invoke方法是该接口定义必须实现的,它完成对真实方法的调用
  • 通过InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务
  • 可以在invoke方法实现中增加自定义的逻辑实现,实现对被代理类的业务逻辑无侵入

五、小结

简要回顾了反射机制,以及反射在Java语言演进中正在发生的变化,并且进一步探讨了动态代理机制和相关的切面编程,分析了其解决的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sysu_lluozh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值