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

本文介绍了Java动态代理的概念,包括代理的作用、优点,以及如何创建和执行代理对象。通过歌手经纪人和业务功能性能统计的例子展示了动态代理的应用,并提及了Spring框架中动态代理(AOP)在事务管理中的重要性。此外,还提到了基于子类的动态代理(CGLIB)作为接口代理的补充,允许对未实现接口的类进行代理。
摘要由CSDN通过智能技术生成


一、动态代理概述

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;
    }

    @Override
    public void jump() {
        System.out.println(name + "开始跳舞");
    }

    @Override
    public 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() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("收首款");
                        // proxy 代理对象的引用
                        // method 正在调用的方法对象
                        // args 代表这个方法的参数
                        Object rs = method.invoke(obj, args); // 采用反射机制,如果没有返回值,则返回null
                        System.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{
    @Override
    public 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;
    }

    @Override
    public void delete(Integer id) {
        try {
            System.out.println("正在删除数据中");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public 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() {
                    @Override
                    public 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 :当前执行方法的代理对象
             */
            @Override
            public 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();
    }
}

输出结果:

在这里插入图片描述

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

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xiu Yan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值