> 文章列表 > 软件设计模式 | 动态代理模式

软件设计模式 | 动态代理模式

软件设计模式 | 动态代理模式

文章目录

  • 一、动态代理概述
    • 1.1 代理的概述和作用
    • 1.2 动态代理的优点
    • 1.3 代理对象的创建
    • 1.4 代理对象调用方法的执行流程
  • 二、动态代理举例
    • 2.1 歌手经纪人
    • 2.2 业务功能的性能统计
    • 2.3 动态代理在 Spring 框架中的应用
  • 三、基于子类的动态代理

一、动态代理概述

1.1 代理的概述和作用

  • 什么是代理?
    代理指某些场景下对象会找一个代理对象,来辅助自己完成一些工作。如:歌星(经济人),买房的人(房产中介)

  • 代理主要干什么工作,是如何工作的?
    是对对象的行为做一些辅助的操作。

  • 代理举例:
    歌手刚出道时,有人花钱让他唱歌,承诺先付首款再付尾款。那么,歌手的整个工作的流程是:收首款、唱歌、收尾款。
    歌手成名以后,业务越来越多,开始雇佣了经纪人。经纪人主要负责:收首款、收尾款以及调用歌手去唱歌,歌手只负责唱歌。
    在这里插入图片描述


1.2 动态代理的优点

  • 非常的灵活,支持任意接口类型的实现类对象做代理,也可以直接为接本身做代理。
  • 可以为被代理对象的所有方法做代理。
  • 不仅简化了编程工作、提高了软件系统的可扩展性,同时也提高了开发效率。
  • 可以在不改变方法源码的情况下,实现对方法功能的增强
    方法增强的理解:歌手原本的工作是唱歌,唱歌前后的收首款和尾款方法就相当于对原本的方法的增强。

1.3 代理对象的创建

  • java 中代理的代表类是: java.lang.reflect.Proxy

  • Proxy 提供了一个静态方法,用于为对象产生一个代理对象返回。

    软件设计模式 | 动态代理模式
    参数二理解:因为客户是通过代理对象去调用歌手的唱歌方法,因此代理类需要接口的列表


1.4 代理对象调用方法的执行流程

  1. 先走向代理
  2. 代理可以为方法额外做一些辅助工作
  3. 开发真正触发对象的方法的执行
  4. 回到代理中,由代理负责返回结果给方法的调用者

二、动态代理举例

2.1 歌手经纪人

项目包结构:

软件设计模式 | 动态代理模式

技能接口:

public interface Skill {void jump();void sing();
}

明星类:

public class Star implements Skill{private String name;public Star(String name) {this.name = name;}@Overridepublic void jump() {System.out.println(name + "开始跳舞");}@Overridepublic void sing() {System.out.println(name + "开始唱歌");}
}

明星代理类:

