前言

非常重要的一个设计模式,也很常见,很多框架都有它的影子。定义就不多说了。两点:

1、为其它对象提供一个代理服务,间接控制对这个对象的访问,联想 Spring 事务机制,在合适的方法上加个 transaction 注解,就分分钟实现了事务。

2、除了1,代理对象还能充当中介的角色。

为什么要有代理模式?

如果希望不给原有对象附加太多的责任(和本对象无关的冗余代码),但是还想能为其实现新功能,那么代理模式就是做这个的。还是联系 Spring 事务机制,很好的应用场景。

实际生活里,可以联系租房,大家租房一般都会找中介,这个中介就是代理对象的角色,它解耦(分离)了房东的一部分责任,因为房东太忙了,或者房东不屑于做这些事情,故交给代理对象去做。

一句话:解耦合,提高扩展性

静态代理模式

顾名思义,代理类被显示的指定了,即代理在代码里被写死了。实现最简单,也很少用,但是能帮助快速理解思想

public interface StaticProxy {
void dosth();
}
//////////////接下来是很熟悉的做法,实现这个借口
public class RealRole implements StaticProxy { @Override
public void dosth() {
System.out.println("do sth");
}
}
///////////然后重要的角色——代理类,联系 Spring 事务机制
public class ProxyRole implements StaticProxy {
private StaticProxy staticProxy; public ProxyRole() {
this.staticProxy = new RealRole();
} @Override
public void dosth() {
// 真正业务逻辑之前的处理,比如加上事务控制
before();
this.staticProxy.dosth(); // 真正的业务逻辑处理,比如数据库的 crud
after(); // 善后处理,比如,事务提交
} private void after() {
System.out.println("after dosth");
} private void before() {
System.out.println("before dosth");
}
}
////////执行
public class ProxyMain {
public static void main(String[] args) {
StaticProxy staticProxy = new ProxyRole();
staticProxy.dosth();
}
}

打印==============

before dosth
do sth
after dosth

如上就是最简单的静态代理模式的实现,很直观,就是使用委托的思想,把责任转移到被代理的对象上,代理类实现非业务相关的功能

缺陷

静态代理非常简单,但是它的缺陷也是显然的,因为静态代理的代理关系在 IDE 编译时就确定了,如果接口改变了,不仅实现类要改变,代理类也要改变,代理类和接口之间的耦合非常严重。

动态代理模式

和静态代理相反,代理类不是写死的,而是动态的创建。又分为两种实现方案:

基于 JDK实现

也很简单,就是利用 Java 的 API 来实现代理类,即我们不用自己写代理类了,也就是上面例子里的类——ProxyRole。到这里,其实也能猜出来,本质就是利用 Java 的反射机制在程序运行期动态的创建接口的实现类,并生产代理对象而已,如此一来,就能避免实现的接口——StaticProxy 改变了,导致代理类也跟着变的场景发生。下面看实现代码:

首先写好需要实现的接口,和具体实现类

public interface DynamicProxy {
void dosth();
}
////////////
public class DynamicRealRole implements DynamicProxy {
@Override
public void dosth() {
System.out.println("do sth");
}
}

然后,要实现 JDK 的一个接口——InvocationHandler,Java 的动态代理机制中,有两个重要的类,一个是 InvocationHandler 接口,一个是 Proxy 类。

注意,DynamicProxyRole 不是代理类,代理类我们不需要自己写,它是 JDK 动态生成给我们的(反射机制)

public class DynamicProxyRole implements InvocationHandler {
private Object object; // 被代理的对象 public DynamicProxyRole(Object object) { // 构造注入
this.object = object;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object ret = method.invoke(object, args);
after(); return ret;
} private void after() {
System.out.println("after dosth");
} private void before() {
System.out.println("before dosth");
}
}

InvocationHandler 接口只有一个方法 —— invoke,参数也很好理解,分别是:

proxy:代理的真实对象,也就是实现类的对象

method:要调用真实对象的某个方法的Method对象,也就是会调用 dosth 的方法的对象

args:调用真实对象某个方法时接受的参数,没有就是空数组

最后写运行类

public class DynamicMain {
private static DynamicProxy realRole = new DynamicRealRole(); // 被代理的类,也就是实现类
private static DynamicProxyRole dynamicProxyRole = new DynamicProxyRole(realRole); public static void main(String[] args) {
// 通过JDK动态代理获取被代理对象(实现类的对象)的代理对象,该代理类实现了指定的需要去代理的接口,也就是第2个参数
DynamicProxy dynamicProxyObj = (DynamicProxy) Proxy.newProxyInstance(
realRole.getClass().getClassLoader(), // 被代理的类的加载器
realRole.getClass().getInterfaces(), // 被代理的类需要实现的接口,可以有多个
dynamicProxyRole // 必须是实现了 InvocationHandler 接口的类,invoke 方法里写业务逻辑和代理方法
);
dynamicProxyObj.dosth();
}
}

