静态代理和动态代理

  |  

在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为 “代理” 的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。

代理模式(Proxy Pattern) :给某一个对象提供一个代理,并由代理对象控制对原对象的引用。

按照代理类的创建时期,代理类可分为两种,即动态代理类和静态代理类。

  • 静态代理类:由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

  • 动态代理类:在程序运行时,运用反射机制动态创建而成。

Java中的静态代理

所谓静态代理,就是代理类是由程序员自己编写的,在编译期就确定好了的。

1
2
3
4
5
6
7
8
9
10
11
public interface HelloSerivice {
public void say();
}

public class HelloSeriviceImpl implements HelloSerivice{

@Override
public void say() {
System.out.println("hello world");
}
}

上面的代码比较简单,定义了一个接口和其实现类。这就是代理模式中的目标对象和目标对象的接口。接下类定义代理对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HelloSeriviceProxy implements HelloSerivice{

private HelloSerivice target;
public HelloSeriviceProxy(HelloSerivice target) {
this.target = target;
}

@Override
public void say() {
System.out.println("记录日志");
target.say();
System.out.println("清理数据");
}
}

上面就是一个代理类,他也实现了目标对象的接口,并且扩展了say方法。下面是一个测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
@Test
public void testProxy(){
//目标对象
HelloSerivice target = new HelloSeriviceImpl();
//代理对象
HelloSeriviceProxy proxy = new HelloSeriviceProxy(target);
proxy.say();
}
}

// 输出为下:
// 记录日志
// hello world
// 清理数据

这就是一个简单的静态的代理模式的实现。代理模式中的所有角色(代理对象、目标对象、目标对象的接口)等都是在编译期就确定好的。

静态代理的用途

  1. 控制真实对象的访问权限 通过代理对象控制对真实对象的使用权限。

  2. 避免创建大对象 通过使用一个代理小对象来代表一个真实的大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。

  3. 增强真实对象的功能 这个比较简单,通过代理可以在调用真实对象的方法的前后增加额外功能。


Java动态代理

Java中,实现动态代理有两种方式:

  1. JDK动态代理java.lang.reflect 包中的Proxy类和InvocationHandler 接口 提供了生成动态代理类的能力。

  2. Cglib动态代理Cglib (Code Generation Library ) 是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的 扩展(类继承)

看 JDK 动态代理的一个简单例子。下面只是加了一句 print,在生产系统中,我们可以轻松扩展类似逻辑进行诊断、限流等。

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 class MyDynamicProxy {
public static void main (String[] args) {
HelloImpl hello = new HelloImpl();
MyInvocationHandler handler = new MyInvocationHandler(hello);
// 构造代码实例
Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), handler);
// 调用代理方法
proxyHello.sayHello();
}
}
interface Hello {
void sayHello();
}
class HelloImpl implements Hello {
@Override
public void sayHello() {
System.out.println("Hello World");
}
}
class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("Invoking sayHello");
Object result = method.invoke(target, args);
return result;
}
}

非常简单地实现了动态代理的构建和代理操作。首先,实现对应的 InvocationHandler;然后,以接口 Hello 为纽带,为被调用目标构建代理对象,进而应用程序就可以使用代理对象间接运行调用目标的逻辑,代理为应用插入额外逻辑(这里是 println)提供了便利的入口。

  • 实现动态代理的方式很多,比如 JDK 自身提供的动态代理,就是主要利用了反射机制。还有其他的实现方式,比如利用字节码操作机制,类似 ASM、CGLIB(基于 ASM)、Javassist 等。

    • 举例,常可采用的 JDK提供的动态代理接口InvocationHandler来实现动态代理类。其中invoke方法是该接口定义必须实现的,它完成对真实方法的调用。通过 InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务。 此外,我们常可以在invoke方法实现中增加自定义的逻辑实现,实现对被代理类的业务逻辑无侵入。

JDK动态代理和Cglib动态代理的区别

  • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用CGLIB实现。

  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。

  • Cglib包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。

Cglib与动态代理最大的区别就是:

  1. 使用动态代理的对象必须实现一个或多个接口

  2. 使用cglib代理的对象则无需实现接口,达到代理类无侵入。

动态代理的用途

  • Java的动态代理的最主要的用途就是应用在各种框架中。

    • 因为使用动态代理可以很方便的运行期生成代理类,通过代理类可以做很多事情,比如AOP,比如过滤器、拦截器等。
  • 在我们平时使用的框架中,像servlet的filter、包括spring提供的aop以及struts2的拦截器都使用了动态代理功能。

    • 我们日常看到的mybatis分页插件,以及日志拦截、事务拦截、权限拦截这些几乎全部由动态代理的身影。

实际使用场景

动态代理应用非常广泛,虽然最初多是因为 RPC 等使用进入我们视线,但是动态代理的使用场景远远不仅如此,它完美符合 Spring AOP 等切面编程。简单来说它可以看作是对 OOP 的一个补充,因为 OOP 对于跨越不同对象或类的分散、纠缠逻辑表现力不够,比如在不同模块的特定阶段做一些事情,类似日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等,参考下图。

AOP 通过(动态)代理机制可以让开发者从这些繁琐事项中抽身出来,大幅度提高了代码的抽象程度和复用度。

参考

Copyright © 2018 - 2020 Kuanger All Rights Reserved.

访客数 : | 访问量 :