代理模式 | 动态代理 | 静态代理

1.什么是代理模式

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

2.代理模式的作用

  • 功能增强: 在原有的功能上,增加了额外的功能,新增加的功能,叫做功能增强,
  • 控制访问:代理类不让你访问目标,例如商家不让用户访问厂家;拦截器就使用了这种思想。

3.代理模式的实现

3.1 静态代理

  • 案例模拟、分析
    用户:客户类
    厂家:目标类
    商家:代理类
    实际逻辑是用户必须从商家购买,不允许用户与厂家直接建立联系(厂家不对用户个人售卖)
    三者的关系: 用户 ----- 商家 ----- 厂家
  • 实现步骤
    (1)创建一个接口,表示商家与厂家都要做的事情
    (2)创建厂家类(目标类),实现接口方法
    (3)创建商家类(代理类),并在代理类中完成目标类的方法调用,同时实现接口方法
    (4)创建客户类,调用商家对象的方法,完成购买
  • Java代码实现
    (1)创建接口,定义sell方法,表示厂家和商家都做的事情
package proxy_pattern.static_proxy;

public interface UsbSell {
    
    

    float sell(int amount);
}

(2)创建目标类,实现接口中的方法

package proxy_pattern.static_proxy;

/**
 * 定义目标类, 实现接口中的方法
 */
public class UsbFactory implements UsbSell{
    
    

    @Override
    public float sell(int amount) {
    
    

        return 30f * amount;
    }
}

(3)创建代理类,也实现接口中的方法,把目标对象作为一个参数,传入代理类的构造方法中,作为代理对象的一个属性;在方法中调用目标对象的方法,同时可以做一些功能增强。

package proxy_pattern.static_proxy;

/**
 * 本质上就是为代理类传入一个目标类对象, 或者直接在代理类中 new 一个目标类对象
 * 
 * 然后在方法中调用目标对象的方法, 同时也可以做一些功能增强 / 控制访问
 */
public class UsbProxy implements UsbSell{
    
    

    //使用构造方法从外面传入一个目标类的对象
    private UsbFactory usbFactory;

    public UsbProxy(UsbFactory usbFactory){
    
    
        this.usbFactory = usbFactory;
    }
    
    //直接在代理类中创建一个目标类对象
//    private UsbFactory usbFactory = new UsbFactory();

    @Override
    public float sell(int amount) {
    
    
        float price = usbFactory.sell(amount);  //调用目标对象的方法

        price += 20;  //使用代理做功能增强

        return price;
    }
}

(4)创建客户类

package proxy_pattern.static_proxy;

/**
 * 1. 创建一个目标对象
 * 2. 创建一个目标对象的代理对象,把目标对象作为一个参数传入
 * 3. 通过调用代理对象的sell方法,完成对目标对象sell方法的调用
 */

public class Test {
    
    
    public static void main(String[] args) {
    
    
        UsbFactory usbFactory = new UsbFactory();  // 1.

        UsbProxy usbProxy = new UsbProxy(usbFactory);  //2.

        System.out.println("使用代理,购买10个: "+usbProxy.sell(10));  // 3.
    }

}
  • 静态代理的缺点
    (1)如果又有一个目标类想委托代理类做代理,两种做法:再为其创建一个代理类或者直接在这个代理类中做修改;而做修改的画,要么更改代理类里的构造方法,把这个目标对象作为一个参数传进去,要么在里面重新new一个目标对象;
    (2)针对给定的一个目标类,如果它有多个方法均需要功能增强,并且在代理类中对其处理也很相似(开启业务,执行目标对象方法,关闭业务),那么这会存在大量的重复代码。
    (3)也就是说使用静态代理,代码是写死在代理类中的,重用性不强!
    在程序运行之前,代理类的 .class文件(字节码文件已经存在)

3.2 动态代理

3.2.1 JDK动态代理

使用Java.reflect包里的类和接口实现动态代理,具体使用到了InvocationHandler、Method、Proxy,具体创建接口,创建目标类与前面的静态代理一样,不同的是如何创建代理对象。

  • Java实现
    (1)创建接口,定义方法
    (2)创建目标类,实现接口方法
    (3)定义一个Handler类,实现InvocationHandler接口,并实现其中的invoke()方法(完成目标对象的回调以及实现功能增强)
package proxy_pattern.dynamic_proxy;

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

/**
 * 在 invoke()方法中需要做的:
 * 1. 完成目标对象方法的回调
 * 2. 完成功能增强(如果需要的话)
 */
public class SellHandler implements InvocationHandler {
    
    

    private Object target;

    public SellHandler(Object target){
    
       //创建 handler对象的时候, 需要把目标对象(被代理对象)作为参数传进来
        this.target =target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
     
        // 参数1: 代理对象本身
        // 参数2: 被代理的方法对象
        // 参数3: 被代理方法应该传入的参数

        Object res = method.invoke(target, args);  // 回调目标对象的方法, 使用res保存
        if(res != null){
    
    
            Float price = (Float) res;
            price += 20;

            res = price;
        }
        return res;
    }
}

(4)在测试类中调用Proxy的静态方法,创建代理对象,并调用代理类的方法
这里其中三个参数
第一个:ClassLoader loader类加载器,是我们目标接口(target)实现类的类加载器
通过 target.getClass().getClassLoader() 获取
第二个:Class<?>[] interfaces为我们的目标接口
通过target.getClass().getInterfaces() 获取
第三个:是一个InvocationHandler 类型的对象,这只是一个接口,我们需要实现这个接口,实现接口中的invoke()方法

package proxy_pattern.dynamic_proxy;

import java.lang.reflect.Proxy;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        UsbFactory usbFactory = new UsbFactory();

        SellHandler handler = new SellHandler(usbFactory);  //把目标对象传入

        UsbSell usbSell = (UsbSell) Proxy.newProxyInstance(usbFactory.getClass().getClassLoader(),
                                    usbFactory.getClass().getInterfaces(), handler);

        System.out.println("使用动态代理,购买10个:"+ usbSell.sell(10));
    }
}

使用Proxy类生成代理对象的过程:
在这里插入图片描述
注意:
1.在JDK动态代理中,要求目标类实现接口,如果目标类没有实现接口,那么在上面的target.getClass().getInterfaces()中,将 获取不到任何内容

2.JDK动态代理,是在程序运行过程中创建代理对象的,这与静态代理先生成代理类的.class文件,再使用代理类对象完全不同!

3.2.2 CGLib动态代理

 使用JDK的Proxy创建代理对象,要求目标类和代理类实现相同的接口。若目标类不存在接口,则无法使用该方式实现,那么也就不能使用JDK动态代理。这个时候,可以使用CGLib动态代理,但它需要引入对应的jar包。
 CGLib代理的生成原理是生成目标类的子类,而子类是通过增强过的,这个子类对象就是代理对象,所以,使用cglib生成动态代理,要求目标类必须能够被继承,即不能是final的类。

4.总结

1、静态代理需要手动写代理类,并且在代理类中传入对应的目标类对象( 直接在代理类中new 或者使用构造方法传入);如果有多个目标类,需要在代理类中传入对应的目标对象;或者直接为每个目标类创建一个代理类。代码重用性极差!
2、JDK动态代理是JDK里自带的, 要求目标类实现接口,对于没有实现任何接口的目标类,不能使用JDK动态代理。
3、CGLib动态代理,它是在内存中构建一个子类对象,从而实现对目标对象功能的扩展;是基于继承来实现代理,所以 无法对final类、private方法和static方法进行代理;需要引入第三方的jar包。

猜你喜欢

转载自blog.csdn.net/Dartao/article/details/127984752