前言

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

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. JAVA的8种基本数据类型和类型转换

    byte 字节型                        1字节(8bit) 初始值:0 char 字符型 2字节 空格 short 短整型 2字节 0 int 整形    4字节 0 long ...

  2. mongoose+koa2 按照_id更新多条数据,删除数组中的字段,然后添加新的字段,$pull和$or结合使用

    await model.photo.update({ _id: { $in: photoIdsParam } }, { $pull: { customerIds: { code: custCode, ...

  3. BZOJ3457 : Ring

    根据Polya定理: \[ans=\frac{\sum_{d|n}\varphi(d)cal(\frac{n}{d})}{n}\] 其中$cal(n)$表示长度为$n$的无限循环后包含$S$的串的数量 ...

  4. Python ----pip安装模块提示“unknown or unsupported command install”的解决办法

    安装pip后,使用pip安装模块时,提示“unknown or unsupported command install” 解决方法: 1.cmd运行"where pip" 找出所有 ...

  5. word2vec skip-gram系列2

    Word2Vec的CBOW模型和Skip-gram模型 故事先从NNLM模型说起,网络结构图如下图所示,接下来先具体阐述下这个网络, 输入是个one-hot representation的向量.比如你 ...

  6. Spring mvc前台后台传值

    前台向后台传值: ①同名参数传递:form表单中提交input,Controller方法入参中,直接以同名参数获取 ②不同名参数传递:from表单提交input,Controller方法入参中需要使用 ...

  7. Spring(2)—IOC

    一.Spring IOC是什么 1.简述 Spring是一个开源框架 Spring为简化企业级应用开发而生,使用Spring可以使简单的JavaBean实现以前只有EJB才能实现的功能 Spring是 ...

  8. 动态规划-最长上升子序列(LIS)

    时间复杂度为〇(nlogn)的算法,下面就来看看. 我们再举一个例子:有以下序列A[]=3 1 2 6 4 5 10 7,求LIS长度. 我们定义一个B[i]来储存可能的排序序列,len为LIS长度. ...

  9. YUV422 YUV420 Planar \ Semi-Planar \ Interleaved YCbCr与YUV

    YCbCr是DVD.摄像机.数字电视等消费类视频产品中,常用的色彩编码方案.YCbCr 有时会称为 YCC..Y'CbCr 在模拟分量视频(analog component video)中也常被称为 ...

  10. adb devices 找不到设备的解决方法

    1.开启adb 2.查看设备时的报错 问题1:cannot connect  to daemon 解决方法:找到占据5037端口的进城,并在“任务管理器”中依据“PID”查找到,解决进程 问题2:手机 ...