设计模式之动态代理

335 阅读3分钟

一.引出代理

生活中,常见的代理有中介,各种产品销售门店...等等。比如,“小米工厂”只负责生产手机,把产品交给“小米之家”去销售。这里“小米之家”就是代理,“小米工厂”就是被代理的对象。为什么要用代理呢?第一:如果客户都直接去工厂拿货,那么工厂既要负责生产又要负责销售,太混乱了不容易管理。第二:这对工厂来说也不安全,哪家工厂还没点商业机密呢,哪能轻易让外人进去。第三:对客户而言,如果在购买手机的时候能多来点优惠,或者赠送耳机呀啥的最好了,那么对于手机工厂来说还要去生产耳机嘛,而代理就可以很好的实现这一点。编程思想来源于生活,因此java中也引进了代理这一概念。

二. 代理的好处

  • 职责清晰,如:工厂只负责生产,销售交给其他部门。也符合单一职责的设计思想
  • 可以实现方法增强。这里方法增强的意思是在不改变原方法的情况下,代理可以实现更多的功能。比如工厂卖手机只卖手机,而代理在卖手机的基础上可以加送耳机。
  • 实现对委托方的保护,在代理中可以进行判断是否要去执行委托方的业务逻辑,起到隔离的作用。
  • 低耦合高内聚,扩展性强,不同的代理可以有不同的代理操作。比如小米之家代理卖小米送耳机;淘宝代理卖小米,可以送钢化膜。 讲动态代理前,先简单看看什么是静态代理。

三. 静态代理

  • 静态代理需要程序员自己编写。

静态代理和动态代理的区别可以理解为:静态创建对象和动态创建对象的区别。通过硬编码写死在程序中的叫静态,如Person p = new Person; 通过反射创建的对象叫动态。

举例:


interface Sell{
    //money指的是买手机的钱
    void sellPhone(Double money);
}

class MiFactory implements Sell{
    @Override
    public void sellPhone(Double money) {
        System.out.println("卖手机赚了" + money);
    }
}

//MiHome是MiFactory的静态代理
class MiHome implements Sell{
    //被代理的对象
    private MiFactory factory = new MiFactory();
    @Override
    public void sellPhone(Double money) {
        //隔离,如果金额非法就没必要执行MiFactory的业务逻辑
        if(money < 0){
            return;
        }
        factory.sellPhone();
        //方法增强
        System.out.println("促销送耳机");
    }
}
public class StaticProxyTest{
    public static void main(String[] args) {
        MiHome miHome = new MiHome();
        miHome.sellPhone();
    }
}
  • 静态代理缺点
    • 静态代理只服务一种类型的对象,如果很多不同类型对象都需要代理,那么会程序中会存在很多个代理,显得代码臃肿不易维护。如小米工厂代理,华为工厂代理,苹果工厂代理...
    • 当接口增加了一个方法,那么所有实现类都要重写这个方法,代理类也要重写增加程序员负担
    • 当某个对象的多个方法都需要同一种增强形式时,如事务,代理对象中会有大量重复代码。

三. 动态代理

通过反射创建的代理叫做动态代理,动态代理和委托方的关系是在运行时确定的(代理对象在程序的运行过程中创建);而静态代理和委托方的关系在编译时就确定了。有两种方式实现动态代理。

  • 基于接口的动态代理:jdk
  • 基于子类的动态代理:cglib

3.1 jdk动态代理

通过Proxy.newProxyInstance(...)创建代理。要求委托方至少实现一个接口,否则不能使用jdk动态代理。

  • newProxyInstance()参数说明
    • ClassLoader loader:类加载器,把委托方加载进内存才能通过反射获取其要增强的方法
    • Class<?>[] interfaces:代理类要实现的接口,用于让代理对象和委托方有相同的方法,因此这里一般传入委托方实现的接口。
    • InvocationHandler : 实现业务增强的核心,通常情况下都是匿名内部类。
    代码实现:
public class JdkProxyTest {
    public static void main(String[] args) {
        //factory委托方
        MiFactory factory = new MiFactory();
        //proxy代理对象
        Sell proxy = (Sell) Proxy.newProxyInstance(
        MiFactory.class.getClassLoader(), //通过类加载器把要增强的方法加载进内存
        factory.getClass().getInterfaces(), //获取委托方实现的接口
        new InvocationHandler() {//匿名内部类
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if ("sellPhone".equals(method.getName())) {
                    double money = Double.parseDouble(args[0].toString());
                    ///隔离,如果金额非法就没必要执行MiFactory的业务逻辑
                    if (money < 0) {
                        return null;
                    }
                    //反射获取委托方要增强的方法
                    method.invoke(factory,money);
                     //方法增强
                    System.out.println("促销送耳机");
                }
                return null;
            }
        });
        proxy.sellPhone(2000D);
    }
}

3.2 cglib动态代理

没有实现接口的普通类要使用代理的话,可以通过cglib来实现。刚才使用的基于接口的动态代理是JDK官方支持的,但是现在要讲的基于子类的动态代理需要第三方的支持,因此导入第三方依赖如下:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.1_3</version>
</dependency>

实现:使用Enhancer类中的create方法创建代理。要求委托方不能是最终类,即不能被final修饰(final修饰的类不可被继承)

  • create()参数说明
    • Class type:用于指定委托方的字节码
    • Callback callback:用于提供增强的代码,一般写的都是该接口的子接口实现类MethodInterceptor。 代码实现:
class AppleFactory{
    public void sellPhone(Double money){
        System.out.println("卖苹果手机");
    }
}
public class cglibProxyTest {
    public static void main(String[] args) {
        AppleFactory factory = new AppleFactory();
        AppleFactory proxy = (AppleFactory) Enhancer.create(factory.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                double money = Double.parseDouble(objects[0].toString());
                if (money < 0) {
                    return null;
                }
                method.invoke(factory,money);
                System.out.println("买苹果手机送耳机");
                return null;
            }
        });
        proxy.sellPhone(1000d);
    }
}

动态代理的缺点:硬要说它的缺点的话,应该就是效率了吧。因为动态代理用到了反射,而反射比起直接创建对象效率是更低的,因为反射基本上是解释执行。