当前位置: 首页 > news >正文

java核心基础之代理机制详解(静态代理、动态代理:JDK、CGlib)

点赞再看,养成习惯,听说微信搜公众号《Java鱼仔》会让自己的技术更上一层楼

(一)什么是代理?

在生活中经常会遇到代理,比如买房我们是去找中介,而不是自己一栋楼一栋楼去挑选,这里的中介就是代理。代理即通过代理对象访问目标对象,还可以在目标对象基础上增强额外的功能。java的代理分为静态代理和动态代理。静态代理即在代码运行前,代理类就已经存在了。动态代理指代理类不是写在代码中的,而是在运行过程中产生的。

(二)静态代理

静态代理中有以下几个角色:

抽象对象:一般是使用接口

真实角色:被代理的角色(房东)

代理角色:代理真实角色,顺便会带一些附加操作(中介)

客户:访问代理角色的人(租客)

 

静态代理就是在代码运行之前,代理类就已经存在了。通过一个实例来模拟静态代理;:

以租房为例,租房有两种方式,一种是直接找房东去租房,另一种是找租房软件,第二种就是通过代理访问目标对象。

首先新建一个抽象对象(租房的接口),只带一个rent方法:

//抽象角色
public interface Rent {
    void rent();
}

然后需要一个真实对象(房东)

//真实角色(房东)
public class Landlord implements Rent{
    @Override
    public void rent() {
        System.out.println("房东出租房子");
    }
}

接着是代理角色(中介),因为要帮房东租房,所以也需要继承Room接口,代理角色在卖房的同时,还可以做一些自己的事情

//代理角色(中介)
public class Proxy implements Rent{
    private Landlord landlord;
    public Proxy(){
    }
    public Proxy(Landlord landlord) {
        this.landlord = landlord;
    }
    @Override
    public void rent() {
        System.out.println("收服务费");
        landlord.rent();
        System.out.println("收中介费");
    }
}

最后是由客户去访问中介实现租房

//客户
public class Client {
    public static void main(String[] args) {
        Landlord landlord=new Landlord();
        Proxy proxy = new Proxy(landlord);
        proxy.rent();
    }
}

然后观察结果:

收服务费
房东出租房子
收中介费

静态代理缺点是,一个真实的角色就需要一个代理角色,代码量会大大增加。

 

(三)动态代理

动态代理指代理类不是写在代码中的,而是在运行过程中产生的。java提供了两种实现动态代理的方式,分别是基于Jdk的动态代理和基于Cglib的动态代理。

(3.1) 基于Jdk的Proxy

首先介绍基于Jdk的Proxy,继续使用租房的案例:

新建一个ProxyInvocationhandler 类,这个类是动态代理的核心部分,需要继承InvocationHandler 接口。

InvocationHandler 是一个接口,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类invoke,由它实现处理内容。

通过Proxy.newProxyInstance可以生成得到代理类。

通过上面几个知识点我们就可以写出一个通用的动态代理类了:

public class ProxyInvocationhandler implements InvocationHandler {
    //被代理的接口
    private Object target;
    public void setTarget(Object target){
        this.target=target;
    }
    //生成得到代理类
    public Object getProxy(){
       return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
    //处理代理实例,并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //通过反射执行被代理的方法
        Object result = method.invoke(target, args);
        return result;
    }
}

接下来是客户去调用:首先是代理对象去代理真实的对象,然后通过getProxy()方法生成动态的代理类,最后执行被代理对象的方法。

public class Client2 {
    public static void main(String[] args) {
        //真实对象
        Landlord landlord=new Landlord();
        //代理角色,不存在
        ProxyInvocationhandler proxyInvocationhandler=new ProxyInvocationhandler();
        //设置要代理的对象
        proxyInvocationhandler.setTarget(landlord);
        //动态生成代理类(注意使用的是接口)
        Rent proxy = (Rent) proxyInvocationhandler.getProxy();
        proxy.rent();
    }
}

当调用proxy.rent()方法时,会自动执行invoke方法,以实现动态代理。

最后结果如下:

房东出租房子

运行时就会在控制台上打印出来,我们还可以在rent()方法执行前后做一些自己的操作,这也是Spring AOP的核心

JDK动态代理类实现了InvocationHandler接口,重写的invoke方法。

JDK动态代理的基础是反射机制(method.invoke(对象,参数))Proxy.newProxyInstance()

 

之前我讲静态代理的时候说静态代理的缺点在于对于每一个被代理的对象,都需要建一个代理类。因为静态代理是在项目运行前就写好的。但是动态代理就不是这样,由于动态代理在运行时才创建代理类,因此只需要写一个动态代理类就好。比如我再创建一个被代理的对象卖房:

写一个通用接口Sell

public interface Sell {
    void sellRoom();
}

接着还是写一个被代理对象的类:

public class RealSell implements Sell {
    public void sellRoom() {
        System.out.println("房东要卖房了");
    }
}

接下来在main方法中执行动态代理

