Java代理模式 - 静态/JDK动态代理使用及实现原理分析(下)

Java代理模式 - 静态/JDK动态代理使用及实现原理分析(下)

作者:Jackson

目录大纲

第三部分 动态代理

3.1 静态代理和动态代理模式的对比

在静态代理中目标类很多的时候,可以使用动态代理,避免静态代理的缺点

动态代理中目标类即使很多:

  1. 代理类数量可以很少
  2. 当修改了接口中的方法时,不会影响代理类
在 java 中,一般情况下要想创建对象

1. 创建类文件,java 文件编译为class
2. 使用构造方法,创建类的对象

而动态代理跳过了第 1 步可以直接创建类的对象。

3.2 动态代理的介绍

img

动态代理是指代理类对象在程序运行时由 JVM 根据反射机制动态生成的。动态代理不需要定义代理类的 .java 源文件可以动态地指定要代理目标类

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

换句话说:动态代理是一种创建 java 对象的能力,让你不用创建 TaoBao 类就能创建代理类对象,除去了中间商。(跳过代理类获得代理类对象)

动态代理的实现方式常用的有两种:使用 JDK 动态代理,与通过 CGLIB 动态代理

动态代理的实现:

  1. jdk 动态代理(理解):使用 java 反射包中的类和接口实现动态代理的功能,反射包 java.lang.reflect,里面有三个类:InvocationHandlerMethodProxy
  2. cglib 动态代理(了解):cglib 是第三方的工具库,用于创建代理对象
    1. cglib 的原理是继承,cglib 通过创建继承于目标类的子类,在子类中重写父类中同名的方法,实现功能的修改。
    2. 因为 cglib 是继承,重写方法,所以要求目标类不能是 final 修饰的,方法也不能是 final 修饰的。cglib 对目标类的要求比较宽松,只要能被继承就可以了。cglib 在很多的框架中使用,比如mybatis、spring框架中都有所使用。

3.2 回顾反射 Method类

先用一个例子回顾一下反射:

HelloService.java 接口:

package com.kaho.service;

public interface HelloService{
    public void sayhello(String name);
}

HelloServiceImpl.java 实现类:

package com.kaho.service.impl;

public class HelloServiceImpl implements HelloService{
    @Override
    public void sayhello(String name){
        system.out.println("你好!" + name);
    }
}

测试类:

package com.kaho;

import com.kaho.Impl.HelloServiceImpl;
import com.kaho.service.HelloService;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestApp {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//        HelloService service = new HelloServiceImpl();
//        service.sayhello("张三");
		//以上是常规方法执行sayhello方法
		
        //下面使用反射机制创建sayhello方法,核心:Method(类中的方法)
        HelloServiceImpl target = new HelloServiceImpl();
		/**
		*   获取与sayhello名称对应的Method类对象,通过接口的.class对象调用:
		*   public Method getMethod(String name, Class<?>... parameterTypes)
		*   name : 是一个String,它指定了所需方法的简单名称
		*   parameterTypes : 以声明顺序标识方法的形式参数类型的类对象的数组
        */
        Method method = HelloService.class.getMethod("sayhello", String.class);
		//通过method可以执行sayhello方法的调用
        /**
        *  public Object invoke(Object obj, Object... args)
        *       表示执行方法的调用
        *   参数:
        *       1.Object obj : 表示对象,目的是要执行这个(接口实现类)对象的方法
        *       2.Object...args : 方法执行时的传入的参数值
        * 	返回值:
        *       Object : 方法执行后的返回值
        */
        Object ret = method.invoke(target, "李四");
    }
}

img

Method类的结构图

  • Class Method
    • java.lang.Object
      • java.lang.reflect.AccessibleObject
        • java.lang.reflect.Executable
          • java.lang.reflect.Method

3.3 JDK 动态代理(理解)

jdk 动态代理是基于 Java 的反射机制实现的。使用 jdk 中接口和类实现代理对象的动态创建。

Jdk 的动态要求目标对象必须实现接口,这是 java 设计上的要求。

从 jdk1.3 以来,java 语言通过 java.lang.reflect 反射包提供三个类支持代理模式 Proxy, Method 和 InovcationHandler。

3.3.1 InvocationHandler 接口

public interface InvocationHandler

InvocationHandler 接口叫做调用处理器,负责完成调用目标方法,并增强功能。

