代理模式介绍、实现动态代理(JDK、CGLIB)及对比

时间:2022-09-25 00:45:31

(目录)


代理模式

什么是代理模式?

  1. 代理就是帮别人做事情

    如:工厂的中介,中介负责为工厂招收工人,那么中介就是工厂的代理;客户通过商家购买东西,商家向厂家购买货物,商家就是工厂的代理

  2. 在开发中存在 a类需要调用c类的方法,完成某一个功能,但是c禁止a调用。

    这时,可以在a和c之间创建一个b类代理,a类访问b类,b类访问c类。例如:登录的时候需要进行短信验证,这个时候代理就是中国移动的子公司来完成短信的发送功能

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


使用代理模式的作用

​ 代理模式能带给我们控制访问某个对象的能力,在某些情况下,一个对象的某些方法想要进行屏蔽或者某种逻辑的控制,则我们可以通过代理的方式进行。再此能力上,引申出来的作用,也是目前在开发中经常使用的一个作用,就是——“在不修改原对象代码的基础上,对原对象的功能进行修改或者增强”。

  • 控制访问代理类不让你访问目标,例如商家不让用户访问厂家

代理模式介绍、实现动态代理(JDK、CGLIB)及对比

  • 功能增强:其中目标对象实现真正的功能,但是代理对象可以对目标对象的功能做进一步的扩充

代理模式介绍、实现动态代理(JDK、CGLIB)及对比


实现代理的方式

有两种模式:静态代理动态代理


静态代理

  • 静态代理:代理类是自己手动创建的,所需要代理的目标类是确定的,实现简单容易理解

例如:A类(用户)-> B类中介 -> C类(目标)

具体实现的代码逻辑:

  1. 创建一个接口,定义接口方法,表明B与C类的共同方法
  2. 创建C类,实现接口方法
  3. 创建B类,也就是代理,需要实现接口方法
  4. 创建A类,调用B中介的方法

动态代理

  • 动态代理(JDK代理,接口代理):利用的反射机制动态地生成代理的对象,我们不需要知道谁代理谁。代理类的那部分代码被固定下来了,不会因为业务的增加而逐渐庞大。
  • 在程序执行过程中,使用 jdk 的反射机制,创建代理类对象, 并动态的指定要代理目标类(一种创建java对象的能力),不用自我创建B类,就可以自动创建代理类对象
  • 通过反射机制创建对象

静态代理 实现

实现步骤:

  1. 创建一个接口,定义卖u盘的方法, 表示你的厂家和商家做的事情。
  2. 创建厂家类实现1步骤的接口
  3. 创建商家,就是代理,也需要实现1步骤中的接口
  4. 创建客户端类调用商家的方法买一个u盘

用户访问商家类,而用户不能直接访问厂家类

  • 接口类:
// 表示功能的,厂家,商家都要完成的功能
public interface UsbSell {

    //定义方法 参数 amount:表示一次购买的数量,暂时不用
    //返回值表示一个u盘的价格。
    float sell(int amount);

    //可以多个其它的方法
    //void print();
}
  • C类(工厂)目标类的改写接口类 方法如下:
//目标类: 金士顿厂家, 不接受用户的单独购买。
public class UsbKingFactory implements UsbSell {
    @Override
    public float sell(int amount) {
        System.out.println("目标类中的方法调用 , UsbKingFactory 中的sell ");
        //一个128G的u盘是 85元。
        //后期根据amount ,可以实现不同的价格,例如10000个,单击是80, 50000个75
        return 85.0f;
    }
}
  • B类(中介)改写接口类方法如下
  1. 目标类C类中方法的调用
  2. 功能增强

先访问C类,才能给A类

//taobao是一个商家,代理金士顿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; //增强功能,代理类在完成目标类方法调用后,增强了功能。
        //在目标类的方法调用后,你做的其它功能,都是增强的意思。
        System.out.println("淘宝商家,给你返一个优惠券,或者红包");

        //增加的价格
        return price;
    }
}
  • A类 代理(中介) 测试类:
public class shopMain {
    public static void main(String[] args){
//             创建代理的商家淘宝对象
        TaoBao taoBao = new TaoBao();
//        我只向淘宝买一件产品,得到报价
        float price = taoBao.sell(2);
        System.out.println("购买一件产品.淘宝的报价为: "+price);
    }
}

输出:

目标类中的方法调用 , UsbKingFactory 中的sell 
淘宝商家,给你返一个优惠券,或者红包
110.0

静态代理的优缺点

优点:

  • 实现简单
  • 容易理解

缺点:

  • 当目标类增多了,代理类也需要增加

    例如:上例中创建了一个工厂类,那么该类只能代表一个工厂,当建立了其它品牌的工厂后,还需要为该工厂创建代理类

  • 当你的接口中功能增加了 或者修改了,会影响众多的实现类。

比如:目标C类,代理B类都需要修改。不修改可不行,因为接口的实现类必须都要实现。


动态代理 实现

为了解决静态的缺点,产生了动态代理 当静态代理的目标类C很多的时候,可以使用动态代理

代理模式介绍、实现动态代理(JDK、CGLIB)及对比

动态代理的实现方式有两种:一种是JDK动态代理,一种是CGLIB动态代理

  • 所谓JDK动态代理,是使用 java反射包中的类和接口实现动态代理的功能

-> 反射包 java.lang.reflect , 里面有三个类 : InvocationHandler , Method, Proxy

  • 所谓CGLIB动态代理。是通过继承目标类。

-> 在子类中重写父类同名方法,实现功能修改(重写的方法不能是final)


① JDK 动态代理机制

在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。

Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        ......
    }

