静态代理: 就是自己编写一个代理类来代理一个具体的类,使使用这个类的客户端不需要知道实现类是什么,怎么做的,而客户端只需知道和使用代理即可,也就是把客户端和具体类进行了解耦合。这个关系可以用明星和经纪人来类比。
编写一个接口明星:
package staticProxy;
public interface Superstar {
public void showUp();
public void getMoney();
}
public void showUp();
public void getMoney();
}
编写一个类实现接口明星:
package staticProxy;
public class GaoYuanYuan implements Superstar{
@Override
public void showUp() {
// TODO Auto-generated method stub
System.out.println("高圆圆开始化妆!");
System.out.println("高圆圆准备出场!");
}
@Override
public void getMoney() {
// TODO Auto-generated method stub
System.out.println("高圆圆获得出场费1000k");
}
}
编写静态代理类来代理明星高圆圆,就像她的经纪人一样:
package staticProxy;
public class YuanYuanProxy implements Superstar{
private Superstar yuanYuanProxy;
public YuanYuanProxy(){
yuanYuanProxy = new GaoYuanYuan();
}
@Override
public void showUp() {
// TODO Auto-generated method stub
System.out.println("经纪人在提醒高圆圆准备出场");
yuanYuanProxy.showUp();
}
@Override
public void getMoney() {
// TODO Auto-generated method stub
System.out.println("经纪人在谈出场费");
yuanYuanProxy.getMoney();
}
}
编写main方法测试静态代理:
package staticProxy;
public class StaticProxyTest {
public static void main(String[] args) {
Superstar yuanSuperstar = new YuanYuanProxy();
yuanSuperstar.showUp();
yuanSuperstar.getMoney();
}
}
输出结果:
经纪人在提醒高圆圆准备出场
高圆圆开始化妆!
高圆圆准备出场!
经纪人在谈出场费
高圆圆获得出场费1000k
可以看到,我们使用经纪人类 GaoYuanYuanProxy实现了 Superstar接口(和GaoYuanYuan实现相同接口),并在构造方法中 new 出一个GaoYuanYuan类的实例。这样一来,我们就可以在GaoYuanYuanProxy 的 showUp()和getMoney() 方法里面去调用GaoYuanYuan的showUp()和getMoney() 方法了。有意思的是,我们在调用逻辑部分( main() 方法),依然持有的是Superstar接口类型的引用,调用的也依然是showUp()和getMoney() 方法,只是实例化对象的过程改变了,结果来看,代理类却自动为我们加上了经纪人的动作。
传统的方式是客户端直接调用GaoYuanYuan这个类来使用,如果想改变高圆圆的出场费,必须要在具体的类GaoYuanYuan中进行修改,这样使得客户端和具体类耦合度变高。通过静态代理的方式,可以直接在代理类GaoYuanYuanProxy中进行修改,使得客户端和具体类的耦合度降低。
虽然静态代理满足了解耦合的需要,但是也有一些缺点。第一,代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。第二,代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为UserManager类的访问提供了代理,但是如果还要为其他类如Department类提供代理的话,就需要我们再次添加代理Department的代理类。
而且静态代理是由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。
要解决静态代理的不足,出现了动态代理。
superstar和gaoyuanyuan类代码不变,将静态代理类改成动态代理类。
package dynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy implements InvocationHandler{
private Object targetObject;
public Object newProxyInstance(Object targetObject){
this.targetObject = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("开始代理");
Object result = null;
result = method.invoke(targetObject, args);
System.out.println("结束代理");
return null;
}
}
编写main方法测试
package dynamicProxy;
public class DynamicProxyTest {
public static void main(String[] args) {
DynamicProxy dynamicProxy = new DynamicProxy();
Superstar gaoYuanYuan = (Superstar) dynamicProxy.newProxyInstance(new GaoYuanYuan());
gaoYuanYuan.showUp();
gaoYuanYuan.getMoney();
}
}
输出结果如下:
开始代理
高圆圆开始化妆!
高圆圆准备出场!
结束代理
开始代理
高圆圆获得出场费1000k
结束代理
被代理对象targetObject通过参数传递进来,我们通过targetObject.getClass().getClassLoader()获取ClassLoader对象,然后通过targetObject.getClass().getInterfaces()获取它实现的所有接口,然后将targetObject包装到实现了InvocationHandler接口的DynamicProxy对象中。通过newProxyInstance函数我们就获得了一个动态代理对象。
可以看到,我们可以通过DynamicProxy代理不同类型的对象,如果我们把对外的接口都通过动态代理来实现,那么所有的函数调用最终都会经过invoke函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等。这也就是AOP(面向切面编程)的基本原理。
动态代理类dynamicproxy中的代码几乎都是通用的,对象只有在main方法中传递gaoyuanyuan时才会生成,也就是说只有在程序运行起来后对象才会动态生成。而且不光可以生成superstar这一种类型,也可以生成其他类型的类的实例,提高了代码重用性和解耦合。
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强。并且动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。