【Java学习笔记】 动态代理

这篇具有很好参考价值的文章主要介绍了【Java学习笔记】 动态代理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

动态代理

1、什么是动态代理?

前面已经提到了,动态代理就是【在内存】中动态生成【字节码代理类】的技术。(虽然不需要开发者书写,但是在内存层面依然存在该代理对象】

优点

  • 减少了代理类的数量
  • 并且解决了代码复用的问题。
动态代理常见的实现技术包括以下三种
  • JDK内置的动态代理技术 :只能代理接口
    • 位置:java.lang.reflect.Porxy ,是一个注解
  • CGLIB(Code Generation Library)动态代理技术,一个开源项目,生成类库,可以适用于接口和类
    • 但是CGLIB的低层是通过【继承】实现的(虽然是继承,但是由于是在内存动态生成字节码类,所以并不会增加耦合度),所以性能比JDK动态代理好
    • CGLIB的低层还有一个字节码处理框架 【ASM】(可能阅读源码时会遇到)
  • Javassist动态代理技术:东京大学的千叶滋教授所创建的开源项目。为JBOOS实现“aop”框架
    • mybatis框架底层就是用的javassist创建接口的字节码对象

Spring的低层主要是靠JDK内置的动态代理和CGLIB实现

2、Java内置的动态代理

1)如何使用Proxy

还是模拟静态代理的场景。接下来展示一下如何使用。

// 用法
OrderService target = new OrderServiceImpl();
Object proxyInstance = Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new TimerInvocationHandler());

java动态代理,java,学习,代理模式

  • newProxyInstance,翻译就是新建代理对象,本质上该方法做了两件事
    • 在内存中动态的生成了一个代理类的字节码class
    • "new"对象了,通过内存中生成代理类,实例化了该代理对象
  • newProxyInstance() 方法有三个参数,分别的作用进行分析
    • ClassLoader loader ——类加载器,将字节码class文件加载到内存当中。而且JDK要求,目标类的类加载器,必须和代理类的类加载器使用同一个
    • Class<>?[] interfaces ——代理类和目标类实现的共同接口
    • InvocationHandler h —— 翻译是调用处理器
2)调用处理器InvocationHandler

这个参数的含义,在理解之前可以做一个简单的推测,我们在使用代理对象时,增强功能的代码应该写在哪里?(首先JDK肯定是不知道开发者要写什么代码的) 目前的三个参数已经用掉了两个,所以,不难推测调用处理器的作用。结合老杜的笔记,如下:

调用处理器的作用:写增强代码。同时InvocationHandler的接口,那我们就需要实现并重写该接口,再作为参数传入Proxy

重写后就可以发现,该接口需要重写一个方法——invoke()

public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;
思考:为什么要强制实现InvocationHandler接口呢?
  • 因为一个类实现接口必须实现接口中的方法
  • 以下的方法必须是invoke(),因为JDK在低层调用invoke的方法已经写好了
    • 也就是说,invoke方法并不是开发者调用,而是为了给JDK调用
思考2:invoke方法什么时候被调用?(如何调用?)
  1. 首先尝试重写invoke方法

    java动态代理,java,学习,代理模式

  2. 调用target(目标对象)—— 还是调用原对象

    java动态代理,java,学习,代理模式

  3. 尝试将newProxyInstance的返回值进行转型,向下转型为OrderService,并尝试调用。代码如下

    OrderService target = new OrderServiceImpl();
    
    OrderService obj  = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new TimerInvocationHandler());
    obj.insert();
    obj.modify();
    
    • 发现打印出INVOKE了,但是,目标对象的真正的方法无了,没有实现代理方法

      java动态代理,java,学习,代理模式

3)invoke方法的使用

invoke方法有如下三个参数

java动态代理,java,学习,代理模式

  • 第一个参数:代理对象的引用
  • 第二个参数,目标对象上的目标方法
  • 第三个参数,目标方法上的实参

核心思路: invoke方法在执行过程中,使用method方法来调用目标对象的目标方法

那么,如何使用Method来调用目标方法呢?

Method是Java反射的一个类,具体用法需要参照一下API给出的解释。

java动态代理,java,学习,代理模式


也就是说方法四要素:哪个对象、哪个方法、传什么参数、传什么值。

而我们这边需要调用目标对象的目标方法,还缺一个关键 —— 目标对象

那么这里就需要使用构造函数将目标对象传入到该方法内以供调用,经过分得出以下方法,如下。

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

public class TimerInvocationHandler implements InvocationHandler {
    private Object target;

    public TimerInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(">>>BEFORE INVOKE ....");
        long begin = System.currentTimeMillis();
        Object retValue = method.invoke(target, args);
        long end = System.currentTimeMillis();
        System.out.println(">>>AFTER  INVOKE...COST:"+(end-begin));
        return null;
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        OrderService target = new OrderServiceImpl();

