Proxy模式概述及如何实现动态代理

前一阵子不是用AOP的时候碰见了一些问题嘛, 由于AOP是基于动态代理实现的,所以今天就抽时间看了一下~

代理(proxy)模式算是很经常用到的一种模式了。就像日常生活一样,自己做不到的一些事情可以找别人帮忙完成一下~比如上不去分了找代练、不想出去吃饭叫外卖 等等..

代理 说简单点就是四个字找人帮忙。在编码过程中具体体现为 通过低侵入的方式来实现一些功能

代理模式主要分为两种实现. 按照职责划分的话。可以大致分作8类:

  • 远程代理
  • 虚拟代理
  • Copy-on-Write 代理
  • 保护代理
  • Cache代理
  • 防火墙代理
  • 同步化代理
  • 智能引用代理

静态代理

通过硬编码实现。 需要代理的对象在程序运行前就是已知的。换句话说,就是写死了的代理~

比如说作为新生代优秀大学生, 部分人喜欢通过 智行 抢火车票而不是 12306, 但是我们知道网上的放票途径只有一种: 12306。 那么智行是怎么抢着票的呢? 它其实是拿着你的信息去找12306要票啦~(瞎猜的,不可确信)也就是说, 在这里智行扮演了个代理者的形象。我们可以直接到 12306 抢票,但是由于智行在抢票的基础上提供了更便捷的功能,所以也有不少人用智行。

这一过程可以用以下代码描述出来

示例

1
2
3
4
5
6
7
8
9
10
11
12
 /**
* 抢票
* @author bestsort
* @version 1.0
* @date 1/3/20 12:47 PM
*/
public interface RobTicket {
/**
*开始抢票
*/
public void startRob();
}
复制
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 到官网( 12306 )去抢票
* @author bestsort
* @version 1.0
* @date 1/3/20 12:52 PM
*/
public class _12306 implements RobTicket{
@Override
public void startRob() {
System.out.println("进入12306抢票");
}
}
复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

/**
* 用智行抢票
* @author bestsort
* @version 1.0
* @date 1/3/20 12:53 PM
*/
public class ZhiXing implements RobTicket{
private _12306 officialWebsite = new _12306();
@Override
public void startRob() {
before();
System.out.println("智行正在帮忙抢票~");
officialWebsite.startRob();
end();
System.out.println("抢票结束----");
}
private void before(){
System.out.println("导入身份信息");
}
private void end(){
System.out.println("抢票成功, 开始自动帮忙订酒店~");
}
}
复制

从上面的代码可以看到, 智行持有了 12306 这个对象, 当有人用智行的时候, 智行可以QWER一通乱秀后再去抢票, 同时惹出事后还能帮忙擦个屁股~ 上面的例子有点类似 AOP 中的 环绕增强(Around)。 所以,当我们想在访问一个类时做一些控制或者其他花里胡哨的操作的时候可以试一试这个模式。

动态代理

上面的代码功能确实不错,但是写起来是真的累死个人…一次只能代理一个类。要是需要代理的类一多,来个超级加倍怕不怕? 更别说静态代理这个东西。写的太多了看起来确实不好看…

要知道

是人类的本质

是人类的本质

是人类的本质

(复读也是~)。

动态代理就因此而生了。当然,它和静态代理还是有一些不同的地方的。 动态代理是通过反射创建,也就是说它是动态生成的。动态代理一般通过两种方式实现: JDK代理CGLIB代理 两者底层实现不同, 我会尽力描述一下两者的特点和使用形式。

P.s: 在Spring 中的组件如果没有实现接口会使用 CGLIB代理,而对于已经实现了接口的组件会使用 JDK代理,可以Debug看一下地址,CGLIB代理的类会有CGLIB-xxxxxx的标识.

JDK 代理

JDK代理 是基于反射拦截器实现的,在我们代理一个类时,要确保满足以下条件:

  1. 这个类必须实现了某一接口
  2. 代理时需要实现InvocationHandler接口
  3. 需要用Proxy.newProxyInstance 生成代理对象

