静态代理
在静态代理中,我们需要创建一个代理类,这个代理类实现被代理类同样的接口,并且代理类中存在一个被代理类接口类型的成员变量。
下面用一个例子来说明:
代码:
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
这时我们很容易想到两种方案:
- 让代理类实现多个接口
- 创建多个代理类
方案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()来动态调用方法,所以当接口增加方法时,只需要修改实现类实体的代码,动态代理部分的代码不需要改动,提高了可维护性。
综上,动态代理相较于静态代理有如下优势:
- 占用空间小,代码轻便
- 可维护性强