Proxy 类的作用是动态创建一个代理对象,也就是代理对象不需要我们自己写。Proxy 提供了许多的方法,用的最多的是 newProxyInstance,注释里也写了:

其中第一个参数是被代理的类的加载器,传入的目的是告诉 JDK 由哪个类加载器对生成的代理进行加载。其实就是真实的类(实现类)的对象的加载器。

第二个参数是代理类需要实现的接口,可以多个。其实就是接口 DynamicProxy,很好理解,在静态代理模式中,我们就需要手动实现这个接口,来实现代理类。

第三个参数就是实现了InvocationHandler接口的类即可,原因是此类里有 invoke 方法,而通过 Proxy 的 newProxyInstance 方法生成的代理类去调用接口方法(dosth)时,对方法(dosth)的调用会自动委托给 InvocationHandler 接口的 invoke 方法,这样也就实现了代理模式。

综上,代理对象就实现了在程序运行时产生。进一步要知道,所有的 JDK 动态代理都会继承 java.lang.reflect.Proxy,同时还会实现我们指定的接口(Proxy 的 newProxyInstance 第二个参数里的接口)。

看到这里,也确定,JDK 动态代理核心就是反射思想的应用,没什么新鲜的东西。

缺陷

JDK 动态代理这种方式只能代理接口,这是其缺陷

基于 CGLib 实现

Java动态代理是基于接口实现的,如果对象没有实现接口,那么可以用 CGLIB 类库实现,它的原理是基于继承实现代理类。代码也不难

首先,写一个类,其没有实现接口,此时前面的 JDK 动态代理就无法使用了。

public class NoInterfaceReal {
public void dosth() {
System.out.println("do sth");
}
}

其次,需要实现CGLIB 类库提供的接口——MethodInterceptor

在这之前,先下载CGLib 包

        <!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>

然后实现其提供的接口——MethodInterceptor,关键方法是 intercept

public class CGLibProxy implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
before();
Object ret = proxy.invokeSuper(obj, args);
after(); return ret;
} private void after() {
System.out.println("after dosth");
} private void before() {
System.out.println("before dosth");
}
}

实现了 MethodInterceptor 接口后,后续生成的代理对象对 dosth 方法的调用会被转发到 intercept 方法,自然也就实现了代理模式。

最后,通过 CGLIB 动态代理生成代理对象,就完成了代理模式,非常简单

public class CGLibMain {
public static void main(String[] args) {
CGLibProxy cgLibProxy = new CGLibProxy();
NoInterfaceReal proxy = (NoInterfaceReal) Enhancer.create(NoInterfaceReal.class, cgLibProxy);
proxy.dosth();
}
}

通过CGLib 的Enhancer类 create 了一个代理对象,参数传入需要被代理的类(可以不是接口),和实现了 MethodInterceptor 接口的类的对象即可。CGLib 就会为我们自动生成继承了被代理的类的代理对象,通过代理对象调用 dosth 方法,其调用会被委托给第二个参数里的 intercept 方法。

综上得知:

1、CGLib 底层是利用 asm 字节码框架实现的,该框架可以在 Java 程序运行时对字节码进行修改和动态生成,故它可以代理普通类,具体细节是通过继承和重写需要被代理的类(NoInterfaceReal)来实现。

2、CGLib 可以实现对方法的代理,即可以实现拦截(只代理)某个方法。

3、通过CGLib 的 Enhancer 类来create 代理对象。而对这个对象所有非final方法的调用都会委托给 MethodInterceptor 接口的 intercept,我们可以在该方法内部写拦截代码,最后在通过调用MethodProxy 对象的 invokeSuper() 方法,把调用转发给真实对象

缺陷

无法对 final 类、或者 final 方法进行代理

代理模式的性能对比

直接搬运结论:CGLib 底层基于asm 框架实现,比 Java 反射性能好,但是比 JDK 动态代理稍微慢一些

代理模式的缺点

主要是性能问题,什么增加系统复杂度等都不是事儿。同等条件,用代理,肯定比不用代理要慢一些。

代理模式和装饰器模式对比

实现方式上很像,但是目标不一样,后者是为了给类(对象)增加新的功能,不改变API,前者除了这些作用,目标主要是为了使用中间人(代理角色)给本类(对象)减少负担。

参见:对复合(协作)算法/策略的封装方法——装饰模式总结

代理模式的应用

非常常见了,AOP非常典型,还有各种框架的拦截器机制,数据库切换等工具。。。

欢迎关注

dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