JDK代理主要依赖于 java.lang.reflect包 中的 ProxyInvocationHandler 这两个类。 依旧以抢火车票为例, 看一下是咋实现的:

上面的RobTicket接口和_12306实现不用变, 新增一个RobTicketInvocationHandler类,这个类实现了InvocationHandler方法,这个类现在的作用就相当于上面的ZhiXing,帮忙抢票的~

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
* @author bestsort
* @version 1.0
* @date 1/7/20 10:00 AM
*/
public class RobTicketInvocationHandler implements InvocationHandler {
private Object object;
public RobTicketInvocationHandler(Object o){ object = o; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(object, args);
end();
return result;
}
private void before(){
System.out.println("导入身份信息");
}
private void end(){
System.out.println("抢票成功, 开始付款");
}
}
复制

然后Main方法也需要变动一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

import cn.bestsort.code.proxy.jdk.RobTicketInvocationHandler;
import java.lang.reflect.Proxy;
/**
* @author bestsort
* @version 1.0
* @date 1/3/20 12:44 PM
*/
public class Main {
public static void main(String[] args) {
try {
RobTicket robTicket = (RobTicket) Proxy.newProxyInstance(RobTicket.class.getClassLoader(),
new Class[]{RobTicket.class},
new RobTicketInvocationHandler(new _12306()));
robTicket.startRob();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
复制

执行结果如下:

1
2
3
导入身份信息
进入官网抢票
抢票成功, 开始付款
复制

可以看到已经成功进行代理了。

CGLIB代理

cglib is a powerful, high performance and quality Code Generation Library, It is used to extend JAVA classes and implements interfaces at runtime.

上述是CGLIB 对自己的一个描述。CGLIB是一个很强大的代码生成类库,可以在运行期间扩展Java类与实现Java接口。在 Spring 中如果要对某一组组件使用 AOP 进行切入话,如果组件实现了接口,则会通过 JDK 进行代理,否则就会通过 CGLIB 进行代理。 也就是说,使用CGLIB代理的类可以不实现接口

下面还是通过代码来看一下怎么实现的

使用 CGLIB 需要引入依赖, 这里通过 maven 导入cglib3.2.5

1
2
3
4
5
6
7
8
<dependencies>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
</dependencies>
复制

_12306

1
2
3
4
5
public class _12306{
public void startRob() {
System.out.println("进入官网抢票");
}
}
复制

RobTicketMethodInterceptor

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
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* @author bestsort
* @version 1.0
* @date 1/7/20 4:12 PM
*/
public class RobTicketMethodInterceptor implements MethodInterceptor {
/**
* @param o 代理对象(由CGLIB生成)
* @param method 要执行的方法
* @param objects 参数列表
* @param methodProxy 代理方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object object = methodProxy.invokeSuper(o, objects);
end();
return object;
}
private void before(){
System.out.println("导入身份信息");
}
private void end(){
System.out.println("抢票成功, 开始付款");
}
}
复制

Main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import cn.bestsort.code.proxy.cglib.RobTicketMethodInterceptor;
import net.sf.cglib.proxy.Enhancer;
/**
* @author bestsort
* @version 1.0
* @date 1/3/20 12:44 PM
*/
public class Main {
public static void main(String[] args) {
//new 一个 Enhancer 对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(_12306.class);
//设置回调对象
enhancer.setCallback(new RobTicketMethodInterceptor());
_12306 proxy = (_12306)enhancer.create();
proxy.startRob();
}
}
复制

执行结果如下:

1
2
3
导入身份信息
进入官网抢票
抢票成功, 开始付款
复制

后记

我们在这里只说明了怎样进行静态代理和动态代理, 至于代理的具体步骤和源码分析, 本来想写的,后来发现篇幅过长。以后会专门写两篇文章尝试分析一下 JDK代理 和 CGLIB代理 的源码的

觉得文章不错的话可以请我喝一杯茶哟~
  • 本文作者: bestsort
  • 本文链接: https://bestsort.cn/2020/01/03/107/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-SA 许可协议。转载请注明出处!并保留本声明。感谢您的阅读和支持!
-------------本文结束感谢您的阅读-------------