代理模式对于我们来说并不难理解,日常生活中我们经常能看到代理模式的影子。我们平时使用「智行」、「同程」等买票的时候,就是一个典型代理模式的应用。

1
我们(使用者) ----- 智行/同程(代理者) ----> 12306(被代理者)

我们不直接通过12306买票,因为这样抢票并不方便,我们通过「智行」/「同程」等第三方专业机构来帮我们买票,这些机构建来代替我们去12306买票,同时这些机构代代理买票的过程中会增强一系列功能,如:增值服务、用户消费记录等。

Java中也实现了代理模式,本文我们来讨论一下「代理模式」。

代理模式

1、概述

代理模式使用代理对象来代替真实对象的访问,可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。

Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。

2、静态代理

静态代理中,对目标的每个方法增强都是通过手动完成。实际应用场景很少,日常开发几乎用不到。

【静态代理的实现步骤:】

  1. 定义一个接口及其实现类;
  2. 创建一个代理类实现这个类;
  3. 将目标对象放到代理类中,任何代理类的对应方法调用目标类的对应方法,主要可以通过代理类屏蔽对目标对象的访问。

场景:买火车票我们不通过12306买票,而是使用「智行」、「同程」等代理购买,这些软件可以提供更加智能的路线选择,同时也提供了一些增值的服务(增强)

卖票接口

1
2
3
public interface SellTicket {
String sell(Integer money);
}

12306网站

1
2
3
4
5
6
7
public class SellTicketImpl implements SellTicket {
@Override
public String sell(Integer money) {
System.out.println("花费" + money + "元购票.");
return "购票成功";
}
}

智行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ZhixingProxy implements SellTicket {
private SellTicket sellTicket;
public ZhixingProxy(SellTicket sellTicket) {
this.sellTicket = sellTicket;
}
@Override
public String sell(Integer money) {
money -= 10; // 收取手续费
System.out.println("[INFO]:智行购票中...");
String ret = sellTicket.sell(money);// 购票
System.out.println("[INFO]:智行购票成功...");
return "智行:" + ret;
}
}

使用者

1
2
3
4
5
6
7
8
public class ProxyDemo {
public static void main(String[] args) {
SellTicketImpl sellTicket = new SellTicketImpl();
ZhixingProxy zhixingProxy = new ZhixingProxy(sellTicket);
String ret = zhixingProxy.sell(100);
System.out.println(ret);
}
}

输出

1
2
3
4
[INFO]:智行购票中...
花费90元购票.
[INFO]:智行购票成功...
智行:购票成功

3、动态代理

与静态代理相比,动态代理更加灵活,不需要针对每一个目标对象单独创建一个代理类,并且不需要必须实现接口,可以直接代理实现类。

JVM动态生成代理类的字节码。Spring AOP、RPC框架的实现都依赖了动态代理。

动态代理实际开发中使用较少,但是框架中被频繁使用。

Java 动态代理实现主要有两种:JDK 动态代理、CGLIB 动态代理

3.1 JDK 动态代理

3.1.1 API

JDK 动态代理中 InvocationHandler 接口Proxy 类是核心。

Proxy 类中的 newProxyInstance() 方法用来生成一个代理对象。

1
2
3
4
5
6
public static Object newProxyInstance(ClassLoader loader,		// 类加载器,用来加载代理对象
Class<?>[] interfaces, // 被代理类实现的一些接口
InvocationHandler h) // 实现了 InvocationHandler 的对象
throws IllegalArgumentException {
......
}

JDK 动态代理中通过 InvocationHandler 来自定义代理逻辑。

当动态代理调用工业方法是,这个方法的调用会被转发到实现了 InvocationHandler 接口类的 invoke 方法来调用。

1
2
3
4
5
6
public interface InvocationHandler {
public Object invoke(Object proxy, // 动态生成的代理类
Method method, // 与代理类对象调用的方法相对应
Object[] args) // 当前method方法的参数
throws Throwable;
}

通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现了 InvocationHandler 接口类的 invoke 方法 。

因此可以在 invoke 方法中自定义处理逻辑。

3.1.2 使用步骤

1、定义一个接口以及实现类,该实现类是被代理类

2、自定 InvocationHandler 接口并重写 invoke方法,在 invoke 内部调用被代理对象的方法,并自定义一些逻辑。

