静态代理与动态代理模式详解(优缺点分析,实例分析,读源码必备)
1、代理模式
(1)概念
- 代理就是帮别人做事情,如:工厂的中介,中介负责为工厂招收工人,那么中介就是工厂的代理;客户通过商家购买东西,商家向厂家购买货物,商家就是工厂的代理
- 在开发中存在a类需要调用c类的方法,完成某一个功能,但是c禁止a调用。这时,可以在a和c之间创建一个b类代理,a类访问b类,b类访问c类。例如:登录的时候需要进行短信验证,这个时候代理就是中国移动的子公司来完成短信的发送功能
- 代理模式就是为其他对象提供一种代理来控制这个对象的访问,在某些情况下一个对象不适合或不能直接引用另一个对象,而代理对象可以在客户类和目标对象直接起到中介的作用
- 功能增强:其中目标对象实现真正的功能,但是代理对象可以对目标对象的功能做进一步的扩充
(2)设计模式
设计模式代表了最佳的实践,通常被有经验的面向对象的软件开发者所采用的,它是开发中面临的一般问题的解决方案,这些解决方案是众多的软件开发人员经过相当长的实践的经验和错误总结出来的
(3)举例
如果设计一个类该类中含有加减乘除四个方法,现在需要给每一个方法添加测试方法执行时间的代码,如果不使用代理的话,就需要对每一个方法进行修改,需要修改多次。违背了开闭原则(OCP,对扩展开放,修改关闭)和单一职责(SRP)
(4)作用
功能增强:在原有的功能上增加额外的功能
控制访问:代理类不让你访问目标类
(5)实现代理的方式
静态代理:代理类是自己手动创建的,所需要代理的目标类是确定的,实现简单容易理解
2、静态代理
- 案例一
是在程序运行前就已经存在代理类的字节码文件,静态代理通常是对原有业务逻辑的扩充,通过让代理类持有真实对象,在代理类的源代码中调用被代理类方法来添加我们需要的业务逻辑。例如:买车不去工厂而是去4S店。
(1)创建一个接口:
interface Animal {public abstract void show(); }
(2)代理类:
public class Fish implements Animal {Sheep sheep=new Sheep();@Overridepublic void show() {sheep.show();System.out.println("我爱游泳!");} }
创建被代理类的对象,在代理类的方法中调用被代理类的方法,在这个过程中可以实现对被代理类的功能的扩充。
(3)被代理类:
public class Sheep implements Animal{@Overridepublic void show() {System.out.println("我爱吃青草");} }
(4)测试类:
public class Test {public static void main(String [] args){Fish fish=new Fish();fish.show();} }
测试结果:
Sheep最爱吃青草,在他被fish类代理之后还学会了一项新的技能:游泳
(5)静态代理的缺点
如果有多个类需要代理,那么就需要创建多个代理类分别代理目标对象,工作量较大,不利于维护。
- 案例二
(1)创建一个接口
public interface UsbSell {float sell(float price); }
这里面是厂家和商家都要完成的功能
(2)创建工厂类
//厂家,不接受用户的单独购买,需要商家 public class UsbFactory implements UsbSell {@Overridepublic float sell(float price) {System.out.println("目标类");return 70;//厂家的U盘价格} }
在工程类中定义的是U盘的出厂价,但是改价格是不能被普通的消费者使用的,只能是商家使用
(3)创建商家类(代理类)
淘宝类:
public class TaoBao implements UsbSell {//声明商家代理的是哪一个厂家private UsbSell factory=new UsbFactory();@Overridepublic float sell(float price) {//调用目标方法,增强功能,增加价格,优惠券float p=factory.sell(price);float p1=p+25;System.out.println("返回5元优惠券");return p1;} }
微商类:
public class WeiShang implements UsbSell {private UsbSell factory=new UsbFactory();@Overridepublic float sell(float price) {//调用目标方法,增强功能float p = factory.sell(price);float p2=p+1;return p2;} }
这两个类都实现了与工厂类相同的接口,增强了工程类(目标类)的方法
(4)创建测试类(普通消费者)
public class ShopMain {public static void main(String[] args) {TaoBao taoBao=new TaoBao();float price=taoBao.sell(100);System.out.println("淘宝的销售价:"+price);} }
目标类 返回5元优惠券 淘宝的销售价:95.0
(5)优点
- 实现简单
- 容易理解
(6)缺点
当目标类增多了,代理类也需要增加(例如:上例中创建了一个工厂类,那么该类只能代表一个工厂,当建立了其它品牌的工厂后,还需要为该工厂创建代理类)
当接口的方法增加或修改的时候,很多类都需要修改。因为,目标类和代理类都实现了相同的接口
3、动态代理(JDK代理,接口代理)
- 案例一
(1)好处
动态代理是利用的反射机制动态地生成代理的对象,我们不需要知道谁代理谁。代理类的那部分代码被固定下来了,不会因为业务的增加而逐渐庞大。
可以实现AOP编程
解耦
(2)创建一个接口
interface Animal {public abstract void show(); }
(3)创建代理类
实现动态代理需要将要扩展的功能写在InvocationHandler实现类里
使用newProxyInstance方法,该方法需要接收三个参数
代理对象不需要实现接口,但是目标对象一定要实现接口
public class Fish implements Animal {@Overridepublic void show() {Animal objectProxy= (Animal) Proxy.newProxyInstance(//创建接口实例Animal.class.getClassLoader(),//用目标对象有相同的类加载器,动态代理类运行时创建,将类加载到内存(反射)new Class[]{Animal.class},//被代理的类所实现的接口(可以是多个)new InvocationHandler() {//绑定代理类的方法(我们自己写的,代理类要完成的功能)@Override//提供invoke方法,代理类的每一个方法执行时,都将调用一次invokepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("我在前!!");method.invoke(new Sheep(),args);//执行目标对象的方法System.out.println("我在后!!");return null;}//proxy:代理后的实例对象//method:对象被调用的方法//args:调用时的参数});objectProxy.show();} }
(4)被代理类
public class Sheep implements Animal{@Overridepublic void show() {System.out.println("我爱吃青草");} }
(5)测试类
public class Test {public static void main(String [] args){Fish fish=new Fish();fish.show();} }
测试结果:
- Method的使用
(1)创建接口
public interface HelloService {public void sayHello(String name); }
(2)接口的实现类
public class HelloServiceImpl implements HelloService {public void sayHello(String name){System.out.println("hello"+name);} }
(3)测试类
普通方式调用sayHello方法:
public class Test {public static void main(String[] args) {HelloService helloService=new HelloServiceImpl();helloService.sayHello("zhai");} }
hellozhai
使用反射执行sayHello方法:
public class Test {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {//使用反射机制执行sayHello方法,核心是Method类中的方法HelloService target=new HelloServiceImpl();//获取sayHello名称对于Method的对象Method method=HelloService.class.getMethod("sayHello",String.class);//通过Method可以执行方法的调用//invoke是Method类中的一个方法,表示添加方法的调用,参数1:表示对象,要执行这个对象的方法//参数2:方法执行的时候的参数值 参数3:方法要执行的时候的返回值//表示执行target对象的sayHello方法,参数是zhai,method代表的是执行的方法Object ret=method.invoke(target,"zhai");} }
hellozhai
(4)为接口添加一个实现类:
public class HelloServiceImpl2 implements HelloService{public void sayHello(String name){System.out.println("nihao"+name);} }
测试类:
public class Test {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {//使用反射机制执行sayHello方法,核心是Method类中的方法HelloService target=new HelloServiceImpl();//获取sayHello名称对于Method的对象Method method=HelloService.class.getMethod("sayHello",String.class);//通过Method可以执行方法的调用//invoke是Method类中的一个方法,表示添加方法的调用,参数1:表示对象,要执行这个对象的方法//参数2:方法执行的时候的参数值 参数3:方法要执行的时候的返回值//表示执行target对象的sayHello方法,参数是zhai,method代表的是执行的方法Object ret=method.invoke(target,"zhai");HelloService target2= new HelloServiceImpl2();Object ret2=method.invoke(target2,"zhai");} }
hellozhai nihaozhai
也就是说method代表的是sayHello方法,也就是目标类的方法
- JDK动态代理的实现
invoke():表示代理对象要执行的功能代码,你的代理类要完成的功能就写在invoke()方法中
(1)代理类要完成的功能
调用目标方法,执行目标方法的功能
增强功能
(2)invoke方法
invoke(Object proxy, Method method, Object[] args)
method:目标类中的方法,jdk负责提供method对象
Object[] args:目标类中的参数
Object proxy:jdk创建的代理对象,无需赋值
(3)使用过程
- InvocationHandler接口:表示代理要干什么(定义目标类要完成的功能)
- 创建目标类实现接口
- 创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能
invoke方法:重写invoke方法,把原来静态代理中代理类要完成的功能写在方法内
method.invoke()是用来执行目标方法的
- 使用Proxy类的静态方法,来创建代理对象,并把返回值转换为接口类型
核心的对象,创建代理对象,之前的对象都是new类的构造方法,现在我们使用的是Proxy类的方法,代替new的使用
方法newProxyInstance(),作用是创建代理对象,需要三个参数
- 动态代理案例二
(1)创建接口
public interface UsbSell {float sell(float price); }
(2)创建U盘的工厂类
public class UsbFactory implements UsbSell {@Overridepublic float sell(float price) {System.out.println("目标类");return 70f;//厂家的U盘价格} }
(3)创建InvocationHandler 接口的实现类
//必须实现InvocationHandler接口,完成代理类的功能(调用目标方法、功能增强) public class MySellHandler implements InvocationHandler {private Object target=null;//动态代理的目标对象是活动的,需要传入进来,传进来的是谁就给谁创建代理public MySellHandler(Object target){this.target=target;}//args代表传进来的参数(100)@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object res=null;res=method.invoke(target,args);if(res!=null){Float price=(Float)res;price=price+25;res=price;}System.out.println("淘宝商家返回5元优惠券");return res;} }
(4)测试类
public class Test {public static void main(String[] args) {//创建目标对象UsbSell usbFactory=new UsbFactory();//创建invocationHandler对象InvocationHandler invocationHandler=new MySellHandler(usbFactory);//创建代理对象UsbSell proxy= (UsbSell) Proxy.newProxyInstance(usbFactory.getClass().getClassLoader(),usbFactory.getClass().getInterfaces(),invocationHandler);System.out.println(proxy.sell(100));} }
(5)添加接口的方法
public interface UsbSell {float sell(float price);void hello(); }
public class UsbFactory implements UsbSell {@Overridepublic float sell(float price) {System.out.println("目标类");return 70f;//厂家的U盘价格}@Overridepublic void hello() {System.out.println("hello");} }
只需要修改接口的方法和目标类的方法,用Proxy对象调用即可
在不修改工厂类和接口的情况下可以增加目标类(工厂类)的方法的功能
jdk的动态代理必须有接口,目标类一定要实现接口,没有接口的时候使用cglib动态代理
4、cglib代理(cglib字节码增强)
(1)概念:
需要导入jar包:核心包和依赖包(spring_core.jar已经集成了这两个包,因此,导入此包即可)
子类是在调用的时候才生成的
使用目标对象的子类的方式实现的代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展,能够在运行时动态生成字节码,可以解决目标对象没有实现接口的问题
缺点:被final或static修饰的类不能用cglib代理,因为它们不会被拦截,不会执行目标对象的额外业务方法
- 总结
(1)优点
在静态代理中的目标类很多的时候,可以使用动态代理,避免静态代理的缺点
- 动态代理中目标类使用的即使很多,代理类的数量可以很少
- 修改接口的方法的时候不会影响到代理类
(2)概念
在程序执行的过程中,使用jdk的反射机制,创建代理类对象并动态地指定要代理的目标类。也就是说动态代理是一种创建java对象的能力,使得我们不用创建淘宝类或微商类,就能创建代理类对象
(3)作用
控制访问:在代理中,控制是否可以调用目标对象的方法
功能增强:可以在完成目标对象的调用时,附加一些额外的功能
代理方式:
静态代理:代理类是手工创建的,目标对象是规定的
动态代理:使用反射机制,在程序执行的时候创建代理类对象,不用创建代理类的类文件,代理类的目标类是可以设置的
(4)实现方式
jdk动态代理:
使用java反射包中的类和接口实现动态代理的功能,反射包是java.lang.reflect,里面有三个类:InvocationHandler、Method、Proxy
cglib动态代理:
cglib是第三方的工具类
原理是继承,通过继承目标类创建它的子类,在子类中重写父类中的方法,实现功能的修改
要求目标类不能是final的,方法也不能是final的
对于没有接口的类,创建动态代理就要使用cglib
静态代理与动态代理模式详解(优缺点分析,实例分析,读源码必备)相关推荐
- VB静态调用与动态调用dll详解
[[请注意]]:在以下语法格式中,请注意 [函数名] 的[大小写]!!! 静态与动态比较: 静态调用简单,动态调用麻烦:静态调用占用资源多,动态调用占用资源少:正所谓鱼和熊掌不可兼得. 静态调用定义: ...
- 组合模式详解附有代码案例分析(包含透明组合模式、安全组合模式的代码示例)
组合模式 一.组合模式的概念和角色 (一).组合模式的概念 (二).组合模式的角色 二.组合模式的应用场景 三.透明组合模式的代码示例 四.安全组合模式的代码示例 五.组合模式的优缺点 (一).优点 ...
- 模板方法模式详解附有代码案例分析(包含模板方法模式重构JDBC操作业务代码示例)
模板方法模式 一.模板方法模式的概念和角色 (一).模板方法模式的概念 (二).模板方法模式的角色 二.模板方法模式的应用场景 三. 模板方法模式的代码示例 四.模板方法模式重构JDBC操作业务 五. ...
- BMP180气压传感器详解与示例(STM32 附带源码)
BMP180气压传感器详解与示例(STM32 附带源码) 简介 工作模式 校准数值 测试流程 第一步:微处理器读取校准数值 第二步:读取温度.气压初始值 第三步:计算温度.气压 第四步:计算海拔高度 ...
- Java API源码在哪里找_详解查看JAVA API及JAVA源码的方法
在java的日常学习中,我们有时候会需要看java的api说明,或者是查看java的源码,使我们更好的了解java,接下来我就来说说如何查看java的api以及java源码 对于java的api,一般 ...
- vue 源码详解(零):Vue 源码流程图
vue 源码详解(零):Vue 源码流程图 最近在研究 Vue 的源码, 整理博客, 结果想到的.看到的内容实在是太多了, 不知道从何写起, 故整理了一个大致的流程图,根据这个顺序进行一一整理. 为了 ...
- Spring配置详解,Spring配置元信息详解,Spring配置大全及源码分析
文章目录 一.Spring都可以配置哪些元信息 二.Spring Bean 配置元信息 1.GenericBeanDefinition 2.RootBeanDefinition 3.Annotated ...
- 【VB技巧】VB静态调用与动态调用dll详解
[[请注意]]:在以下语法格式中,请注意 [函数名] 的[大小写]!!!静态与动态比较:静态调用简单,动态调用麻烦:静态调用占用资源多,动态调用占用资源少:正所谓鱼和熊掌不可兼得.静态调用定义:就是常 ...
- linux 驱动编译静态,Linux驱动静态编译和动态编译方法详解
内核源码树的目录下都有两个文档Kconfig和Makefile.分布到各目录的Kconfig构成了一个分布式的内核配置数据库,每个Kconfig分别描述了所属目录源文档相关的内核配置菜单.在内核配置m ...
最新文章
- MariaDB Spider分库分表引擎调研
- 读书:儒林外史第一回
- pmp学习资料_南昌如何选择PMP报考条件-海外人才交流协会
- Dubbo服务引用原理
- 驱动备份工具哪个好_大庆seo排名优化推广公司工具哪个好
- leetcode 424. 替换后的最长重复字符(滑动窗口)
- ACM技巧 - O(1)快速乘(玄学) 总结
- linux系统监控和进程管理
- 《Linux内核设计与实现》读书笔记(2)--- 进程管理
- 力扣 有多少小于当前数字的数字
- ueditor关闭元素
- 普元云计算-你适合微服务么:实施微服务的4个先决条件和重点工作
- 采购订单暂存和持有相关的问题?
- halcon修改程序框字体大小
- Oracle之学习if条件选择语句
- 解决Linux连不上外国软件源或者软件源失效
- 经历多次重写,苹果平台最强科学计算器PCalc背后的故事
- antd踩坑记录之upload上传
- C语言变量的存储类别和生存期
- 手把手教您使用北美打折返利网