public class StarAgentProxy {/*** 设计一个方法来返回 一个明星对象 的 代理对象*      参数一:定义代理类的类加载器*      参数二:代理类要实现的接口列表*      参数三:将方法调用分派到的处理程序*/public static Skill getProxy(Star obj){// 为张三这个对象,生成代理对象return (Skill) Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("收首款");// proxy 代理对象的引用// method 正在调用的方法对象// args 代表这个方法的参数Object rs = method.invoke(obj, args); // 采用反射机制,如果没有返回值,则返回nullSystem.out.println("收尾款");return rs;}});}
}

客户端模拟:

public class Test {/*** 理解动态代理*/public static void main(String[] args) {// 1. 创建一个对象Star star = new Star("张三");// 为张三对象,生成一个代理对象(经纪人)Skill proxy = StarAgentProxy.getProxy(star);proxy.jump();System.out.println("--------");proxy.sing();}
}

输出结果:

在这里插入图片描述


2.2 业务功能的性能统计

需求: 模拟某企业用户管理业务,需包含用户登录、删除、查询功能,并要统计每个功能的耗时

项目包结构:

软件设计模式 | 动态代理模式

用户业务层接口:

public interface UserService {String login(String loginName, String password);void delete(Integer id);void selectUsers();
}

用户业务层实现类:

public class UserServiceImpl implements UserService{@Overridepublic String login(String loginName, String password) {String rs = "登录名称或者密码错误!";if("admin".equals(loginName) && "123456".equals(password)){rs = "登录成功";}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}return rs;}@Overridepublic void delete(Integer id) {try {System.out.println("正在删除数据中");Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}@Overridepublic void selectUsers() {System.out.println("查询了100个用户数据!");try {Thread.sleep(3000);} catch (Exception e) {e.printStackTrace();}}
}

代理对象工具类:

public class ProxyUtil {/*** 通过静态方法为用户业务对象返回代理对象*/public static UserService getProxy(UserService obj){return (UserService) Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long startTime = System.currentTimeMillis();// 真正触发对象的行为执行Object rs = method.invoke(obj, args);long endTime = System.currentTimeMillis();System.out.println(method.getName() + "方法耗时:" + (endTime-startTime)/1000.0 + "s");return rs;}});}
}

客户端模拟:

public class Test {public static void main(String[] args) {UserService proxy = ProxyUtil.getProxy(new UserServiceImpl());proxy.login("admin", "123456");proxy.delete(1);proxy.selectUsers();}
}

输出结果:

在这里插入图片描述


2.3 动态代理在 Spring 框架中的应用

转账方法的事务问题回顾:

参考:Spring 从入门到精通系列 09 —— 转账方法的事务问题与动态代理

转账业务流程中,如果中间的某一部分业务出现异常,那么会导致异常后的事务不会执行,从而引发账户出错的严重情况。

在这里插入图片描述
问题的关键在于:
整个业务方法一共获取了四次数据库连接对象,有四个业务需要处理。当前事务完成后,会直接提交事务。那么当某个事务出现异常时,只对他自己的事务进行回滚,对其他的事务不回滚。

当时提出的解决方案是:
由于整体的业务属于一个线程,那么通过使用 ThreadLocal 对象把 Connection 连接对象和当前线程绑定,即使一个线程中只有一个 Connection 对象,而不是原本的四个。(要么都发生,要么都不发生)

更新事务控制后,具体实现如下:

在这里插入图片描述
代码变的很复杂,而且每个方法都需要加上:开启事务、提交事务…等事务处理。因此,可利用动态代理的技术进行处理。
Spring 的 AOP 的实现思想就是动态代理,即 在不修改源码的基础上对已有方法进行增强。


三、基于子类的动态代理

上文讲述的代理模式属于 基于接口的动态代理模式,当其不实现任何接口时,该动态代理对象不能得到。
但是 基于子类的动态代理 可以实现不用实现接口的情况下实现代理。

导入依赖:

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>2.1_3</version>
</dependency>

明星类:

public class Star{private String name;public Star() {}public Star(String name) {this.name = name;}public void jump() {System.out.println(name + "开始跳舞");}public void sing() {System.out.println(name + "开始唱歌");}
}

明星代理类:

public class ProxyUtil {/*** 通过静态方法为用户业务对象返回代理对象*/public static Star getProxy(Star obj){return (Star) Enhancer.create(obj.getClass(), new MethodInterceptor() {/*** 执行被代理对象的任何方法都会经过该方法* @param proxy* @param method* @param args*    以上三个参数和基于接口的动态代理中invoke方法的参数是一样的* @param methodProxy :当前执行方法的代理对象*/@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("收首款");Object rs = method.invoke(obj, args);System.out.println("收尾款");return rs;}});}
}

客户端模拟:

public class Test {public static void main(String[] args) {final Star star = new Star("张三");Star proxy = ProxyUtil.getProxy(star);proxy.jump();System.out.println("--------");proxy.sing();}
}

输出结果:

在这里插入图片描述

注:基于子类的动态代理,被代理类必须实现无参构造(当被代理类中有参构造函数时,需重写无参构造),否则报以下异常。

软件设计模式 | 动态代理模式