3、通过 Proxy.newProxyInstance 创建一个代理对象。

3.1.3 场景实现

卖票接口:参考上面

12306:参考上面

JDK 动态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class JDKProxy {
public static void main(String[] args) {
SellTicketImpl sellTicket = new SellTicketImpl();
Class<SellTicketImpl> sellTicketClass = SellTicketImpl.class;
SellTicket sellTicketProxy = (SellTicket)Proxy.newProxyInstance(sellTicketClass.getClassLoader(), sellTicketClass.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[JDKProxy INFO]:执行前");
String ret = (String) method.invoke(sellTicket, args);
System.out.println("[JDKProxy INFO]:执行后");
return "[JDKProxy ]:" + ret;
}
});
String ret = sellTicketProxy.sell(100);
System.out.println(ret);
}
}

上面代码中我们通过匿名对象的方式将InvocationHandler实现传递个newProxyInstance参数中。

输出

1
2
3
4
[JDKProxy INFO]:执行前
花费100元购票.
[JDKProxy INFO]:执行后
[JDKProxy ]:购票成功

3.2 CGLib 动态代理

JDK 动态代理中存在一个问题,只能代理实现了接口的类。

为了解决这个问题,可以使用 CGLIB 动态代理开避免这个问题。

CBLIB,Code Generation Lib ,是一个基于ASM的动态字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。

CGLIB 通过继承方式实现代理,很知名的开源框架使用了 CGLIB ,如 Spring AOP。

3.2.1 API

CGLIB 动态代理中, MethodInterceptor 接口和 Enhancer 类是核心。

使用时需要自定义 MethodInterceptor 接口 并重写 intercept 方法,intercept 用于增强。

1
2
3
4
5
6
7
public interface MethodInterceptor extends Callback{
public Object intercept(Object obj, // 被代理的对象,需要被增强的对象
java.lang.reflect.Method method,// 被增强的方法
Object[] args, // 方法传入的参数
MethodProxy proxy // 用于调用原始方法的参数
) throws Throwable;
}

可以通过 Enhancer 类动态获取被代理类,当代理类调用方法时,实际调用的是 MethodInterceptor 的 intercept 方法。

3.2.2 使用步骤

1、定义被代理类

2、自定义 MethodInterceptor 重写 intercept 方法,intercept 与 JDK动态代理的 invoke 方法类似。

3、通过 Enhancer 类的 create 方法创建对象。

3.2.3 代码示例

添加依赖:CGLIB 不是JDK原生内容,是有个开源项目

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

接口:参考静态代理代码

12306:参考静态代码

CGLIBProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CGLibProxy {
public static void main(String[] args) {
SellTicketImpl sellTicket = new SellTicketImpl();
Class<SellTicketImpl> sellTicketClass = SellTicketImpl.class;
SellTicket sellTicketProxy = (SellTicket) Enhancer.create(sellTicketClass, sellTicketClass.getInterfaces(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("[CGLib INFO]:动态前");
String ret = (String) methodProxy.invoke(sellTicket, objects);
System.out.println("[CGLib INFO]:执行后");
return "[CGLib INFO]:" + ret;
}
});
String ret = sellTicketProxy.sell(100);
System.out.println(ret);
}
}

上面通过匿名对象来传递MethodInterceptor实现。

输出

1
2
3
4
[CGLib INFO]:动态前
花费100元购票.
[CGLib INFO]:执行后
[CGLib INFO]:购票成功

3.3 JDK 与 CGLIB 对比

JDK动态代理 CGLib动态代理
实现原理 反射机制生成 ASM字节码框架
使用场景 实现了接口的类 对于不实现接口的可以使用CGLib
效率 一般较高 相对较低
核心API Proxy/InvocationHandler Enhancer/MethodInterceptor

注意:由于CGlib是通过生成被代理类的子类的方式来增强,因此不用用于final修饰的类中。

4、补充-代理模式对比

4.1 JDK代理和CGLIB代理

使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在 JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者 方法进行代理,因为CGLib原理是动态生成被代理类的子类。

在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代 理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率 低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代 理,如果没有接口使用CGLIB代理。

4.2 静态代理与动态代理

动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中 的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们 可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实 现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题

5、参考

评论