一.代理模式
代理模式是一种常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途,为目标对象提供额外的访问方式,在不修改目标对象的前提下,扩展目标对象的额外功能。
用张图来形象的表示一下:
代理模式分为两种:静态代理和动态代理。
二.静态代理
由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
1.实例实现
a.创建接口类:
public interface Person {
void submitMoney();
}
b.创建实现类:
public class Student implements Person {
@Override
public void submitMoney() {
Log.e("Seven", "-------submit money!");
}
}
c.创建代理类:
/**
* 代理类,需要实现Person接口
*/
public class StudentProxy implements Person {
Student stu;
public StudentProxy(Person st) {
stu = (Student) st;
}
@Override
public void submitMoney() {
//通过代理类,可以在调用被代理类方法前执行自己的逻辑
//addSelfMethod();
Log.e("Seven", "------this student grows up");
stu.submitMoney();
}
}
d.客户端使用:
//被代理的对象学生tom
Person tom = new Student();
//代理对象,并将tom传给代理对象
Person monitor = new StudentProxy(tom);
//代理对象执行操作
monitor.submitMoney();
运行结果:
1-20 16:08:21.671 E/Seven (23703): ------this student grows up
1-20 16:08:21.671 E/Seven (23703): -------submit money!
客户端在使用时没有直接通过tom(被代理对象)来执行操作,而是通过Monitor(代理对象)来代理执行了。
代理模式最主要的就是有一个公共接口(Person),一个具体的实现类(Student),一个代理类(StudentProxy),代理类持有具体实现类的实例,代为执行具体类实例方法。
上面提到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。比如上述中addSelfMethod()或输出日志。
2.静态代理优缺点:
优点:
1、业务类student只需要关注业务逻辑本身,保证了业务类的重用性。
2、客户端Client和业务类student之间没有直接依赖关系,对客户的而言屏蔽了具体实现。
缺点:
1、代理对象的一个接口只服务于一种接口类型的对象,静态代理在程序规模稍大时就无法使用。
2、如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
三.动态代理
代理类在程序运行时创建的代理方式被称为动态代理。 上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
1.实例实现
a.创建接口类:
public interface Person1 {
void submitMoney();
}
b.创建实现类:
public class Student1 implements Person1 {
@Override
public void submitMoney() {
Log.e("Seven", "-------submit money!");
}
}
c.创建StuInvocationHandler类实现InvocationHandler接口
创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法,在invoke方法中执行被代理对象target的相应方法。
public class StuInvocationHandler<T> implements InvocationHandler {
//被代理对象实例
T target;
public StuInvocationHandler(T t) {
target = t;
}
/**
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用目标方法时传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.e("Seven", "proxy execute " + method.getName() + " method start");
//通过反射调用目标类的方法
Object result = method.invoke(target, args);
Log.e("Seven", "proxy execute " + method.getName() + " method end");
return result;
}
}
d.客户端使用:
//创建实例对象,该对象就是被代理的对象
Person1 jack = new Student1();
//创建一个与代理对象相关联的InvocationHandler
StuInvocationHandler stuInvocationHandler = new StuInvocationHandler<>(jack);
//创建一个代理对象stuProxy来代理jack,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person1 stuProxy = (Person1) Proxy.newProxyInstance(Person1.class.getClassLoader(),
new Class<?>[]{Person1.class}, stuInvocationHandler);
//代理对象执行操作
stuProxy.submitMoney();
运行结果:
1-20 16:08:29.345 E/Seven (23703): proxy execute submitMoney method start
1-20 16:08:29.345 E/Seven (23703): -------submit money!
1-20 16:08:29.345 E/Seven (23703): proxy execute submitMoney method end
动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。上例中,所有的被代理对象执行的方法都会打印日志,然而只是在invoke()做了很少的代码量,没有在所有的方法中修改代码。
动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体实现,而且动态代理中被代理对象和代理对象是通过InvocationHandler来完成的代理。下面来看一下具体实现。
四.动态代理分析
a.创建动态代理类
上例中使用Proxy.newProxyInstance(Person1.class.getClassLoader(),new Class<?>[]{Person1.class}, stuInvocationHandler)来创建了动态代理实例,看一下具体实现:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException {
Objects.requireNonNull(h);
//step 1 对class 进行clone
final Class<?>[] intfs = interfaces.clone();
......
// step 2 获取到代理类
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
......
// step 3 获取到代理类的构造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
......
// step 4 通过构造方法创建代理类实例,传入已经创建的InvocationHandler对象
return cons.newInstance(new Object[]{h});
}
......
}
b.代理类分析
从step 2可以看到生成了代理类[可以通过generateProxyClass("$Proxy0", Student.class.getInterfaces())获取到代理类的class文件],
public final class $Proxy0 extends Proxy implements Person1
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
//生成代理类的构造方法,方法参数为InvocationHandler类型
//该参数是在创建实例时step 4 传入的已经创建的StuInvocationHandler
//代理对象持有一个InvocationHandler对象
public $Proxy0(InvocationHandler paramInvocationHandler) throws {
//调用父类Proxy的构造方法
super(paramInvocationHandler);
}
......
public final void submitMoney() throws {
try {
//直接调用InvocationHandler中的invoke方法,并把m3传了进去,invoke()调用的就是m3对应的方法
//InvocationHandler对象持有一个被代理对象,最终会调用被代理对象的方法
super.h.invoke(this, m3, null);
return;
} catch (Error|RuntimeException localError) {
throw localError;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
.......
static
{
try
{
//submitMoney()通过反射得到的名字m3
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("dynamicProxy.Person1").getMethod("submitMoney", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
.....
}
}
通过对这个生成的代理类源码的查看,可以看出:
Proxy0继承了Proxy,实现了自定义的目标接口Person1;
Proxy0定义了接口Person1中的方法submitMoney(),以及Object对象的几个方法equals()、toString()、hashCode();
构造方法中传了InvocationHandler对象,并且在定义的方法中调用了它的invoke()方法;
Proxy0继承了Proxy,因此也就决定了java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。
关键点 | 具体流程 |
---|---|
创建InvocationHandler接口实现类,并传入目标类对象 | 实现InvocationHandler类,实现invoke()方法 |
Proxy.newProxyInstance()创建代理类,并传入InvocationHandler接口实例 | 1.通过为Prxoy类指定类加载器和一组接口,从而创建动态代理类的字节码,根据字节码创建动态代理类; 2.通过反射机制获取到动态代理类的构造方法; 3.通过构造方法创建代理类实例(传入InvocationHandler接口实例) ; |
通过调用代理对象方法来调用目标对象方法 | 1.动态代理类实现了与目标类一样的接口,并实现了需要目标类对象需要调用的方法; 2.调用方法时,相当于调用父类Proxy的h.invoke()方法;h是在创建代理类时传入的InvocationHandler接口实例; 3.在invoke对象中通过反射机制调用目标类对象的方法; |
动态代理实现的具体过程:
可以把InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。
本文参考了java动态代理实现与原理详细分析
相关实例请看Retrofit2原理分析