动态代理的常用实现方式是反射。反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。

动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类似机制做到的,比如用来包装 RPC 调用、面向切面的编程(AOP)。

JDK 自身提供的动态代理,就是主要利用了上面提到的反射机制。但动态代理不止有反射一种实现方式,还有其他的实现方式,比如利用传说中更高性能的字节码操作机制,类似 ASM、cglib(基于 ASM,一个 Java 字节码操作框架)、Javassist 等。简单来说,动态代理是一种行为方式,而反射或 ASM 只是它的一种实现手段而已。

JDK Proxy 和 CGLib 的区别主要体现在以下几个方面:

  • JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;
  • Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新 JDK Proxy,例如 Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;
  • JDK Proxy 是通过拦截器加反射的方式实现的;
  • JDK Proxy 只能代理继承接口的类;
  • JDK Proxy 实现和调用起来比较简单;
  • CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
  • CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的。

什么是静态代理

静态代理是代理类在编译期间就创建好了,不是编译器生成的代理类,而是手动创建的类。在编译时就已经将接口,被代理类,代理类等确定下来。,软件设计中所指的代理一般是指静态代理,也就是在代码中显式指定的代理。

下面我们通过一个简单的案例,来了解下静态代理。

Cat.java
1
2
3
4
5
6
7
8
9
/**
* 静态代理类接口, 委托类和代理类都需要实现的接口规范。
* 定义了一个猫科动物的两个行为接口,吃东西,奔跑。
* 作为代理类 和委托类之间的约束接口
*/
public interface Cat {
public String eatFood(String foodName);
public boolean running();
}
Lion.java
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
/**
* 狮子 实现了猫科动物接口Cat, 并实现了具体的行为。作为委托类实现
*/
public class Lion implements Cat {
private String name;
private int runningSpeed;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getRunningSpeed() {
return runningSpeed;
}
public void setRunningSpeed(int runningSpeed) {
this.runningSpeed = runningSpeed;
}
public Lion() { }

@Override
public String eatFood(String foodName) {
String eat = this.name + " Lion eat food. foodName = " + foodName;
System.out.println(eat); return eat;
}
@Override
public boolean running() {
System.out.println(this.name + " Lion is running . Speed :" + this.runningSpeed); return false;
}
}

代理类角色(FeederProxy)

FeederProxy.java
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
/**
* 饲养员 实现Cat接口,作为静态代理类实现。代理狮子的行为。
* 代理类中可以新增一些其他行为,在实践中主要做的是参数校验的功能。
*/
public class FeederProxy implements Cat {
private Cat cat;
public FeederProxy(){}
public FeederProxy(Cat cat) {
if (cat instanceof Cat) {
this.cat = cat;
}
}
public void setCat(Cat cat) {
if (cat instanceof Cat) {
this.cat = cat;
}
}
@Override
public String eatFood(String foodName) {
System.out.println("proxy Lion exec eatFood ");
return cat.eatFood(foodName);
}
@Override
public boolean running() {
System.out.println("proxy Lion exec running.");
return cat.running();
}
}

静态代理类测试

staticProxyTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 静态代理类测试
*/
public class staticProxyTest {
public static void main(String[] args) {
Lion lion = new Lion();
lion.setName("狮子 小王");
lion.setRunningSpeed(100);
/**
* new 静态代理类,静态代理类在编译前已经创建好了,和动态代理的最大区别点
*/
Cat proxy = new FeederProxy(lion);
System.out.println(Thread.currentThread().getName()+" -- " + proxy.eatFood("水牛"));
proxy.running();
}
}

静态代理很好的诠释了代理设计模式,代理模式最主要的就是有一个公共接口(Cat),一个委托类(Lion),一个代理类(FeederProxy),代理类持有委托类的实例,代为执行具体类实例方法。

代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指客户端不直接调用实际对象的方法,客户端依赖公共接口并使用代理类。 那么我们在代理过程中就可以加上一些其他用途。

就这个例子来说在 eatFood 方法调用中,代理类在调用具体实现类之前添加System.out.println("proxy Lion exec eatFood ");语句 就是添加间接性带来的收益。代理类存在的意义是为了增加一些公共的逻辑代码。

静态代理的缺陷

  1. 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

  2. 代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。

  3. 静态代理一个代理只能代理一种类型,而且是在编译器就已经确定被代理的对象。

JDK Proxy 和 CGLib 的使用样例

JDK Proxy 动态代理实现

评论




Copyright 2019-2020 YANLIANG'S BLOG 载入天数...载入时分秒...