• JDK动态代理


    搬运自:动力节点的Java-JDK动态代理(AOP)使用及实现原理分析



    1、代理模式

    代理模式是指,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用。

    换句话说, 使用代理对象,是为了在不修改目标对象的基础上, 增强主业务逻辑。客户类真正的想要访问的对象是目标对象,但客户类真正可以访问的对象是代理对象,所以客户类对目标对象的访问是需要通过访问代理对象来实现的。当然,代理类与目标类要实现同一个接口。

    例如: 有 A, B, C 三个类, A 原来可以调用 C 类的方法, 现在因为某种原因 C 类不允许 A 类调用其方法,但 B 类可以调用 C 类的方法。 A 类通过 B 类调用 C 类的方法。这里 B 是 C 的代理。 A 通过代理 B 访问 C.

    通过代理的访问关系:

    image-20200821000759414

    Window 系统的快捷方式也是一种代理模式。快捷方式代理的是真实的程序,双击快捷方式是启动它代表的程序。


    1.1、代理模式的作用

    1. 控制访问:目标对象不让外界直接访问,代理类帮助外界间接访问目标对象。例如顾客买商品不能去厂家买,只能找商家。
    2. 增强功能:在目标方法调用的同时,增加额外功能

    1.2、代理模式的分类

    1. 静态代理
    2. 动态代理

    2、一个实际的业务需求

    需求:用户需要购买 u 盘, u 盘厂家不单独接待零散购买,厂家规定一次最少购买 1000 个以上,用户可以通过淘宝的代理商或者微商进行购买。

    淘宝、微商都是 u 盘工厂的代理商, 他们代理对 u 盘的销售业务。

    购买路线:用户$-->$代理商(淘宝,微商)$-->$厂家(金士顿,闪迪等不同的厂家)

    金士顿(King)对购买 1 千以上的单价是 85,3 千以上单价是 80,5 千以上单价是 75。 单个单价 120 元。


    定义卖 u 盘的业务接口:

    public interface UsbSale {
    
        float sale(int amount);
    }
    

    3、静态代理

    静态代理是指,代理类在程序运行前就已经定义好相应的的 .java 源文件,其与目标类的关系在程序运行前就已经确立。 在程序运行前代理类已经编译为.class 文件。

    静态代理类是自己手工实现的,需要创建一个java类,表示代理类,同时你所要代理的目标类是确定的。


    3.1、实现需求

    1. 创建目标类(厂家),实现业务接口
    2. 创建代理类(商家)也业务接口
    3. 创建客户端类(顾客),调用代理类的方法购买 u 盘

    3.1.1、目标类

    public class UsbKingFactory implements Usbsell {
    
        @Override
        public float sell(int amount) {
    
    
            if(amount < 0)
                throw new IllegalArgumentException("商品数量参数错误");
            if(amount < 1000)
                return 85 * amount;
            else if(amount < 3000)
                return 80 * amount;
            else
                return 75 * amount;
        }
    }
    

    3.1.2、代理类

    代理类一般需要完成两个功能:

    1. 目标类中方法的调用
    2. 功能增强
    // 定义一个淘宝商家,卖金士顿的 u 盘
    public class Taobao implements Usbsell {
    
        @Override
        public float sell(int amount) {
    
            UsbKingFactory kingFactory = new UsbKingFactory();
            // 这里就是调用目标类的方法
            float price = kingFactory.sell(amount);
            // 这里就是功能增强
            System.out.println("淘宝质量保证");
            return price + 25*amount;
        }
    }
    

    3.1.3、客户端类

    客户端类访问代理类,顾客进入淘宝买金士顿的 u 盘,

    public class ShopApplication {
    
        public static void main(String[] args) {
            
            float price = 0.0f;
            Taobao taobao = new Taobao();
            price = taobao.sell(1);
            System.out.println("淘宝 购买价格为:"+price);
        }
    }
    

    使用代理的访问关系图:

    image-20200822181832557


    3.2、静态代理的优缺点

    3.2.1、优点

    1. 实现简单
    2. 容易理解

    3.2.2、缺点

    1. 代码复杂,难于管理。
      代理类和目标类实现了相同的接口, 每个代理类都需要实现目标类的方法, 这样就出现了大量的代码重复。如果业务接口增加一个方法,除了所有目标类需要实现这个方法外,所有代理类也需要实现这个方法。增加了代码维护的复杂度。
    2. 代理类依赖目标类,代理类过多。代理类只服务于一种类型的目标类,如果要服务多个类型。势必要为每一种目标类都进行代理,静态代理在程序规模稍大时就无法胜任了,代理类数量过多。

    4、动态代理

    动态代理是指,代理类对象在程序运行时由 JVM 根据反射机制动态生成的。 动态代理不需要定义代理类的 .java 源文件。在程序运行时,调用 jdk 提供的方法就能创建代理类对象。

    动态代理其实就是 jdk 运行期间, 动态创建 class 字节码并加载到 JVM。

    动态代理的作用:可以在不改变原来目标方法代码的前提下, 在代理中增加自己的功能代码。

    动态代理的实现方式常用的有两种:

    • 使用 JDK 动态代理
    • 通过 CGLIB 动态代理

    CGLIB简介

    CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的 Code 生成类库,它可以在运行期扩展 Java 类与实现 Java 接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP。

    使用 JDK 的 Proxy 实现代理,要求目标类与代理类实现相同的接口。若目标类不存在接口, 则无法使用该方式实现。但对于无接口的类,要为其创建动态代理,就要使用 CGLIB 来实现。 CGLIB 代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。 所以, 使用CGLIB 生成动态代理,要求目标类必须能够被继承,即不能是 final 的类。

    CGLIB 经常被应用在框架中,例如 MyBatis,Spring ,Hibernate 等。 CGLIB 的代理效率高于 Jdk。对于 CGLIB 一般的开发中并不使用。做了一个了解就可以。


    5、JDK 动态代理

    JDK 动态代理是基于 Java 的反射机制实现的。

    JDK 动态代理是代理模式的一种实现方式,其只能代理接口,这是 Java 设计上的要求。

    从 JDK1.3 以来, java 语言通过 java.lang.reflect 包提供三个类支持代理模式: ProxyMethodInovcationHandler

    1. InvocationHandler,用于实现业务逻辑
    2. Method,用于执行目标方法
    3. Proxy,用于创建代理对象

    5.1、InvocationHandler 接口

    InvocationHandler 接口叫做调用处理器,为什么叫做调用处理器呢?因为之后我们创建的代理对象,调用被代理对象的方法时,InvocationHandlerinvoke 就要做处理了,代理对象的原方法和功能增强都要在这里执行。该接口也只有这一个方法:

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

    参数

    • Object proxy:jdk生成的代理对象,jdk运行时赋值
    • Method method:目标类中的方法(Method类),jdk运行时赋值
    • Object[] args:目标类中方法的参数, jdk运行时赋值

    InvocationHandler 的使用方法:

    1. 创建类实现 InvocationHandler
    2. 重写 invoke() 方法, 把原来由静态代理实现的代理类要完成的功能,写在 invoke() 方法中。

    实现了 InvocationHandler 接口的类用于加强目标类的主业务逻辑,具体加强的代码逻辑就是定义在 invoke() 中,通过代理对象执行接口中的方法时,会自动调用 invoke() 方法。


    5.2、Method 类

    Method 类,表示目标类中的方法,通过 Method 类可以执行某个目标类中的方法

    method.invoke(目标类的对象,方法参数);
    

    Method 的反射用法:

    // 1、接口
    public interface HelloService {
    
        void sayHello(String words);
    }
    
    // 2、接口的实现类,也就是目标类
    public class HelloServiceImpl implements HelloService {
        
        @Override
        public void sayHello(String words) {
            System.out.println(words);
        }
    }
    
    // 3、测试类
    public class Main {
    
        public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    
    
    //        HelloService helloService = new HelloServiceImpl();
    //        helloService.sayHello("你好,张三");
    
            // 创建目标类的对象
            HelloService target = new HelloServiceImpl();
            
            // 获取目标类的指定方法
            // 参数1:方法的名称
            // 参数2:方法的参数的类型
            Method method = HelloServiceImpl.class.getMethod("sayHello", String.class);
            
            // invoke,调用方法
            // 参数1:执行该方法的对象(方法必须通过对象才能调用)
            // 参数2:方法的实参
            // invoke有返回值,是目标类方法执行后的返回值
            method.invoke(target, "你好,李四");
        }
    }
    

    5.3、Proxy 类

    JDK 的 java.lang.reflect.Proxy是 JDK 动态代理实现的核心,使用其静态方法 newProxyInstance(),然后依据目标对象、业务接口及调用处理器三者,自动生成一个动态代理对象。

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler);
    

    参数:

    • ClassLoader loader:目标类的类加载器,通过目标对象的反射获取,如:类a,a.getCalss().getClassLoader()
    • Class<?>[] interfaces:目标类实现的接口数组,通过目标对象的反射获取
    • InvocationHandler handler:调用处理器,手动写

    返回值:目标类的代理对象


    6、实现 JDK 动态代理

    jdk动态代理,必须有接口,目标类必须实现接口, 没有接口时,需要使用cglib动态代理。

    1. 创建一个接口,作为目标接口,在其中定义一些功能
    2. 为目标接口创建一个实现类,作为目标类
    3. 创建 InvocationHandler 接口的实现类,在 invoke()中完成代理类的功能
    4. 创建动态代理对象,使用 Proxy.newProxyInstance() 方法,并把返回值强制转为接口类型

    代码演示:

    // 1、创建接口
    public interface Usbsell {
    
        float singleSell();
        float wholesale(int amount);
    }
    
    // 2、目标类
    public class UsbKingFactory implements Usbsell {
    
        @Override
        public float singleSell() {
    
            System.out.println("金士顿u盘出厂价100");
            return 100.0f;
        }
    
        @Override
        public float wholesale(int amount) {
    
            if(amount <= 1)
                throw new IllegalArgumentException("商品数量参数错误");
            System.out.println("金士顿u盘,批发更便宜");
            if(amount < 1000)
                return 85 * amount;
            else if(amount < 3000)
                return 80 * amount;
            else
                return 75 * amount;
        }
    }
    
    // 3、调用处理器的实现类
    public class UsbHandler implements InvocationHandler {
    
        private Object target = null;
    
        public UsbHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            Object ret = null;
            // 执行目标类的方法
            ret = method.invoke(target, args);
    
            // if-else是不是不对呀?这样就不是动态了是不?
            if(method.getName().equalsIgnoreCase("singleSell")){
    
                // 功能增强
                if(ret != null){
    
                    Float price = (Float) ret;
                    price += 25;
                    ret = price;
                }
                System.out.println("淘宝单卖加价25");
            }else if(method.getName().equalsIgnoreCase("wholesale")){
    
                // 功能增强
                if(ret != null){
    
                    Float price = (Float) ret;
                    price += (Integer)args[0]*20;
                    ret = price;
                }
                System.out.println("淘宝批发,单个加价20");
            }
            return ret;
        }
    }
    
    // 4、创建动态代理对象
    public class ShopApplication {
    
        public static void main(String[] args) {
    
            // 1、创建目标对象
            UsbKingFactory kingFactory = new UsbKingFactory();
    
            // 2、创建InvocationHandler对象
            UsbHandler usbHandler = new UsbHandler(kingFactory);
    
    
            Usbsell proxyInstance = (Usbsell) Proxy.newProxyInstance(kingFactory.getClass().getClassLoader(),
                    kingFactory.getClass().getInterfaces(),
                    usbHandler);
    
            float singleSell = proxyInstance.singleSell();
            System.out.println("商店里一个金士顿u盘卖"+singleSell);
    
            float wholesale = proxyInstance.wholesale(500);
            System.out.println("批发500个"+wholesale);
    
        }
    }
    
  • 相关阅读:
    vs2003 序列化json
    异步执行sql语句
    【读书笔记】原型模式代码(C#)
    【读书笔记】工厂方法模式代码(C#,C++)
    【转】C++纯虚函数
    【读书笔记】模板方法模式代码(C++)
    【读书笔记】代理模式翻译成C++了
    【读书笔记】模板方法模式(C#)
    【读书笔记】原型模式代码(C++) 第一版
    【读书笔记】原型模式第二版(C++)新鲜出炉
  • 原文地址:https://www.cnblogs.com/sout-ch233/p/13556909.html
Copyright © 2020-2023  润新知