【设计模式】--- 装饰器模式、静态代理模式和动态代理模式
文章目录
- 1 引子
- 2 业务场景介绍
- 3 静态代理模式
- 4 装饰器模式
- 5 动态代理模式
- 5.1 Proxy --- 具体的代理对象生成组件
- 5.2 InvocationHandler --- 封装被代理对象、调用被代理对象的方法并对方法进行增强
- 5.3 另一种代码书写方式 --- 代理对象的生成直接写在InvocationHandler对象里
- 6 总结
- 7 扩展 --- Proxy.newProxyInstance底层原理简析
本文源码地址:https://github.com/nieandsun/mybatis-study
1 引子
看过上篇文章《【Mybatis源码探索】 — Mybatis查询过程核心源码解读 — 先聊聊selectOne方法》对Executor和StatementHandler的源码解析后,相信你肯定对装饰器模式有了一定的认识,本篇文章将对该模式做具体的归纳总结。同时考虑到由于静态代理模式和装饰器模式过于相似
的原因本篇文章也会对静态代理模式以及动态代理模式进行归纳总结 —》 这里之所以带上动态代理模式是因为 想要更深入的探索mybatis源码,必须对动态代理模式有一个比较深入的理解。
2 业务场景介绍
无论是铅笔制造公司还是钢笔制造公司,它们制造那么多的笔肯定是为了卖给别人,为了描述卖笔这个行为,可以为此定义一个抽象接口:
//抽象接口,用来描述笔制造厂的行为
public interface PenFactory {void salePens(String color);
}
铅笔制造公司卖的是铅笔,所以可以为此生成一个具体的实现类:
//具体实现类,卖自己工厂生产的铅笔
public class PencilFactory implements PenFactory {@Overridepublic void salePens(String color) {System.err.println("卖" + color + "颜色的铅笔");}
}
当然铅笔制造公司肯定可以在卖自己生产的笔之前专门派自己公司的人做市场调研,然后再将笔卖给具体的用户。。。但是这样肯定会增加他们的人力成本。
其实现在随着社会分工的细化,铅笔制造公司完全可以只专注于自己的本行工作 — 制造更好的铅笔。而市场调研、将笔卖给每一个用户,甚至对用户使用满意度等的调查都由代理商去完成 —> 虽然代理商肯定会从中赚取差价,但是假若你自己卖只能卖10元一支,而代理商通过宣传等手段能卖20元1支,这样人家替你卖,卖出1支还是给你10元,你是不是肯定没吃亏???
3 静态代理模式
基于以上场景,假设我是一个非常专一的代理商,这辈子就只想做好一件事,就是想做好铅笔的代理,那我这个代理商的代码就可以这样写:
//静态代理 -------专一的代理商
//【注意】 这里要和铅笔工厂实现一样的接口----PenFactory
public class SpecificProxy implements PenFactory {//这辈子只做铅笔的代理private PenFactory penFactory = new PencilFactory();@Overridepublic void salePens(String color) {beforeEnhance();penFactory.salePens(color);afterEnhance();}private void beforeEnhance() {System.out.println("在卖笔之前对市场进行调研。。。。");}private void afterEnhance() {System.out.println("卖了一段时间的笔之后,对市场进行总结评估");}
}
其实这就是所谓的静态代理模式。
4 装饰器模式
还是基于2中的场景,假设我是一个稍微有点野心的代理商,我不止想做铅笔的代理,我甚至还想做钢笔制造厂的代理、圆珠笔制造厂的代理,甚至毛笔,碳素笔。。。那该怎么搞?
如果想照搬静态代理方式的话,那肯定就不能只有一个PenFactory接口了:可以定义一个铅笔工厂的接口,在该接口里有一个卖铅笔的方法,然后写一个铅笔工厂的具体实现类;再定义一个钢笔工厂的接口,在接口里弄一个卖钢笔的方法,然后写一个钢笔的具体实现类。。。建立代理商类的时候一一实现这些工厂的接口、包装具体的工厂对象、对各个方法进行前后增强。。。显然这样代码会变得及其冗余!!! 而且这种方式违背了设计模式的开闭原则 —> 代理一种新类型的笔,就要在代理商的代码里新实现一个接口,这很有可能会殃及到原来的代理业务代码。
那到底该怎么办呢?其实就算不懂设计模式,我觉得大家肯定也会写出如下的代码:
//其实这就是装饰器模式了 --- 铅笔、钢笔、毛笔各种笔的代理商
//【注意】 这里要和铅笔工厂、钢笔工厂等各种笔工厂实现一样的接口----PenFactory
public class PensProxy implements PenFactory {private PenFactory penFactory;/**** 只要是笔我都可以代理,由调用者指明我到底代理哪种笔* @param penFactory*/public PensProxy(PenFactory penFactory) {this.penFactory = penFactory;}@Overridepublic void salePens(String color) {beforeEnhance();penFactory.salePens(color);afterEnhance();}private void beforeEnhance() {System.out.println("在卖笔之前对市场进行调研。。。。");}private void afterEnhance() {System.out.println("卖了一段时间的笔之后,对市场进行总结评估");}
}
其实这就是所谓的装饰器模式。我想读到这里肯定有人会懵逼: 这不也是对各个工厂做代理么?为什么这个就叫装饰器模式而3中所讲的就叫代理模式啊。。。其实对于这个问题我觉得可以从以下几个方面去理解:
(1)我讲的这个栗子不好,因为从最开始就一直在说工厂、代理商这些概念。。。很容易让人有一种先入为主的感觉
(2)回看上篇文章《【Mybatis源码探索】 — Mybatis查询过程核心源码解读 — 先聊聊selectOne方法》中提到的Executor和StatementHandler的源码,再加上我举得栗子,怎么就不能说装饰器模式的具体实现也是对目标类的一种代理呢???
(3)其实也可以这么理解:上面的类并不真正干活,它其实主要做的任务是卖东西之前的市场调查和卖东西之后的市场评估,真正的卖东西这个事,它其实是调用的被代理对象的服务 —> 即对被代理对象的一种装饰。
5 动态代理模式
接着继续前面的场景,假设我是一个野心更大的代理商,我不仅想代理笔,还想代理玩具、代理家具、代理电子产品。。。
那无论是照搬静态代理模式、还是装饰器模式,都不可避免的会修改代理类 —> 这当然不是我们开发者想要的结果 —》违背开闭原则。
那该怎么办呢? 这时候就不得不提动态代理模式了。动态代理模式有两个关键的组件:Proxy和InvocationHandler,接下来对其进行一一介绍。
5.1 Proxy — 具体的代理对象生成组件
Proxy并不是具体的代理对象,而是代理对象的生成对象,它有一个方法newProxyInstance(...)
就是专门用来生成具体的代理对象的。newProxyInstance()有三个参数,具体的解释如下:
package com.nrsc.pattern.dynamicproxy;
import com.nrsc.pattern.factory.PenFactory;
import com.nrsc.pattern.factory.PencilFactory;
import java.lang.reflect.Proxy;public class Main1 {public static void main(String[] args) {//新建铅笔工厂类PenFactory pencilFactory = new PencilFactory();//新建InvocationHandler的具体实现类 --- 对该类的解释请看下面的注释NrscInvocationHandler nrscInvocationHandler = new NrscInvocationHandler();nrscInvocationHandler.setTarget(pencilFactory);/**** 第一个参数为类加载器 ---- 传入的是【被代理类】所用的类加载器,因为这样生成的代理类就可以代理【被代理类】了** 第二个参数为【被代理类】实现的所有接口* ---Proxy.newProxyInstance(...)方法的返回值类型为Object,而Object对象只有equals()、hashCode()、* ----toString()等几个方法,代理类如果想代理【被代理类】的方法,肯定就得强转为和【被代理类】一样的类型* ---凭什么可以随便强转呢??? 就是因为Proxy生成的代理对象其实也实现了和【被代理对象】一样的接口,* ---而这些接口就是通过该参数传递给Proxy的* ---其实这也是为什么JDK进行动态代理,必须要求【被代理类】必须至少实现一个接口的原因** 第三个参数为InvocationHandler的具体实现类:* ----这个对象里封装了真正的【被代理对象】,以及对【被代理对象】方法的具体调用,当然在方法调用前后,我们可以对其进行增强* ----Proxy在创建具体的代理对象的时候既然需要这个对象,那它创建出来的具体的代理对像肯定会使用这个对象,怎么使用呢???* ----其实很简单,就是当你拿到代理对象时(这个代理对象肯定是已经强转为具体类型的对象了),* ----可以直接按照【代理的那个对象】原有的方法调用方式进行法调用,但是呢,我既然是一个代理对象,* ----肯定不会让你直接调用【代理的那个对象】的方法,而是调用创建代理对象时传入的InvocationHandler对象的invoke(...)方法。*/PenFactory penFactoryProxy = (PenFactory) Proxy.newProxyInstance(pencilFactory.getClass().getClassLoader(),pencilFactory.getClass().getInterfaces(),nrscInvocationHandler);/***** 可以按照PenFactory的方法调用方式进行方法调用,但是真正走的是nrscInvocationHandler中的invoke(...)方法*/penFactoryProxy.salePens("蓝");}
}
5.2 InvocationHandler — 封装被代理对象、调用被代理对象的方法并对方法进行增强
InvocationHandler其实是一个接口,它里面只有一个抽象方法invoke,其源码如下:
package java.lang.reflect;
public interface InvocationHandler {/***** 第一个参数: 其实就是由Proxy生成的具体的代理对象,这个参数一般不会用到---》因为假设A代理对象调用m1方法时,* 不会直接进入m1方法,而是会进入invoke(..)方法;进入到invoke(...)方法后,拿到的proxy其实就是A代理对象,* 如果你想用proxy进行其他方法的调用,它还是不会直接进入到其他方法,而是会又进入到invoke(...)方法。。。从而形成死循环* * 第二个参数:其实就是代理对象调用的被代理对象的具体方法,但是如果想调用到具体的被代理对象,肯定还需要一个被代理对象* 【这里要参考静态代理或装饰器模式去理解 --- 真正干活的是被代理对象】* * 第三个参数:其实就是代理对象调用的具体方法的参数 */public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}
从上面的注释可以知道,要想调用到具体的被代理对象的方法,InvocationHandler 对象里必须得包含【被代理对象】,这里写了一个具体的实现如下:
package com.nrsc.pattern.dynamicproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class NrscInvocationHandler implements InvocationHandler {/**** 被代理的对象 --- 其实该对象是真正提供服务的对象* --- 如同装饰器模式一样,这个类并不真正干活,它其实主要做的任务是卖东西之前的市场调查和卖东西之后的市场评估* --- 真正的卖东西这个事,它其实是调用的被代理对象的服务*/private Object target;public void setTarget(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {beforeEnhance();//通过反射调用被代理对象的具体方法//【联系静态代理模式和装饰器模式】--这里调用的方法肯定是被代理对象的方法 -- 即实际的工厂里的方法Object res = method.invoke(target, args);afterEnhance();return res;}private void beforeEnhance() {System.out.println("卖东西之前对市场进行调研。。。。");}private void afterEnhance() {System.out.println("卖了一段时间之后,对市场进行总结评估");}
}
5.3 另一种代码书写方式 — 代理对象的生成直接写在InvocationHandler对象里
其实搞明白了动态代理模式的使用原理之后,完全可以将代理对象的生成放在InvocationHandler对象里,代码如下:
package com.nrsc.pattern.dynamicproxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class DynamicProxyCompany implements InvocationHandler {private Object target;public void setTarget(Object target) {this.target = target;}/**** 这里的this就是指的当前的InvocationHandler* @return*/public Object getProxyInstance() {return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {beforeEnhance();//通过反射调用被代理对象的具体方法//【联系静态代理模式和装饰器模式】--这里调用的方法肯定是被代理对象的方法 -- 即实际的工厂里的方法Object res = method.invoke(target, args);afterEnhance();return res;}private void beforeEnhance() {System.out.println("卖东西之前对市场进行调研。。。。");}private void afterEnhance() {System.out.println("卖了一段时间之后,对市场进行总结评估");}
}
这样代理类的生成、调用就可以变成下面的样子了:
package com.nrsc.pattern.dynamicproxy;import com.nrsc.pattern.factory.PenFactory;
import com.nrsc.pattern.factory.PencilFactory;
import com.nrsc.pattern.factory.ToyFactory;
import com.nrsc.pattern.factory.YellowDuckToyFactory;
public class Main2 {public static void main(String[] args) {//新建代理商公司DynamicProxyCompany dynamicProxyCompany = new DynamicProxyCompany();//指定代理商公司具体要代理的工厂dynamicProxyCompany.setTarget(new PencilFactory());//由代理商公司找到具体的笔代理人 --- 即生成具体的代理对象 // --- 将代理对象强转为PenFactory,因为如果不强转的话生成的代理类将是一个Object对象,// --- 而Object对象只有equals()、hashCode()、toString()等几个方法,无法调用卖笔的方法// --- 这应该就是为什么JDK的动态代理为什么需要被代理的对象必须至少实现一个接口的原因PenFactory pencilFactoryProxy = (PenFactory) dynamicProxyCompany.getProxyInstance();//具体的笔代理人卖笔pencilFactoryProxy.salePens("红");//指定代理商公司具体要代理的工厂dynamicProxyCompany.setTarget(new YellowDuckToyFactory());//由代理商公司找到具体的玩具代理人ToyFactory toyFactory = (ToyFactory) dynamicProxyCompany.getProxyInstance();//具体的玩具代理人卖玩具toyFactory.saleToys(10000);}
}
6 总结
共同点:
无论是静态代理模式、装饰器模式还是动态代理模式他们都需要被代理的类(当然对于装饰器模式也可以说是被装饰的类)至少实现一个接口
不同点:
静态代理模式:
会在代理对象里实例化好被代理的对象 —》 这种模式更适合于对某个n久不变的逻辑进行代理。
装饰器模式:
并不会在代理对象(或者说叫装饰器对象)里直接实例化好具体要代理(或者说要装饰)的对象,而是将这个对象作为代理对象的一个属性,需要调用者来具体指定到底代理哪个具体的类 —》这种模式比较适合对某一个类型的对象进行代理(或者说装饰)如上篇文章《【Mybatis源码探索】 — Mybatis查询过程核心源码解读 — 先聊聊selectOne方法》中提到的Executor和StatementHandler,以及本文说的笔工厂。
动态代理模式:
动态代理模式我觉得其实更像是装饰器模式的加强版。
(1)它的代理对象并不是确定的,而是通过每次调用Proxy.newProxyInstance(…)方法生成的,这和其他两种模式都不一样;
(2) InvocationHandler对像里封装了被代理对象以及对被代理对象的方法的调用和方法增强
(3)代理对象进行方法调用时可以按照被代理对象的方法调用方式进行调用,但是并不会直接调用到被代理对象,而是调用到 InvocationHandler对像里的invoke(…)方法
动态代理的适用情况比较多:比如说我提到的代理玩具制造厂、文具制造厂、电子产品制造厂的综合代理类;比如说前面讲过的spring事务;之后要继续探索的Mybatis mapper调用方式底层原理等。
7 扩展 — Proxy.newProxyInstance底层原理简析
【设计模式】--- 装饰器模式、静态代理模式和动态代理模式相关推荐
- Python设计模式-装饰器模式
Python设计模式-装饰器模式 代码基于3.5.2,代码如下; #coding:utf-8 #装饰器模式class Beverage():name = ""price = 0.0 ...
- Go 设计模式 - 装饰器模式
装饰模式使用对象组合的方式动态改变或增加对象行为.Go语言借助于匿名组合和非入侵式接口可以很方便实现装饰模式.使用匿名组合,在装饰器中不必显式定义转调原对象方法. 设计模式 装饰器模式 装饰器模式主要 ...
- Spring设计模式(装饰器模式)
Spring设计模式(装饰器模式) 模式的定义: 装饰者模式定义: 动态地为一个对象添加一些额外的职责,若要扩展一个对象的功能,装饰者提供了比继承更有弹性的替代方案. 模式的结构图 : 模式包含角 ...
- cglib动态代理jar包_代理模式详解:静态代理+JDK/CGLIB 动态代理实战
1. 代理模式 代理模式是一种比较好的理解的设计模式.简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标 ...
- 代理模式(静态代理、jdk动态代理、CGLib动态代理)
目录 1.什么是代理模式? 2.静态代理 1.案例 2.优化案例 3.静态代理瓶颈 3.动态代理 1.什么是动态代理? 2.jdk动态代理 1.动态代理的工具类 匿名内部类简介 2.jdk动态代理实现 ...
- Spring : 静态代理模式和JDK、CGLIB动态代理
1.美图 2.概述 为了更好的分析分析Spring的另一个核心功能AOP,需要先温习一下动态代理的知识,如果对java的动态代理无所了解的话,那么对AOP源码的分析就无从谈起.代理模式可分为静态代理和 ...
- 轻松学,Java 中的代理模式(proxy)及动态代理
我们先来分析代理这个词. 代理 代理是英文 Proxy 翻译过来的.我们在生活中见到过的代理,大概最常见的就是朋友圈中卖面膜的同学了. 她们从厂家拿货,然后在朋友圈中宣传,然后卖给熟人. 按理说,顾客 ...
- 两万字吐血总结,代理模式及手写实现动态代理(aop原理,基于jdk动态代理)
代理模式及手写实现动态代理 一.代理模式 1. 定义 2. 示例 (1)静态代理 (2)动态代理 3. 通用类图 4. 代理模式的优点 二.jdk动态代理实现原理 1. jdk动态代理源码分析(通过该 ...
- 结构设计模式 - 装饰器设计模式
结构设计模式 - 装饰设计模式 装饰器设计模式用于在运行时修改对象的功能.同时,同一类的其他实例不会受此影响,因此单个对象将获得已修改的行为.装饰器设计模式是结构设计模式之一(如Adapter Pat ...
- 代理详解 静态代理+JDK/CGLIB 动态代理实战
1. 代理模式 代理模式是一种比较好理解的设计模式.简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对 ...
最新文章
- ffmpeg linux安装_ffmpeg命令中文手册
- PHPCMS V9 框架代码分析(入口程序)
- php7mysql查询_php7连接MySQL实现简易查询程序的技巧
- 根据字符串选择类并完成类的初始化--方法一
- 新安装和已安装nginx如何添加未编译安装模块/补丁
- js aes加密_nodejs中使用Crypto-JS对图片进行加解密
- row number函数_Hive排名函数ROW_NUMBER,RANK 和 DENSE_RANK的区别
- oracle提升,Oracle特权提升
- php 生成xls解决乱码,怎么解决php导出excel文件乱码问题
- 卡巴斯基最新Key下载 生成卡巴斯基Key的工具下载 卡巴斯基Key下载
- 抖音上热门的小技巧,不看后悔
- 基本类型包装及数学工具类的使用
- mysql中explain是什么_MySQL中EXPLAIN的解释_MySQL
- 【学习】Congestion Control
- java栅格化,UI设计要不要用栅格化布局?
- Java程序员月薪达到三万,需要技术水平达到什么程度?(文末送书)
- python print字体颜色 print背景颜色
- DirectX10+MSVC
- poi从3.6 升级为新版本5.2.2,导致 原先的代码 报错,替换方案
- 【HTML、CSS练习题1】
热门文章
- Mac安装Drozer apk安全测试框架踩坑记录, ‘openssl/opensslv.h‘ file not found 和implicit declaration of function‘xx‘
- 什么是BS?BS和CS模式的区别是什么?
- dw怎么把html页面居中,dw怎么把所有内容居中
- js格式化日期为各种国际格式
- 基于RFID定位技术的资产管理--新导智能
- 痞子衡嵌入式:嵌入式里通用微秒(microseconds)计时函数框架设计与实现
- ipconfig、ping命令、其他常用网络命令
- AspNetPager.dll 用法
- CF K. City
- 单片机控制舵机matlab仿真,单片机舵机控制程序教程及proteus仿真原理图