动态代理相较于静态代理的优势

静态代理

在静态代理中,我们需要创建一个代理类,这个代理类实现被代理类同样的接口,并且代理类中存在一个被代理类接口类型的成员变量。

下面用一个例子来说明:
在这里插入图片描述

代码:

public interface Animal {
    public void eat();
}

public class Cat implements Animal{
    public void eat() {
        System.out.println("a cat is eating...");
    }
}

public class StaticProxyAnimal implements Animal {
    Animal animal;

    public StaticProxyAnimal(Animal animal){
        this.animal = animal;
    }

    public void eat() {
        System.out.println("before eating");
        animal.eat();
        System.out.println("after eating");
    }
}

测试代码:

public class StaticProxyTest {
    public static void main(String[] args) {
        Cat cat = new Cat();
        Animal proxyCat = new StaticProxyAnimal(cat);
        proxyCat.eat();
    }
}

运行结果:

before eating
a cat is eating...
after eating

如果增加一个被代理类Dog,实现的接口仍是Animal,上述的静态代理类StaticProxyAnimal不需要改变也能完成代理,原因是Cat和Dog都可以向上转型为Animal

Dog:

public class Dog implements Animal {
    public void eat() {
        System.out.println("a dog is eating...");
    }
}

测试代码:

public class StaticProxyTest {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Animal proxyDog = new StaticProxyAnimal(dog);
        proxyDog.eat();
    }
}

运行结果:

before eating
a dog is eating...
after eating

但是上面的代理中,对Cat和Dog的增强是完全相同的,如果需要对Cat和Dog进行不同的增强,我们应该如何实现?静态代理仍然可用,我们可以用反射机制来实现。

修改后的代理类:

public class StaticProxyAnimal implements Animal {
    Animal animal;

    public StaticProxyAnimal(Animal animal){
        this.animal = animal;
    }

    public void eat() {
        System.out.println("before " +animal.getClass().getName().substring(7)+" eating");
        animal.eat();
        System.out.println("after " +animal.getClass().getName().substring(7)+" eating");
    }
}

由于我写的实体类所在包名是长度为6的字符串,为了方便,所以采用substring(7),使得打印时只显示类名不显示包名

测试代码:

public class StaticProxyTest {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Animal proxyDog = new StaticProxyAnimal(dog);
        Animal proxyCat = new StaticProxyAnimal(cat);
        proxyDog.eat();
        proxyCat.eat();
    }
}

运行结果:

before Dog eating
a dog is eating...
after Dog eating
before Cat eating
a cat is eating...
after Cat eating

这样看来,动态代理好像没那么必要,但是思考下面两种情形:

  1. 如果存在多个接口的实现类需要被代理,应该如何创建静态代理类?
  2. 当我们修改接口时,还需要修改哪些部分?

问题1

这时我们很容易想到两种方案:

  1. 让代理类实现多个接口
  2. 创建多个代理类

方案1.1

当代理类实现多个接口时,需要实现这些接口的所有方法,这个代理类将变得非常臃肿,也不符合“高内聚,低耦合”,“单一职责”的设计原则

我们用实际例子来展示:

首先,增加一个接口Person及其实现类Student

public interface Person {
    public void speak();
}

public class Student implements Person{
    public void speak() {
        System.out.println("a student is speaking");
    }
}

让代理类实现多个接口

public class StaticProxyInterfaces implements Animal, Person {
    Animal animal;
    Person person;

    public StaticProxyInterfaces(Animal animal) {
        this.animal = animal;
    }

    public StaticProxyInterfaces(Person person) {
        this.person = person;
    }

    public void eat() {
        System.out.println("before " +animal.getClass().getName().substring(7)+" eating");
        animal.eat();
        System.out.println("after " +animal.getClass().getName().substring(7)+" eating");
    }

    public void speak() {
        System.out.println("before " +person.getClass().getName().substring(7)+" speaking");
        person.speak();
        System.out.println("after " +person.getClass().getName().substring(7)+" speaking");
    }
}

我们可以看到,如果此时接口或者接口中的方法增多,代理类将变得十分冗长

下面来看看测试代码以及运行结果

public class StaticProxyTest2 {
    public static void main(String[] args) {
        Cat cat = new Cat();
        Student student = new Student();
        StaticProxyInterfaces proxyCat = new StaticProxyInterfaces(cat);
        StaticProxyInterfaces proxyStudent = new StaticProxyInterfaces(student);
        proxyCat.eat();
        proxyStudent.speak();
    }
}
before Cat eating
a cat is eating...
after Cat eating
before Student speaking
a student is speaking
after Student speaking

这样看起来好像并没有很大的问题,但proxyCat以及proxyStudent代理对象都可以访问互相的方法,这不符合实际。

因此,该方案不可行。

方案1.2

当接口数量增多时,对每一个接口创建一个代理类,将需要创建非常多的代理类,不仅占用内存,而且可维护性差

问题2

当我们增加接口的方法数量时,该接口的所有实现类都需要实现这些方法,所有实体类以及代理类都需要修改代码,可维护性差

动态代理

针对上面两个问题,使用动态代理可以迎刃而解

问题1

我们仅仅需要创建一个代理工厂,即可对各种类型的目标类进行代理

public class ProxyBeanFactory {
    Object target;

    public ProxyBeanFactory(Object target){
        this.target = target;
    }

    public Object getProxyInstance(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("before " +target.getClass().getName().substring(7)+" eating");
                Object returnValue = method.invoke(target, args);
                System.out.println("after " +target.getClass().getName().substring(7)+" eating");
                return returnValue;
            }
        });
    }
}

测试代码:

public class DynamicProxyTest {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Student student = new Student();
        Animal proxyDog = (Animal) new ProxyBeanFactory(dog).getProxyInstance();
        Animal proxyCat = (Animal) new ProxyBeanFactory(cat).getProxyInstance();
        Person proxyStudent = (Person) new ProxyBeanFactory(student).getProxyInstance();
        proxyDog.eat();
        proxyCat.eat();
        proxyStudent.speak();
    }
}

运行结果:

before Dog eating
a dog is eating...
after Dog eating
before Cat eating
a cat is eating...
after Cat eating
before Student eating
a student is speaking
after Student eating

由此可见,动态代理使得代码更加轻便

问题2

在动态代理中,代码中并不存在代理类,而采用拦截方法来增强代码,通过反射机制采用method.invoke()来动态调用方法,所以当接口增加方法时,只需要修改实现类实体的代码,动态代理部分的代码不需要改动,提高了可维护性。

综上,动态代理相较于静态代理有如下优势:

  • 占用空间小,代码轻便
  • 可维护性强
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值