接着学习设计模式系列,今天讲解的是代理模式。

定义

什么是代理模式?

代理模式,也叫委托模式,其定义是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。它包含了三个角色:

Subject:抽象主题角色。可以是抽象类也可以是接口,是一个最普通的业务类型定义。

RealSubject:具体主题角色,也就是被代理的对象,是业务逻辑的具体执行者。

Proxy:代理主题角色。负责读具体主题角色的引用,通过真实角色的业务逻辑方法来实现抽象方法,并在前后可以附加自己的操作。

用类图来表示的话大概如下:

我们可以用举一个电影演员拍戏的例子,一般来说,演员最主要的工作就是演戏,其他的事可以交给他的经纪人去做,例如谈合同,安排档期等等,而负责这些场外工作的经纪人就相当于Proxy,而负责核心业务的演员就是 RealSubject

这就是代理模式的设计思路,除此之外,代理模式分为静态代理和动态代理,静态代理是我们自己创建一个代理类,而动态代理是程序自动帮我们生成一个代理类,可以在程序运行时再生成对象,下面分别对它们做介绍。

静态代理

静态代理在程序运行之前,代理类.class文件就已经被创建了。还是用上面演员演戏的例子,在静态代理模式中,我们要先创建一个抽象主题角色 Star

public interface Star {
// 演戏
void act();
}

接下来就是创建具体的主题角色和代理主题角色,分别实现这个接口,先创建一个具体的主题角色 Actor

/**
* 演员,也就是具体的主题角色
*
* @author Tao
* @since 2019/7/9 18:34
*/
public class Actor implements Star {
public void act() {
System.out.println("演员演戏~~~");
}
}

然后就是创建代理主题角色,也就是代理类,代理类本身并不负责核心业务的执行流程,演戏这事还得明星自己来。所以在代理类中需要将真实对象引入,下面是具体的代码实现:

/**
* 代理对象
* @author Tao
* @since 2019/7/9 18:43
*/
public class Agent implements Star {
/**
* 接收真实的明星对象
*/
private Star star; /**
* 通过构造方法传进来真实的明星对象
*
* @param star star
*/
public Agent(Star star) {
this.star = star;
} public void act() {
System.out.println("签合同");
star.act();
System.out.println("演完戏就收钱了");
}
}

代码的逻辑还是比较清晰的,通过维护一个Star对象,可以在act里调用具体主题角色的业务逻辑,并且在核心逻辑前后可以做一些辅助操作,比如签合同,收钱等,这样代理模式的角色就都分工完成了,最后用一个场景类来验证下:

public class Client {
public static void main(String[] args) {
Star actor = new Actor();
Agent agent = new Agent(actor);
agent.act();
}
}

运行的结果如下:

签合同

演员演戏~~~

演完戏就收钱了

动态代理

动态代理分为两种,分别是JDK动态代理和 CGLIB 动态代理,怎么又分了,代理模式分类真多,不过来都来了,就都学习一下吧。

JDK动态代理

前面说了,在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK再运行时帮我们动态的来创建。

/**
* 动态代理处理类
*
* @author Tao
* @since 2019/7/9 19:04
*/
public class JdkProxyHandler { /**
* 用来接收真实明星对象
*/
private Object star; /**
* 通过构造方法传进来真实的明星对象
*
* @param star star
*/
public JdkProxyHandler(Star star) {
super();
this.star = star;
} /**
* 给真实对象生成一个代理对象实例
*
* @return Object
*/
public Object getProxyInstance() {
return Proxy.newProxyInstance(star.getClass().getClassLoader(),
star.getClass().getInterfaces(), (proxy, method, args) -> { System.out.println("签合同");
// 执行具体的业务逻辑
Object object = method.invoke(star, args);
System.out.println("演出完经纪人去收钱……"); return object;
});
}
}

这里说一下Proxy.newProxyInstance 这个方法,该方法包含了三个参数,

  • ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的;
  • Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型;
  • InvocationHandler:指定动态处理器,执行目标对象的方法时会触发事件处理器的方法。

