设计模式04-代理模式

代理模式(Proxy Pattern)是指为其它对象提供一种代理,以控制对这个对象的访问。代理对象在调用端与目标对象之间起到中介作用。代理模式属于结构型设计模式。

示例代码

静态代理:显示声明被代理对象

https://github.com/chenpenghui93/design-pattern/tree/master/src/main/java/com/example/designpattern/proxy

动态代理:动态配置和替换被代理对象

https://github.com/chenpenghui93/design-pattern/tree/master/src/main/java/com/example/designpattern/proxy

静态代理与动态代理的本质区别:

  1. 静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步新增,违背开闭原则
  2. 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则
  3. 使用动态代理若要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类即可完成,无需修改代理类的代码

JDK实例

JDK Proxy采用字节码重组的方式,重新生成新的对象来替代原始对象以达到动态代理的目的。生成对象步骤如下

  1. 拿到被代理对象的引用,并且获取(反射)所有的接口
  2. JDK Proxy类重新生成一个新的类,这个新类会实现被代理类所实现的所有接口
  3. 动态生成Java代码
  4. 编译新生成的Java代码,生成class文件
  5. 将class文件加载至JVM中运行

CGLib与JDK动态代理对比:

  • JDK动态代理是实现了被代理对象的接口,CGLib是继承了被代理对象
  • JDK和CGLib都是在运行期生成字节码,JDK是直接写Class字节码,CGLib是使用ASM框架写Class字节码;CGLib代理实现更复杂,生成代理类比JDK效率低
  • JDK是通过反射机制调用代理方法;CGLib是通过FastClass机制直接调用方法,CGLib执行效率更高

Spring实例

ProxyFactoryBean的核心方法getObject(), 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Nullable
public Object getObject() throws BeansException {
this.initializeAdvisorChain();
if (this.isSingleton()) {
return this.getSingletonInstance();
} else {
if (this.targetName == null) {
this.logger.info("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property.");
}

return this.newPrototypeInstance();
}
}

在 getObject()方法中,主要调用 getSingletonInstance()和 newPrototypeInstance()。在 Spring 的配置中,如果不做任何设置,那么 Spring 代理生成的 Bean 都是单例对象;如果修改 scope 则每次创建一个新的原型对象。

Spring利用动态代理实现AOP有两个非常重要的类,JdkDynamicAopProxy和CglibAopProxy,类图如下

Spring中的代理选择原则:

  1. 当Bean有实现接口时,Spring就会用JDK的动态代理

  2. 当Bean没有实现接口时,Spring选择CGLib

  3. Spring可以通过配置强制使用CGLib,在配置文件中添加如下内容

    1
    <aop:aspectj-autoproxy proxy-target-class="true"/>

适用场景

  • 保护目标对象
  • 增强目标对象

模式优点

  • 代理模式能够将代理对象与真实被调用的目标对象分离,一定程度上降低了系统的耦合程度,易于扩展
  • 代理可以起到保护目标对象的作用
  • 代理可以增强目标对象的职责

模式缺点

  • 代理模式会造成系统设计中类的数目增加
  • 在调用端与目标对象之间增加了一个代理对象会造成请求处理速度变慢
  • 增加了系统的复杂度

参考