        OrderService obj = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new TimerInvocationHandler(target));
        obj.insert();
        obj.modify();
    }
}

输出结果

java动态代理,java,学习,代理模式

这里我们增强了普通的无返回值的方法,那么如何代理(增强)带返回值的方法呢?

4)invoke方法的返回值

对OrderService做如下修改,新增了一个带返回值的方法以便测试

java动态代理,java,学习,代理模式

  • 尝试在上述代码中,不做修改,直接调用代理对象 —— 返回值为空

    java动态代理,java,学习,代理模式

  • 那么在Invoke里 将 方法执行结果return。—— 即可得到预期的结果

    java动态代理,java,学习,代理模式

注意:这个invoke 方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话:invoke 方法必须将目标对象的目标方法执行结果继续返回。

至此,已经完成了通过JDK内置的代理类,实现使用代理模式增强业务代码的目的了。但是代码其实其实还是有点多,可以尝试使用工具类进行一个封装优化。

5)进一步封装一个自定义的工具方法

这里老杜的封装其实不是很严谨,应该传参还需要设置成可以传入一个自定义的处理器,这样这个工具类才会更加易用。改进后如下

工具类

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

public class ProxyUtil {
    /**
     * 生成代理对象
     * @param target 目标对象
     * @param handler 调用处理器
     * @return 代理对象
     */
    public static Object getProxyInstance(Object target , InvocationHandler handler){
        Object proxyInstance = Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler);
        return proxyInstance;
    }
}

优化后客户端写法如下:

public class Client {
    public static void main(String[] args) {
        OrderService target = new OrderServiceImpl();
        TimerInvocationHandler timerInvocationHandler = new TimerInvocationHandler(target);
        OrderService obj = (OrderService) ProxyUtil.getProxyInstance(
            target, timerInvocationHandler);
    }
}

3、CGLIB 动态代理

老杜提到,这里的CGLIB写法并不是重点,需要了解一个大概的写法,并且明白,是使用继承的特性实现的动态代理。所以,CGLIB会更加的泛用。

1)什么是CGLIB(和JDK动态代理的区别
  • CGLIB既可以代理接口,又可以代理类
  • 低层是采用继承的方式是实现的
  • 所以被代理的目标类,不能使用final修饰
  • 另外。CGLIB功能更强大,效率也更高
2)用法/写法
  1. 既然提到CGLIB可以继承普通类,那么我们就直接声明一个普通类作为目标对象
  2. 创建客户端类 Client
  3. 创建字节码增强对象 Enhancer,作为CGLIB库中的核心对象,就是依靠它来生存代理类的
  4. 设置父类(告诉CGLIB父类,即目标类是谁)
  5. 设置回调(相当于JDK代理中的调用处理器,invocationHandler)
  6. 创建代理对象:做两件事
    • 在内存中生成目标对象的子类,其实就是代理类的字节码
    • 创建代理对象(通过代理类
3)回调如何设置?

实现的思路和JDK动态地理类似,都是实现一个接口

  • InvocationHandler (方法四要素)
  • MethodInterceptor (方法四要素)

不同之处在于,target目标对象不需要再手动创建构造函数传入了

如果运行报错 ,是因为JDK版本太高了,需要加参数

--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED
4)测试并观察代理对象的运行结果

java动态代理,java,学习,代理模式

  • 结果是成功的,实现了业务增强的效果。

  • 老杜这里提到,要注意这个打印出来的对象地址是比较特殊的,可以用于以后调试的时候查看

    java动态代理,java,学习,代理模式

测试代码如下:

业务逻辑:

package com.zhc.cglib.proxy.service;

public class OrderServiceCGlib {

    public void modify() {
        try {
            Thread.sleep(1500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("FINISH MODIFY");
    }


    public String getName() {
        System.out.println("GET NAME ...");
        return "ZHANGSAN";
    }
}

自定义方法拦截器:

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class TimerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long begin = System.currentTimeMillis();

        Object retValue = methodProxy.invokeSuper(target, objects);

        long end = System.currentTimeMillis();

        System.out.println("耗时:"+(end-begin));
        return retValue;
    }
}

客户端 :文章来源地址https://www.toymoban.com/news/detail-759560.html

package com.zhc.cglib.proxy.client;

import com.zhc.cglib.proxy.service.OrderServiceCGlib;
import com.zhc.cglib.proxy.service.TimerMethodInterceptor;
import net.sf.cglib.proxy.Enhancer;

public class CGlibClient {
    public static void main(String[] args) {
        /**
         * 1、创建增强器
         * 2、设置父类
         * 3、设置回调
         * 4、获取代理对象
         */
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderServiceCGlib.class);
        enhancer.setCallback(new TimerMethodInterceptor());
        OrderServiceCGlib orderServiceCGlib = (OrderServiceCGlib) enhancer.create();
        System.out.println(orderServiceCGlib.getName());
        orderServiceCGlib.modify();