通过代理对象执行目标接口中的方法,会把方法的调用分派给调用处理器 (InvocationHandler)的实现类,执行实现类中的 invoke() 方法,我们需要把代理类要完成的功能写在 invoke() 方法中 。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
        proxy:jdk在运行时创建的代理对象,在方法中直接使用
        method:代表目标方法,jdk提供的Method类对象
        args:代表目标方法的参数,jdk运行时赋值的
     //以上三个参数都是 jdk 运行时赋值的,无需程序员给出。
        Throwable处理代理实例上的方法调用并返回结果

在 invoke 方法中可以截取对目标方法的调用,在这里进行功能增强。实现了 InvocationHandler 接口的类用于加强目标类的主业务逻辑。这个接口中有一个方法 invoke(),具体加强的代码逻辑就是定义在该方法中的。通过代理对象执行接口中的方法时,会自动调用 invoke()方法。

当在代理对象上调用方法时,方法调用将被编码并分派到其调用处理程序(实现了 InvocationHandler 接口的类)的 invoke() 方法。

InvocationHandler 接口:表示你的代理要干什么。
怎么用: 1.创建类实现接口 InvocationHandler
2.重写 invoke() 方法, 把原来静态代理中代理类要完成的功能写在其中。

3.3.2 Method类

Method类:表示方法的,确切地说就是目标类中的方法。

作用:通过 Method 中的 invoke() 方法可以执行某个目标类的目标方法,Method.invoke();

这两个 invoke()方法,虽然同名,但无关。

public Object invoke (Object obj, Object...args)  
    obj:表示目标对象 
	args:表示目标方法参数,就是其上一层(InvocationHandler中)invoke方法的第三个参数 
    
	该方法的作用是:调用执行obj对象所属类的方法,这个方法由其调用者Method对象确定。 

    在代码中,一般的写法为 method.invoke(target, args); 
	其中,method为上一层(InvocationHandler中)invoke方法的第二个参数。这样即可调用目标类的目标方法。 

Object ret= method.invoke(target,"李四")
内部是调用了目标方法

说明:method.invoke() 就是用来执行目标方法的,等同于静态代理中的

   		//向厂家发送订单,告诉厂家,我买了U盘,厂家发货
    	//发送给工厂,我需要的订单,返回报价
        float price = factory.sell(amount);

3.3.3 Proxy类

proxy类:核心的对象,创建代理对象。之前创建对象都是 new 类的构造方法()
现在我们是使用Proxy类的方法,代替new的使用。
方法: 静态方法 newProxyInstance()
作用是:依据目标对象、业务接口及调用处理器三者,自动生成一个动态代理对象。

等同于静态代理中的 TaoBao taoBao = new TaoBao();

public static Object newProxyInstance( ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)  throws IllegalArgumentException
 参数:
	 1.ClassLoader loader : 目标类的类加载器,负责向内存中加载对象的。使用反射机制获取对象的ClassLoader
	  如何获取?  类a , a.getCalss().getClassLoader(),  目标对象的类加载器
	  每一个类都继承Object类,在Object中有一个getClass方法,表示 类对象的运行时类的Class对象。而Class类里面有一个public ClassLoader getClassLoader()方法
     2.Class<?>[] interfaces : 目标类实现的接口数组,通过目标对象的反射可获取
     3.InvocationHandler h : 我们自己写的调用处理器,代理类要完成的功能。 

	返回值 : 就是代理对象

3.4 JDK 动态代理的实现

jdk 动态代理是代理模式的一种实现方式,其只能代理接口

实现步骤

1、新建一个接口,作为目标接口 ,在其中声明目标类要完成的方法(功能)

2、为接口创建一个实现类,这是目标类

3、创建 java.lang.reflect.InvocationHandler 接口的实现类,在 invoke() 方法中完成代理类的功能:

1.调用目标方法
2.增强功能

4、使用 Proxy.newProxyInstance() 方法创建动态代理对象,并把返回值强制转为接口类型。

在 idea 中创建 java project

工程名称:ch02_dynamic_proxy

1)定义目标接口

新建一个接口,作为目标接口 ,在其中声明目标类要完成的方法(功能)

package com.kaho.service;

public interface UsbSell {
    float sell(int amount);
}

2)定义目标接口实现类

为接口创建一个实现类,这是目标类

package com.kaho.factory;

import package com.kaho.service.UsbSell;