AOP 技术原理——代理模式全面总结的更多相关文章

  1. 2018.12.24 Spring中的aop演示(也就是运用aop技术实现代理模式)

    Aop的最大意义是:在不改变原来代码的前提下,也不对源代码做任何协议接口要求.而实现了类似插件的方式,来修改源代码,给源代码插入新的执行代码. 1.spring中的aop演示 aop:面向方面编程.不 ...

  2. Spring Aop技术原理分析

    本篇文章从Aop xml元素的解析开始,分析了Aop在Spring中所使用到的技术.包括Aop各元素在容器中的表示方式.Aop自动代理的技术.代理对象的生成及Aop拦截链的调用等等.将这些技术串联起来 ...

  3. springAop:Aop(Xml)配置,Aop注解配置,spring_Aop综合案例,Aop底层原理分析

    知识点梳理 课堂讲义 0)回顾Spring体系结构 Spring的两个核心:IoC和AOP 1)AOP简介 1.1)OOP开发思路 OOP规定程序开发以类为模型,一切围绕对象进行,OOP中完成某个任务 ...

  4. Spring AOP 和 动态代理技术

    AOP 是什么东西 首先来说 AOP 并不是 Spring 框架的核心技术之一,AOP 全称 Aspect Orient Programming,即面向切面的编程.其要解决的问题就是在不改变源代码的情 ...

  5. 3.静态AOP实现-代理模式

    通过代理模式实现在RegUser()方法本身业务前后加上一些自己的功能,如:BeforeProceed和AfterProceed,即不修改UserProcessor类又能增加新功能 定义1个用户接口, ...

  6. Spring AOP的作用,动态代理模式

    AOP即面向切面编程.AOP是基于代理模式的. 代理模式: 当我们需要修改一个类,在类中加入代码时,为了不破坏这个类的封装性.可以使用代理模式,建立一个代理类. 比如:修改需求,在调用UserCont ...

  7. 代理模式 vs 装饰模式

    代理模式和装饰模式有很大的相似性,二者的类图(几乎)是一样的.下面分别讲解代理模式和装饰模式. 1.代理模式 一般著名的跑步运动员都会有自己的代理人,如果想联系该运动员的比赛事宜,可以直接联系他的代理 ...

  8. Spring框架_代理模式(静态代理,动态代理,cglib代理)

    共性问题: 1. 服务器启动报错,什么原因? * jar包缺少.jar包冲突 1) 先检查项目中是否缺少jar包引用 2) 服务器: 检查jar包有没有发布到服务器下:                 ...

  9. Java进阶篇设计模式之七 ----- 享元模式和代理模式

    前言 在上一篇中我们学习了结构型模式的组合模式和过滤器模式.本篇则来学习下结构型模式最后的两个模式, 享元模式和代理模式. 享元模式 简介 享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能 ...

随机推荐

  1. 2046 ACM 数学

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=2046 思维:与之前有两道题目相似,n可以由n-1和n-2递推过来.f(n)=f(n-1)*1+f(n-2) ...

  2. mongoDB,mongoose,没有数组就添加,如果有了数组,就向数组中添加新元素

    db.getCollection('photos').findOneAndUpdate("5b028e71f32bd5004f905879",   //findByIdAndUpd ...

  3. Android 中的设计模式

    1.单例模式 ContentProvider是单例模式,多个ContentResolver操作的都是同一个ContentProvider.

  4. Ruby用百度搜索爬虫

    Ruby用百度搜索爬虫 博主ruby学得断断续续,打算写一个有点用的小程序娱乐一下,打算用ruby通过百度通道爬取网络信息. 第三方库准备 mechanize:比较方便地处理网络请求,类似于Pytho ...

  5. servlet 表单加上multipart/form-data后request.getParameter获取NULL(已解决)

    先上结论(可能不对,因为这是根据实践猜测而来,欢迎指正) 表单改为multipart/form-data传值后,数据就不能通过普通的request.getParameter获取. 文件和文件名通过Fi ...

  6. Delphi 之弹出气泡消息提示

    //h:窗体中控件的句柄(用在某个控件上提示消息)(要调用的控件句柄) //text:要提示的文本 //cap:提示框上的标题 //IconType:提示框上显示的图标(共三个值.1.2.3分别为提示 ...

  7. Reactor反应器模式 (epoll)

    1. 背景 最近在看redis源码,主体流程看完了. 在网上看到了reactor模式,看了一下,其实我们经常使用这种模式. 2. 什么是reactor模式 反应器设计模式(Reactor patter ...

  8. windows下Graphviz安装及入门教程

    下载安装配置环境变量 intall 配置环境变量 验证 基本绘图入门 graph digraph 一个复杂的例子 和python交互 发现好的工具,如同发现新大陆.有时,我们会好奇,论文中.各种专业的 ...

  9. Lazarus 初识

    Lazarus 使用 Free Pascal 的编译器,支持 Object Pascal 语言,与 Delphi 高度兼容,并看做后者的自由软件替代品. Lazarus 下载与安装 我们先去 Laza ...

  10. js正则提取数字小数,提取中文,提取英文

    var value="污染物:PM2.5"; //提取中文 console.log(value.replace(/[^\u4E00-\u9FA5]/g,'')); //提取英文 c ...