对动态代理浅层理解

对动态代理浅层理解

一、考察目的

1、考点分析

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

2、典型回答

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

  • 实现动态代理的方式很多,比如JDK自身提供的动态代理,就是主要利用了上面提到的反射机
制。还有其他的实现方式,比如利用传说中更高性能的字节码操作机制,类似ASM、cglib (基
于 ASM )、Javassist 等。

二、知识拓展

1、反射机制及其演进

基本反射机制
  • 反射机制是Java语言提供的一种基础功能,赋予程序在运行时自省(introspect)
的能力,通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属
性和方法,调用方法或者构造对象,甚至可以运行时修改类定义

  • java.Iang.reflect包下的Class. Field. Method. Constructor类等,这些完全就是我
们去操作类和对象的元数据对应。

反射中的Accessible
  • 对于反射提供的AccessibleObject.setAccessible
(boolean flag)方法,它的子类也大都重写了这个方法,这里的所谓accessible可以理解成修饰成员
的public, protected, private ,这意味着我们可以在运行时修改成员访问限制

  • 应用场景

    • setAccessible的应用场景非常普遍,遍布我们的日常开发、测试、依赖注入等各种框架中。比
如,在O/R Mapping框架中,我们为—Java实体对象,运行时自动生成setter, getter的逻辑,这里加载或者持久化数据非常必要的,框架通常可以利用反射做这个事情,而不需要手动写类似代码。
    • 另一个典型场景就是绕过API访问控制。我们日常开发时可能被迫要调用内部API去做些事
情,比如,自定义的高性能NIO框架需要显式地释放DirectBuffer, 使用反射绕开限制是一种
常见方法.
  • 在Java 9以后,Jigsaw项目新增的模块化
系统,出于强封装性的考虑,对反射访问进行了限制。Jigsaw引入了所谓Open的概念,只有
当被反射操作的模块指定的包对反射调用者模块Open ,才能使用setAccessible ;否则,被
认为是不合法(illegal)操作。如果我们的实体类是定义在模块里面,我们需要在模块描述符中
明确声明

    module MyEntities {
        //Open for reflection
        opens cow.mycorp to Java.persistence;
    }
    

2、动态代理

基本概念和作用
  • 如果熟悉设计模式中的代理模式,我们知道,代理可以看作是对调
用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成。其实很多
动态代理场景,我认为也可以看作是装饰器(Decorator)模式的应用。
  • 代理可以让调用者与实现者之间解耦。比如进行RPC调用,框架内部的寻址、序列化、反
序列等,对于调用者往往是没有太大意义的,通过代理,可以提供更加友善的界面。
JDK动态代理

​ 在生产系统中,我们可以轻松扩展类似逻辑进行诊断,限流等。

  • 用户管理接口
package com.wangxiaoxi.proxy.entity;
//用户管理接口
public interface UserManager {
    //新增用户抽象方法
    void addUser(String userName,String password);
    //删除用户抽象方法
    void delUser(String userName);
    
}
  • 用户管理接口实现类
package com.wangxiaoxi.proxy.entity;
//用户管理实现类,实现用户管理接口
public class UserManagerImpl implements UserManager{
    //重写新增用户方法
    @Override
    public void addUser(String userName, String password) {
        System.out.println("调用了新增的方法!");
        System.out.println("传入参数为 userName: "+userName+" password: "+password);
    }
    //重写删除用户方法
    @Override
    public void delUser(String userName) {
        System.out.println("调用了删除的方法!");
        System.out.println("传入参数为 userName: "+userName);
    }
    
}
  • JDK动态代理实现类
package com.wangxiaoxi.proxy.jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.wangxiaoxi.proxy.entity.UserManager;
import com.wangxiaoxi.proxy.entity.UserManagerImpl;
//JDK动态代理实现InvocationHandler接口
public class JdkProxy implements InvocationHandler {
    private Object target ;//需要代理的目标对象

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK动态代理,监听开始!");  //扩展逻辑1
        System.out.println("------------------------------");
        Object result = method.invoke(target, args);
        System.out.println("------------------------------");
        System.out.println("JDK动态代理,监听结束!");   //扩展逻辑2
        return result;
    }
    //定义获取代理对象方法
    private Object getJDKProxy(Object targetObject){
        //为目标对象target赋值
        this.target = targetObject;
        //JDK动态代理只能针对实现了接口的类进行代理,newProxyInstance 函数所需参数就可看出
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
    }

    public static void main(String[] args) {
        JdkProxy jdkProxy = new JdkProxy();//实例化JDKProxy对象
        UserManager user = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl());//通过实现用户管理接口,获取代理对象
        user.addUser("admin", "123123");//执行新增方法
    }
}
  
输出结果
JDK动态代理,监听开始!
------------------------------
调用了新增的方法!
传入参数为 userName: admin password: 123123
------------------------------
JDK动态代理,监听结束!

  • 问题:

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

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

cglib实现动态代理
  • cglib动态代理采取的是创建目标类的子类的方式,因为是子类化,我们可以达到近似使用被调
用者本身的效果。在Spring编程中,框架通常会处理这种情况,当然我们也可以显式指定。
JDK Proxy与cglib的对比

原理区别:

  • java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
  • 而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
    • 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
    • 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
    • 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

如何强制使用CGLIB实现AOP?

  • 添加CGLIB库,SPRING_HOME/cglib/*.jar
  • 在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>

JDK动态代理和CGLIB字节码生成的区别?

  • JDK动态代理只能对实现了接口的类生成代理,而不能针对类

  • CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法

    因为是继承,所以该类或方法最好不要声明成final

优势对比:

  • JDK Proxy的优势:
    • 最小化依赖关系,减少依赖意味着简化开发和维护,JDK本身的支持,可能比cglib更加可
靠.
    • 平滑进行JDK版本升级,而字节码类库通常需要逬行更新以保证在新版Java上能够使用。
    • 代码实现简单。
  • 基于类似cglib框架的优势
    • 有的时候调用目标可能不便实现额外接口,从某种角度看,限定调用者实现接口是有些侵入
性的实践,类似cglib动态代理就没有这种限制。
    • 只操作我们关心的类,而不必为其他相关类增加工作量。
    • 高性能。

备注:

  • 反射机制在现代JDK中,自身已经得到了极大的改进和优化,同时,JDK很多功能也不完全是
反射,同样使用了 ASM逬行字节码操作。

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

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

三、网友互动

网友1:

  • 提一些建议:应该从两条线讲这个问题,一条从代理模式,一条从反射机制。不要老担心篇
幅限制讲不清问题,废话砍掉一些,深层次的内在原理多讲些(比如asm),容易自学的扩展知识可以用链接代替
  • 代理模式(通过代理静默地解决一些业务无关的问题,比如远程、安全,事务、日志、资源
关闭…让应用开发者可以只关心他的业务)
    • 静态代理:事先写好代理类,可以手工编写,也可以用工具生成。缺点是每个业务类
都要对应一个代理类,非常不灵活。
    • 动态代理:运行时自动生成代理对象。缺点是生成代理对象和调用代理方法都要额外
花费时间
      • JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生
成代理对象。新版本也开始结合ASM机制。
      • cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。
  • Java发射机制的常见应用:动态代理(AOP、RPC)、提供第三方开发者扩展能力(Servlet容器,JDBC连接)、第三方组件创建对象(DI)

四、参考文档

1、极客时间《Java核心技术36讲》第6讲

2、Spring的两种动态代理:Jdk和Cglib 的区别和实现

3、Java JDK 动态代理(AOP)使用及实现原理分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值