        System.out.println(orderServiceCGlib);
    }
}

到了这里,关于【Java学习笔记】 动态代理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java中的代理模式(二)JDK动态代理

    大家好👋,我是极客涛😎,上一篇中我们对代理模式有两大类,静态代理和动态代理,对于静态代理相信大家都信手拈来。对于动态代理还有两种实现,一种是java原生的Jdk代理,一种是Cglib方式。因为涉及到源码解读,所以我也将分两期完成,本期主要讲讲JDK动态代理的实

    2024年01月22日
    浏览(42)
  • Java 代理模式详解,静态代理与动态代理的区别及优缺点

    代理模式是一种常用的设计模式,它允许通过引入一个代理对象来控制对目标对象的访问。在Java中,代理模式被广泛应用,它可以提供额外的功能,如权限检查、缓存、日志记录等,同时还能在不修改目标对象的情况下对其进行扩展。 代理模式(Proxy Pattern)是指通过代理对象

    2024年02月11日
    浏览(44)
  • 【Java】jdk1.8 Java代理模式,Jdk动态代理讲解(非常详细,附带class文件)

       📝个人主页:哈__ 期待您的关注  想要学代理模式,我们就要先弄清一个概念 “什么是代理”? 在我们的现实生活中,你或许不少听过关于代理的名词,如:代理商。那什么又叫做代理商?让我一个词来形容就是 中间商。 举个例子,在你买二手房的时候,你一般不会直

    2024年04月15日
    浏览(45)
  • 代理模式 静态代理和动态代理(jdk、cglib)——Java入职第十一天

            一个类代表另一个类去完成扩展功能,在主体类的基础上,新增一个代理类,扩展主体类功能,不影响主体,完成额外功能。比如买车票,可以去代理点买,不用去火车站,主要包括静态代理和动态代理两种模式。 代理类中包含了主体类 无法根据业务扩展,每一次

    2024年02月10日
    浏览(51)
  • Java学习笔记:爬虫-操作动态网页的Selenium

    Why Selenium? 有些网页内容是在浏览器端动态生成的,直接Http获取网页源码是得不到那些元素的。 Selenium可以自动启动一个浏览器、打开网页,可以用程序操作页面元素,也可以获得浏览器当前页面动态加载的页面元素。 比如:百度图片的图片是动态加载的。 用法: 1、下载安

    2024年02月13日
    浏览(44)
  • 黑马程序员 Java设计模式学习笔记(一)

    目录 一、设计模式概述 1.1、23种设计模式有哪些? 1.2、软件设计模式的概念 1.3、学习设计模式的必要性 1.4、设计模式分类 二、UML图 2.1、类图概述 2.2、类图的作用 2.3、类图表示法 类的表示方式 类与类之间关系的表示方式 关联关系 聚合关系 组合关系 依赖关系 继承关系

    2024年01月19日
    浏览(53)
  • Java SE 学习笔记(十九)—— XML、设计模式

    在有些业务场景下,存储数据或者传输数据给别人的时候需要满足一定的规范进行组织 XML 的全称为(EXtensible Markup Language),是一种 可扩展 的 标记语言 ,是一种数据表示格式,可以用于自定义数据格式,可以描述非常复杂的数据结构,常用于传输和存储数据。 例如: XM

    2024年02月08日
    浏览(40)
  • 反射机制-体会反射的动态性案例(尚硅谷Java学习笔记)

    // 举例01 public class Reflect{ } 案例:榨汁机榨水果汁,水果分别有果(com.reflect.Apple)、香蕉(Banana)、桔子(Orange)等。 效果如图。 提示: 1、声明(Fruit)水果接口,包含榨汁抽象方法: void squeeze(); /skwi:z/ 2、声明榨汁机(Juicer),包含运行方法: public void run(Fruit f),方法体中,调用f的榨汁方

    2024年02月11日
    浏览(47)
  • java中的静态代理、jdk动态代理以及CGLIB 动态代理

    代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能 那以下文章主要谈三种代理模式, 分别是静态代理,jdk的动态代理,cglib的动

    2024年02月11日
    浏览(44)
  • 【Java】纯小白的三种工厂模式基础知识学习笔记

    在Java中,工厂模式是一种设计模式,用于创建对象而无需指定明确的类。工厂模式通过定义一个共同的接口或抽象类来创建对象,然后由工厂类根据特定条件或参数来实例化具体的对象。 工厂模式通常包括三种类型:简单工厂模式、工厂方法模式和抽象工厂模式。 简单工厂

    2024年02月20日
    浏览(57)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包