//金士顿厂商(目标类)
public class UsbKingFactory implements UsbSell {
    //目标方法
    @Override
    public float sell(int amount) {
        System.out.println("目标类中,执行了sell目标方法");
        //u盘代理商在厂商购买u盘的价格,目前先假定只购买一个u盘
        return 85.0f;
    }
}

3)定义调用处理程序

创建 java.lang.reflect.InvocationHandler 接口的实现类,在 invoke() 方法中完成代理类的功能:

1.调用目标方法
2.增强功能

先创建好实现类:

public class MyHandle implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
}

在之前的静态代理的实现方法上进行修改:

//之前的代理类
//淘宝是一个商家,代理金士顿厂家U盘的销售
public class TaoBao implements usbSell {
	//声明 商家代理的厂家 具体是谁
    private UsbKingFactory factory =new UsbKingFactory();

    @Override
	//实现销售U盘功能
    public float sell(int amount) {
		//向厂家发送订单,告诉厂家,我买了U盘,厂家发货
		//发送给工厂,我需要的订单,返回报价
        float price = factory.sell(amount);
		//商家需要加价也就是代理要增加价格(功能增强)
        price = price + 25;//在单价之上增加25元作为利润
		//在目标类的方法调用后,你做的其他功能,都是增强的意思
        System.out.println("淘宝给你返回一个优惠券\红包");
		//增加后的价格
        return price;
    }
}

需要注意的重要细节:

//需要创建有参构造器,参数是目标对象。为的是完成对目标对象的方法调用
	//传入是谁的对象,就给谁创建代理
    public MyHandle(Object target) {
        this.target = target;
    }

invoke里面的设置
    Object res =null;
    //向厂家发送订单,告诉厂家,我买了U盘,厂家发货
    //发送给工厂,我需要的订单,返回报价
//    float price = factory.sell(amount);
    res = method.invoke(target,args);//待执行的目标方法,执行后返回值

最后得到完整的调用处理程序:

package com.kaho.handler;

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

//必须实现InvocationHandler接口,来完成代理类要做的功能(1.调用目标方法 2.功能增强)
public class MyHandler implements InvocationHandler{
    
    private Object target = null;//外面传进来的目标对象
    
    //动态代理:目标对象是活动的,不是固定的,需要传入进来。
    //传入是哪个目标类的对象,就给它创建代理
    public MyHandle(Object target) {
        //给目标对象赋值
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        Object res = null;
        //向厂家发送订单,告诉厂家,我买了U盘,厂家发货
		//发送给工厂,我需要的订单,返回报价
//        float price = factory.sell(amount);
        res = method.invoke(target, args);//执行目标方法
        
		//商家需要加价也就是代理要增加价格(功能增强)
//        price = price + 25;//在单价之上增加25元作为利润
        if(res != null){
            float price = (float)res;
            price =price + 25;
            res = price;
        }
        
		//在目标类的方法调用后,你做的其他功能,都是增强的意思
        System.out.println("淘宝给你返回一个优惠券\红包");
		//增加后的价格
//        return price;
        return res;
    }
}

4)创建动态代理对象

使用 Proxy.newProxyInstance() 方法创建动态代理对象,并把返回值强制转为接口类型。

创建一个MainShop类.

package com.kaho;

import com.kaho.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class MainShop {
    public static void main(String[] args){ 
		//创建对象,使用Proxy
		//1.创建目标对象
        //UsbKingFactory factory = new UsbKingFactory();
        UsbSell factory = new UsbKingFactory();
		//2.创建Invocationhandler对象
        InvocationHandler handle = new MyHandle(factory);

		//3.创建代理对象
        UsbSell proxy = (UsbSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), handle);
		//4.通过代理对象执行业务方法
        float price = proxy.sell(1);
        System.out.println("通过动态代理对象,调用方法:" +price);
    }
}
目标类中,执行了sell目标方法
淘宝给你返回一个优惠券\红包
通过动态代理对象,调用方法:110.0

3.5 JDK 动态代理的执行流程

img

此时对程序进行 debug,在 InvocationHandler 实现类中重写的 invoke() 方法中打一个断点
img

此时再观察,代理对象MyHandler里面的invoke方法的参数
在这里插入图片描述

img

3.6 CGLIB 动态代理(了解)

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

使用 JDK 的 Proxy 实现代理,要求目标类与代理类实现相同的接口。若目标类不存在 接口,则无法使用该方式实现。

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Kaho Wang

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

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

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

打赏作者

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

抵扣说明:

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

余额充值