1.什么是代理?
代理本身是一种设计模式(Proxy Pattern),它的具体表现形式是,当你想调用类A的方法时,实际上并不直接调用类A,而是调用类A的代理类B,它们具有相同的方法名。换句话说,类B持有类A的对象,并且可以通过同名的方法来实现对类A的访问控制。那么问题就产生了:为什么要用这种看似多此一举,画蛇添足的方式呢?
2.为什么要用代理?
代理模式有它相应的业务场景:
1)解决直接访问对象带来的问题:比如由于安全问题,类A不能直接访问,那么此时就需要一个代理类B来供外部调用,类B提供类A允许的功能函数;
2)调用对象方法时,提供对方法的控制,比如在方法调用前,执行某些动作,即我们通常所谓的AOP。这样可以在不影响原有类的情况下,对方法执行更精确的控制。
3.代理的实现方式
代理的基本逻辑关系如下:
在实际开发中,我们往往需要实现的部分是代理类的实现,和客户端调用,那么代理类的实现方式有如下几种,为了描述方便,我们将Class A称为被代理类,ClassB称为代理类:
1)静态代理:顾名思义,自己写一个固定的代理类即图中的ClassB ,实用性不高,不推荐;
2)java本身的动态代理:即ClassB的生成是在运行时动态生成,这样的灵活性最高,代理类所代理的类可以动态变化(通过构造函数或者getter方法),局限是被代理类必须是接口实现,通过使用java 的代理类生成模板代码(java的一大特色就是代码模板多);
3)Cglib库,解除了被代理类必须是接口实现的限制,也属于动态代理。
4. java的动态代理实现
1)首先要有被代理类和它的接口。
接口如下:
public interface Person {
public void eat();
public void drink();
public void sleep();
public void work();
}
创建一个类Programer(程序员也是人嘛),实现接口Person。
public class Programer implements Person {
@Override
public void eat() {
System.out.println("Programmer eat");
}
@Override
public void drink() {
System.out.println("programer drink");
}
@Override
public void sleep() {
System.out.println("programmer sleep");
}
@Override
public void work() {
System.out.println("coding some code");
}
}
2)被代理类有了,然后创建一个代理句柄类PersonHandler,它要实现java的InvocationHandler,代码如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class PersonHandler implements InvocationHandler {
private Object proxyObject;
public PersonHandler(Object object) {
this.proxyObject = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用方法为:" + method.getName());
method.invoke(proxyObject);
return null;
}
}
PersonHandler的构造函数的参数,就是要被代理的类,我们这里用的是Object类型,目的是兼容各种类型的被代理类,这样设计具有一定的通用性。
下面的invoke方法,就是在生成动态代理对象之后,调用每个方法,最终都要走这个invoke方法,它的参数Method,就是被代理类的方法对象。
3)写一个客户端ProgramerProxy调用动态代理。
import java.lang.reflect.Proxy;
public class ProgramerProxy {
public static void main(String[] args) {
Person programer = new Programer();
PersonHandler personHandler = new PersonHandler(programer);
Person programmerProxy =
(Person)Proxy.newProxyInstance(programer.getClass().getClassLoader(),
programer.getClass().getInterfaces(), personHandler);
programmerProxy.eat();
}
}
其中的关键代码是:
Person programmerProxy =
(Person)Proxy.newProxyInstance(programer.getClass().getClassLoader(),
programer.getClass().getInterfaces(), personHandler);
这句代码,就是通过Proxy生成了一个代理类的实例,指向了Person接口的一个具体实现类programmerProxy;
下来就可以愉快的调用programmerProxy中的方法了,由于它和Programer类都是Person接口的实现类,因此方法名是一样的。
4)总结
我们都干了什么?做了四件事:
- 写了一个接口A
- 写了一个类B实现了接口A;
- 写了一个类C实现了InvocationHandler接口,并且类C持有类B;
- 写了一个类D,通过java的Proxy类,以B,C为参数,创造了一个动态代理类E(也是A的对象),E可以调用类B的所有方法。
那么整个过程中,被代理类是A,代理类是E,其余的对象都是必要的参数和过程中的工具类。
4. 代理类是如何工作的?
programmerProxy在调用eat方法时,实际上调用的是PersonHandler的invoke方法,eat的方法作为Method对象当做了invoke的参数,那么要执行eat,必须在invoke方法中执行method.invoke方法,即反射调用eat方法,被代理对象此时做为invoke参数才真正被代理了。
由此看来,PersonHandler作为中间层对象,实现了对代理类的访问,某种程度上也可以将它看成一个代理类,只不过这个代理类有一定的通用性(可以代理各种类型的对象,而不是某一个类)。
时序图:
5. 扩展:
我们可以看到,所有的代理类必须要经过InvocationHandler的invoke方法,可以对方法进行拦截,加入自己的拦截代码,每个类的调用的公共代码可以在这里进行实现,比如日志打印等等。
InvocationHandler是不是必须一定要有一个被代理的对象呢,未必,我们其实想做的只是拦截方法,不一定要调用method.invoke,我们可以利用这个特性,加入公共方法,比如mybatis就的实现就利用了这个特性。