0%

设计模式(3)——代理模式

之前我们说过反射机制,今天我们聊一聊反射机制相关的一个设计模式————代理模式,代理模式中的动态代理就是应用了我们反射来动态生成代理对象完成方法调用。

一. 代理模式概念

代理模式是给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。

当我们想对一个对象增加一些附加的功能,例如权限的验证、增加日志功能,同时我们又不想破坏对象本身的结构,我们就可以创建一个和这个对象功能相同的代理对象,通过对代理对象的方法调用,间接调用被代理的对象,还可以在方法调用的前后增加一些附加的功能,以达到增强对象的目的。

代理模式一共有三种,静态代理模式、动态代理模式、gclib代理模式。

二. 静态代理模式

upload successful

如图,我们在有一个ITeacherDao接口,然后我们又创建一个类TeacherDao实现了ITeacherDao接口,当我们在使用这个类的时候,我们希望对类中实现的teache方法做一些增强。这里我们创建TeacherProxy代理类同样实现了ITeacherDao接口,并通过构造函数,传入接口参数来间接对TeacherDao形成依赖关系。代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//接口类
public interface ITeacherDao {
String teach(String name);
}


//实现方法
public class TeacherDao implements ITeacherDao{
@Override
public String teach(String name) {
System.out.println("老师教学生" + name);
return name;
}
}

//代理类
public class TeacherProxy implements ITeacherDao{
private ITeacherDao target;

//利用接口来代理目标乐居
public TeacherProxy(ITeacherDao target) {
this.target = target;
}

@Override
public String teach(String name) {
System.out.println("代理开始");
String stu = target.teach(name);
System.out.println("代理结束");
return stu;
}
}

我们向TeacherProxy传入ITeacherDao对象target,并通过构造函数传入被代理对象,在实现teach方法的过程中,通过调用被代理类的teach方法达到方法增强的效果。在实现过程中我们用到了泛型操作,这使得我们可以创建新的ITeacherDao实现类,而无需修改代理类的代码。

静态代理的优点:

  • 易于理解和实现
  • 代理类和真实类的关系是编译期静态决定的,和下文马上要介绍的动态代理比较起来,执行时没有任何额外开销。

静态代理的缺点:
对于每一个接口都需要一个创建新的代理类,当一个工程创建的接口增多时,我们需要创建和维护的代理类也会增多,这无疑是增加工程的代码量和复杂度,不易于管理和维护。

三. 动态代理模式

动态代理模式也叫做JDK代理模式,它是通过调用JDK的Proxy类newProxyInstance()来实现对象代理,他会使代理类在运行时动态的生成。
JDK中生成代理对象的API:

代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:

1
static Object newProxyInstance(ClassLoader loader, Class [] interfaces, InvocationHandler handler)

这是一个静态方法,且接收的三个参数依次为:

  • ClassLoader loader: 指定当前被委托对象使用类加载器,用null表示默认类加载
  • Class [] interfaces: 指定被委托对象实现的接口。
  • InvocationHandler handler: 调用处理器,执行目标对象的方法时,会触发调用处理器的方法,从而把当前执行目标对象的方法作为参数传入

java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

1
2
3
4
5
// 该方法负责集中处理动态代理类上的所有方法调用。
//第一个参数既是代理类实例,
//第二个参数是被调用的方法对象
// 第三个参数是调用方法的参数。
Object invoke(Object proxy, Method method, Object[] args)

当我们在调用代理类的方法的时候,代理类会转而进入到InvocationHandler的invoke方法中从而操作被代理对象调用相应的方法。这样我们就可以对被代理对象进行统一的处理,也可以更具参数的不同对被代理对象进行风别处理。

我们在使用JDK动态代理时,通过创建一个代理工厂生成代理对象,并动态执行被代理对象的方法调用,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ProxyFactory{
Object target;

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

public Object getNewInstance(){
Object o = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理开始");
Object ObjectReturn = method.invoke(target, args);
System.out.println("代理结束");
return ObjectReturn;
}
});
return o;
}


}

我们点开newProxyInstance源码可以看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);

final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}

/*
* Look up or generate the designated proxy class.
*/

//生成代理类
Class<?> cl = getProxyClass0(loader, intfs);

/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}

//利用代理类生构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//利用构造器在生成实例
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}