写完了动态代理实现类,我们写个场景类测试下,

public class Client {
public static void main(String[] args) {
Star actor = new Actor();
// 创建动态代理对象实例
Star jdkProxy = (Star) new JdkProxyHandler(actor).getProxyInstance();
jdkProxy.act();
}
}

执行结果正常输出:

签合同

演员演戏~~~

演出完代理去收钱……

由此可见,JDK 动态代理确实发挥了代理的功能,相对于静态代理,JDK 动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但它同样有缺陷,就是动态代理的实现类需要类实现接口来完成代理的业务,也就是说它始终无法摆脱仅支持interface代理的桎梏,这是设计上的缺陷。而这时CGLIB 动态代理就派上用场了。

CGLIB 动态代理

CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。下面我们写一个关于CGLib的动态代理类,值得说下的是,CGLib所在的依赖包不是JDK本身就有的,所以我们需要额外引入,如果是用maven来管理的话,就可以直接引入如下的依赖:

<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.3</version>
</dependency>
</dependencies>

使用 CGLIB 需要实现 MethodInterceptor 接口,并重写intercept 方法,在该方法中对原始要执行的方法前后做增强处理。该类的代理对象可以使用代码中的字节码增强器来获取。具体的代码如下:

public class CglibProxy implements MethodInterceptor {
/**
* 维护目标对象
*/
private Object target; public Object getProxyInstance(final Object target) {
this.target = target;
// Enhancer类是CGLIB中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展
Enhancer enhancer = new Enhancer();
// 将被代理的对象设置成父类
enhancer.setSuperclass(this.target.getClass());
// 回调方法,设置拦截器
enhancer.setCallback(this);
// 动态创建一个代理类
return enhancer.create();
} public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("签合同");
// 执行具体的业务逻辑
Object result = methodProxy.invoke(o, objects);
System.out.println("演出完经纪人去收钱……");
return result;
}
} 场景测试类:
public class Client {
public static void main(String[] args) {
Star actor = new Actor();
// 创建动态代理对象实例
Star proxy = (Star) new CglibProxy().getProxyInstance(actor);
proxy.act();
}
}

可以看出,测试类的逻辑和JDK动态代理差不多,其实套路都是一样的,其实技术实现不同。

总结一下CGLIB代理模式: CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

扩展知识

这里扩展一个知识点,那就是Spring AOP的底层实现,为什么在这里提及呢?因为Spring AOP的底层实现就是基于代理模式,而JDK 动态代理和 CGLIB 动态代理均是实现 Spring AOP 的基础。我们可以看下AOP的部分底层源码:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 判断目标类是否是接口或者目标类是否Proxy类型,若是则使用JDK动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 使用CGLIB的方式创建代理对象
return new ObjenesisCglibAopProxy(config);
}
else {
// 上面条件都不满足就使用JDK的提供的代理方式生成代理对象
return new JdkDynamicAopProxy(config);
}
}
}

源码的判断逻辑并不难,主要是根据目标类是否是接口或者Proxy类型来判断使用哪种代理模式创建代理对象,使用的代理模式正是JDK动态代理和CGLIB 动态代理技术。由此可见,了解代理模式还是很重要的,起码以后面试官问AOP的底层实现时,我们还能吹一波呢,哈哈~~~