这个方法一共有 3 个参数:

  1. loader :类加载器,用于加载代理对象。
  2. interfaces : 被代理类实现的一些接口
  3. h : 实现了 InvocationHandler 接口的对象;

要实现动态代理的话,还必须需要实现InvocationHandler 来自定义处理逻辑。

当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。


InvocationHandler,Method,Proxy类

1 Interface InvocationHandler

public interface InvocationHandler {

    /**
     * 当你使用代理对象调用方法的时候实际会调用到这个方法
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

invoke() 方法有下面三个参数:

  1. proxy :动态生成的代理类
  2. method : 与代理类对象调用的方法相对应
  3. args : 当前 method 方法的参数

也就是说:

你通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。

你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。

代理类完成的功能
    1.调用目标方法,执行目标方法的功能
    2.功能增强,在目标方法调用时,增加功能

2 Method

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

作用:

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

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

 object ret= method. invoke(service22,"李四")

说明:

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

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

3 Proxy类

proxy类:

核心的对象,创建代理对象。之前创建对象都是new(),现在是使用proxy类的方法,代替new的使用。

方法:静态方法 newProxyInstance()

作用是:创建代理对象,等同于静态代理中的TaoBao taoBao=new TaoBao()

我们来观察方法原型

	/**
     * 
     * @param loader  被代理类的类加载器,不用多说,没这个怎么反射去找被代理类的信息
     * @param interfaces 被代理类实现的接口
     * @param h InvocationHandler的实现类 
     * @return
     * @throws IllegalArgumentException
     */
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException

参数:

  1. ClassLoader loader 类加载器,负责向内存中加载对象的,使用反射机制获取对象的classLoader
  2. Class<?>[] interfaces: 接口,目标对象实现的接口,也是反射获取的
  3. InvocationHandler h : 我们自己写的,代理类要完成的功能

返回值:

代理对象


动态代理案例:

  1. 创建接口,定义目标类要完成的功能
  2. 创建目标类实现接口
  3. 自定义InvocationHandler并重写invoke方法
  4. invoke 方法中调用原生方法(被代理类的方法)并自定义一些处理逻辑
  5. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法创建代理对象并把返回值转换成接口类型

1.创建需要被代理的接口(必须要有)和实现类

public interface service {
    public void reduceStock();
}

public class ServiceImpl implements service {
    //业务方法
    @Override
    public void reduceStock() {
        System.out.println("扣减库存开始");
    }
}


2.创建代理类

需要实现InvocationHandler接口重写invoke方法,这里可以对方法进行增强。

public class Dynamicproxy implements InvocationHandler {
    private Object targetObject;

    public Dynamicproxy(Object targetObject) {
        this.targetObject = targetObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("日志开始");
        Object invoke = method.invoke(targetObject, args);
        System.out.println("日志结束");
        return invoke;
    }
}

3.创建动态代理对象

public class TestApp {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        // 创建代理对象

        // 1. 创建目标对象 --》 实现接口的业务类
        Service service = new ServiceImpl();
        // 2. 创建代理类对象 --》 InvocationHandler对象
        InvocationHandler handler = new Dynamicproxy(service);
        // 3. 创建代理对象
        Service o = (Service) Proxy.newProxyInstance(ServiceImpl.class.getClassLoader(),
                service.getClass().getInterfaces(), handler);
        // 4. 通过代理执行方法
        // o.reduceStock();

        // 使用匿名内部类的写法:
        Service o1 = (Service) Proxy.newProxyInstance(
                service.getClass().getClassLoader(),
                service.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        System.out.println("哈哈哈哈");
                        Object invoke = method.invoke(service, args);
                        System.out.println("嘿嘿嘿");

                        return invoke;
                    }
                });

        o1.reduceStock();

    }

}

4.调用接口方法

o.reduceStock();

输出:

代理模式介绍、实现动态代理(JDK、CGLIB)及对比


Proxy类,实现动态代理的流程,使用返回指定接口的代理类实例

代理模式介绍、实现动态代理(JDK、CGLIB)及对比

我们此时debug一下程序,在invok实现类中打一个断点 此时我们再观察,代理对象MyHandler里面的invoke方法的参数 代理模式介绍、实现动态代理(JDK、CGLIB)及对比

代理模式介绍、实现动态代理(JDK、CGLIB)及对比


② CGLIB 动态代理

使用步骤

  1. 定义一个类;
  2. 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
  3. 通过 Enhancer 类的 create()创建代理类;

案例编写:

不同于 JDK 动态代理不需要额外的依赖。CGLIBopen in new window(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖

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

1.实现一个使用阿里云发送短信的类

public class AliSmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

2.自定义 MethodInterceptor(方法拦截器)

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

import java.lang.reflect.Method;

/**
 * 自定义MethodInterceptor
 */
public class DebugMethodInterceptor implements MethodInterceptor {


    /**
     * @param o           被代理的对象(需要增强的对象)
     * @param method      被拦截的方法(需要增强的方法)
     * @param args        方法入参
     * @param methodProxy 用于调用原始方法
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object object = methodProxy.invokeSuper(o, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return object;
    }

}

3.获取代理类

import net.sf.cglib.proxy.Enhancer;

public class CglibProxyFactory {

    public static Object getProxy(Class<?> clazz) {
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        enhancer.setCallback(new DebugMethodInterceptor());
        // 创建代理类
        return enhancer.create();
    }
}

4.实际使用

AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");

运行上述代码之后,控制台打印出:

before method send
send message:java
after method send

JDK 动态代理和 CGLIB 动态代理对比

  1. JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。
  2. CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法
  3. 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

静态代理和动态代理的对比

  1. 灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
  2. JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的