我们可以看到newProxyInstance现为我们生成了一个代理类,再利用代理类生成一个构造器,最后用构造器生成代理对象。这里我们看到构造器传入了constructorParams,我们追踪一下源码。

1
2
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };

发现constructorParams是一个包含了InvocationHandler.class的数组,其实我们最终生成代理类会包含一个InvocationHandler对象h,当我们调用代理对象的方法的时候,实际上调用调用的是h的invoke()方法进而调用实际的方法。我们接下来用idea的反编译看一下生成的class类文件,反编译的操作是在VM options中添加:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.sun.proxy;

import com.mjj.proxy.JDKproxy.ITeacherDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements ITeacherDao {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public $Proxy0(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final String teacher(String var1) throws {
try {
return (String)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.mjj.proxy.JDKproxy.ITeacherDao").getMethod("teacher", Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

这里我们可以清晰的看到代理类在初始化的时候通过反射加载方法,在调用方法的时候实际上是调用我们传入的InvocationHandler的方法。本质上代理类和被代理类都实现了相同的接口,拥有相似的结构,而在方法的调用上都是通过反射实现对代理类的调用。

我们维护的动态代理类生成器ProxyFactory无需实现接口,具有可维护、易扩展的特点,具体的源代码可以参考这个链接(JDK动态代理实现原理(jdk8))但是JDK动态代理有一个无法避免的缺点就是,被代理类必须实现接口。若我们想代理一个没有实现接口的类,我们就不能使用这种方法,而只能使用我们下面介绍的cglib代理。

三. cglib代理模式

cglib代理模式不需要被代理的对象实现接口,其底层是通过ASM字节码框架生成类的字节码,达到动态创建类的目的。它利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。下面我借用一下别人的uml类图来说明一下:

upload successful
cglib通过先创建了代理类并实例化proxy,proxy通过继承关系继承被代理类,然后通过对父类方法的重写以达到业务增强的效果。
实例代码如下:

创建目标类:Target:方法简单输出一句话

1
2
3
4
5
public class Target {
public void request() {
System.out.println("执行目标类的方法");
}
}

创建目标类的方法增强拦截器:TargetMethodInterceptor:在拦截器内部,调用目标方法前进行前置和后置增强处理。

1
2
3
4
5
6
7
8
9
10
public class TargetMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("方法拦截增强逻辑-前置处理执行");
Object result = proxy.invokeSuper(obj, args);
System.out.println("方法拦截增强逻辑-后置处理执行");
return result;
}
}

生成代理类,并测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CglibDynamicProxyTest {

public static void main(String[] args) {
Enhancer enhancer = new Enhancer();

// 设置生成代理类的父类class对象
enhancer.setSuperclass(Target.class);

// 设置增强目标类的方法拦截器
MethodInterceptor methodInterceptor = new TargetMethodInterceptor();
enhancer.setCallback(methodInterceptor);

// 生成代理类并实例化
Target proxy = (Target) enhancer.create();

// 用代理类调用方法
proxy.request();
}
}

测试输出:可以看到成功进行了业务增强的处理。

1
2
3
方法拦截增强逻辑-前置处理执行
执行目标类的方法
方法拦截增强逻辑-后置处理执行

四. 动态代理和cglib对比

  • 实现方式不同
    动态代理是利用相同接口实现和被代理类相同结构代理类,再利用反射机制实现方法代理,而cglib是利用asm框架操作字节码文件生层代理类,再利用继承关系实现代理。
  • 使用方式不同
    动态代理要求代理对象必须实现接口,而cglib虽然不需要被代理对象实现接口,但是由于使用了继承关系,所以cglib无法代理final对象。
  • 效率对比
    关于两者之间的性能的话,网上有人对于不通版本的jdk进行测试,经过多次试验,测试结果大致是这样的,在1.6和1.7的时候,JDK动态代理的速度要比CGLib动态代理的速度要慢,但是并没有教科书上的10倍差距,在JDK1.8的时候,JDK动态代理的速度已经比CGLib动态代理的速度快很多了,但是JDK动态代理和CGLIB动态代理的适用场景还是不一样的哈!

五. 参考文献

Java的三种代理模式
Java代理设计模式的四种具体实现:静态代理和动态代理
Java 动态代理作用是什么?
Java:聊聊JDK和CGLib动态代理实现和区别
设计模式(11)动态代理 JDK VS CGLIB面试必问