设计模式:代理模式是什么,Spring AOP还和它有关系?的更多相关文章

  1. linkin大话设计模式--代理模式

    代理模式是一种应用非常广泛的设计模式,当客户端代码需要调用某个对象的时候,客户端并不关心是否可以准确的得到这个对象,他只要一个能够提供该功能的对象而已,此时我们就可以返回该对象的代理.总而言之,客户端 ...

  2. C++设计模式——代理模式

    前言 青春总是那样,逝去了才开始回味:大学生活也是在不经意间就溜走了,现在上班的时候,偶尔还会怀念大学时,大家在一起玩游戏的时光.大学喜欢玩游戏,但是可悲的校园网,速度能把人逼疯了:还好,后来搞了一个 ...

  3. Java设计模式-代理模式之动态代理(附源代码分析)

    Java设计模式-代理模式之动态代理(附源代码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的差别就是:动态代理是在执行时刻动态的创建出代理类及其对象. 上篇中的静态代 ...

  4. Java 之 设计模式——代理模式

    设计模式——代理模式 一.概述 1.代理模式 (1)真实对象:被代理的对象 (2)代理对象:代理真实对象的 (3)代理模式:代理对象代理真实对象,达到增强真实对象功能的目的 二.实现方式 1.静态代理 ...

  5. 9. 星际争霸之php设计模式--代理模式

    题记==============================================================================本php设计模式专辑来源于博客(jymo ...

  6. PHP设计模式-代理模式

    概念理解: 代理模式,是对简单处理程序(或指针)的增强,用于引用一个对象:这个指针被代理对象取代,代理对象位于客户端和真实程序之间,指针有一个可被多个目标利用的钩子. 参与者: client(参与者) ...

  7. 浅谈Python设计模式 - 代理模式

    声明:本系列文章主要参考<精通Python设计模式>一书,并且参考一些资料,结合自己的一些看法来总结而来. 一.在某些应用中,我们想要在访问某个对象之前执行一个或者多个重要的操作,例如,访 ...

  8. Spring中常见的设计模式——代理模式

    一.代理模式的应用场景 生活中的中介,黄牛,等一系列帮助甲方做事的行为,都是代理模式的体现.代理模式(Proxy Pattern)是指为题对象提供一种代理,以控制对这个对象的访问.代理对象在客户端和目 ...

  9. 设计模式--5.5 代理模式-通用代码及aop

    1.通用代码 (1)Subjects package com.design.代理模式.通用代码; public interface Subject { void request(); } (2)Rea ...

随机推荐

  1. js错误界面

    <!DOCTYPE html><html><head><meta http-equiv="Content-Type" content=&q ...

  2. Bind Enum to Combobox.SelectedIndex

    原文:Bind Enum to Combobox.SelectedIndex Do you mean that you want to bind a variable (not a property) ...

  3. XAML的命名空间

    原文:XAML的命名空间 一个最简单的XAML例子   <Window x:Class="WpfApplication1.MainWindow" xmlns="ht ...

  4. Win10《芒果TV》发布两周年纪念特别献礼,陪你度国庆,好礼送不停

    2015年芒果TV和微软中国联姻,在Windows10发布之际,共同宣告了Win10版<芒果TV>的诞生,第一个版本于2015年9月30日登陆Windows商店,历经28次迭代,现在她两岁 ...

  5. scp 专题

    Tips:阿里云中需要使用内网ip,否则会一直阻塞Linux scp命令用于Linux之间复制文件和目录,具体如何使用这里好好介绍一下,从本地复制到远程.从远程复制到本地是两种使用方式.这里有具体举例 ...

  6. Advanced Installer 11.9基于IIS打包札记(For MySQL)

    原文:Advanced Installer 11.9基于IIS打包札记(For MySQL) Mysql免安装前期部署 下载绿色命令行版本的mysql,将其放入到发布的程序发布包内,执行Update批 ...

  7. VCL的通用属性,方法和事件 good

    Action 一些组件标识与组件相连接的活动对象Align 一些组件确定组件在父类组件区内的对齐方式Anchors 大部分组件说明与组件连接的窗体的位置点AutoSize 一些组件说明组件是否可根据内 ...

  8. Easy Compression Library(代替TFileStream, TMemoryStream and TStream)

    Easy Compression Library is a very easy-to-use replacement of TFileStream, TMemoryStream and other T ...

  9. mysql自动安装教程说明

    这里只说明了思路和方法 我们在安装程序里面可能需要安装的时候将mysql一起安装,那么我们就按照下面的顺序思路来. 首先我们安装的电脑上可能已经安装了mysql,所以我们的mysql服务就起一个名字, ...

  10. JDK JRE JVM 分别是什么

    JDK: Java Development Kit Java 开发工具包 JRE: Java Runtime Enviroment Java 运行时环境 JVM: Java Virtual Machi ...