public static void main(String[] args) {
    //真实对象
    RealSell realSell=new RealSell();
    //代理角色,不存在
    ProxyInvocationhandler proxyInvocationhandler=new ProxyInvocationhandler();
    //设置要代理的对象
    proxyInvocationhandler.setTarget(realSell);
    //动态生成代理类
    Sell proxy = (Sell) proxyInvocationhandler.getProxy();
    proxy.sellRoom();
}

最终实现结果如下:

房东要卖房了

通过动态代理,我可以通过一个动态代理类,去代理多个对象。

(3.2) 基于CGlib的动态代理

基于Jdk的动态代理局限性在于代理的类必须要实现接口,而基于CGlib的动态代理则没有这个限制:

搭建CGlib环境我们首先要引入一个CGlib的jar包:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

此时我们不再需要接口,直接新建一个CGRoom类:

public class CGRoom {
    public void rent(String roomName){
        System.out.println("租了"+roomName);
    }
}

CGlib实现动态代理的核心在于MethodInterceptor接口:

public class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理执行之前:"+method.getName());
        Object object=methodProxy.invokeSuper(o,objects);
        System.out.println("代理执行之后:"+method.getName());
        return object;
    }

这个接口只有一个intercept()方法,这个方法有4个参数:

1)表示增强的对象,即实现这个接口类的一个对象;

2)表示要被拦截的方法;

3)表示要被拦截方法的参数;

4)表示要触发父类的方法对象;

最后生成代理类对象并输出执行结果

public static void main(String[] args) {
    //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
    Enhancer enhancer=new Enhancer();
    //设置目标类的字节码文件
    enhancer.setSuperclass(CGRoom.class);
    //设置回调函数
    enhancer.setCallback(new MyMethodInterceptor());
    //创建代理对象
    CGRoom proxy= (CGRoom) enhancer.create();
   proxy.rent("碧桂园");
}

运行结果:

代理执行之前:rent
租了碧桂园
代理执行之后:rent

 

(四)总结

JDK动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承,所以JDK动态代理只能代理接口);CGLIB能够代理普通类;

 

相关文章:

  • Spring事务管理详解(传播属性、隔离级别)
  • 5分钟学会使用Less预编译器
  • RabbitMQ学习系列(一):RabbitMQ的了解安装和使用
  • RabbitMQ学习系列(二):简单队列详解
  • spring学习笔记(4)依赖注入详解
  • RabbitMQ学习系列(三):工作队列详解
  • RabbitMQ学习系列(四):发布-订阅模型详解
  • Android进阶学习
  • RabbitMQ学习系列(五):routing路由模式和Topic主题模式
  • RabbitMQ学习系列(六):RabbitMQ消息确认机制
  • cisco 交换机自动备份配置
  • 应届毕业生因为疫情休息在家,可以通过哪些途径提高自己?
  • APP产品交互设计分析总结(不断更新中...)
  • 以SpringBoot作为后台实践ajax异步刷新
  • 观察:阿里的VR实验室能解决什么问题?
  • JavaScript-如何实现克隆(clone)函数
  • [NodeJS] 关于Buffer
  • docker容器内的网络抓包
  • EOS是什么
  • Java 内存分配及垃圾回收机制初探
  • Javascript设计模式学习之Observer(观察者)模式
  • java中的hashCode
  • JS+CSS实现数字滚动
  • Rancher如何对接Ceph-RBD块存储
  • SpiderData 2019年2月23日 DApp数据排行榜
  • use Google search engine
  • 多线程 start 和 run 方法到底有什么区别?
  • 关于for循环的简单归纳
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 技术:超级实用的电脑小技巧
  • 每天10道Java面试题,跟我走,offer有!
  • 视频flv转mp4最快的几种方法(就是不用格式工厂)
  • 微信小程序--------语音识别(前端自己也能玩)
  • # C++之functional库用法整理
  • #AngularJS#$sce.trustAsResourceUrl
  • (1)STL算法之遍历容器
  • (2024最新)CentOS 7上在线安装MySQL 5.7|喂饭级教程
  • (阿里巴巴 dubbo,有数据库,可执行 )dubbo zookeeper spring demo
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (四)搭建容器云管理平台笔记—安装ETCD(不使用证书)
  • (转)JAVA中的堆栈
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)...
  • .NetCore部署微服务(二)
  • .NET开源全面方便的第三方登录组件集合 - MrHuo.OAuth
  • .sh文件怎么运行_创建优化的Go镜像文件以及踩过的坑
  • /dev/VolGroup00/LogVol00:unexpected inconsistency;run fsck manually
  • @Autowired @Resource @Qualifier的区别
  • @font-face 用字体画图标
  • @property python知乎_Python3基础之:property
  • @require_PUTNameError: name ‘require_PUT‘ is not defined 解决方法
  • @德人合科技——天锐绿盾 | 图纸加密软件有哪些功能呢?
  • [ Linux ] Linux信号概述 信号的产生
  • [.NET]桃源网络硬盘 v7.4
  • [2016.7 Day.4] T1 游戏 [正解:二分图 偏解:奇葩贪心+模拟?(不知如何称呼不过居然比std还快)]