从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 新闻合订本
    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 新闻合订本
    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

    • Spring 介绍
    • 程序中的耦合
    • IoC 的概念和作用
    • Spring 中的依赖注入
    • 基于注解的 IoC
    • 使用基于 XML 的 IoC 完成单表的 CRUD
    • 使用基于注解的 IoC 完成单表的 CRUD
    • IoC 的纯注解配置
    • Spring 整合 Junit
    • 事务问题
    • 代理模式
      • 什么是代理
      • 在程序中模拟代理
      • 动态代理的概念
      • 基于接口的动态代理
      • 基于子类的动态代理
      • 使用动态代理增强 service
      • 修改 service 类
      • 配置 IoC
      • 修改测试类
      • 测试
      • 总结
    • AOP 的概念和入门
    • 基于注解的 AOP
    • Spring 的 JdbcTemplate
    • JdBCDaoSupport
    • 基于 XML 的 AOP 实现事务控制
    • 基于注解的 AOP 实现事务控制
    • Spring 的事务控制
    • 基于 XML 的声明式事务控制
    • 基于注解的声明式事务控制
    • 纯注解实现事务控制
    • Spring 编程式事务控制
    • Spring5 新特性
  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java
  • Spring
2023-05-08
目录

代理模式

# 75.代理模式

本文我们来回顾下代理模式,这对于我们理解 AOP 很有帮助。 ‍

# 什么是代理

举个例子,假设有一个负责生产电脑的厂家,如果生产了 1w 台电脑,全部靠自己去销售和负责售后的话,会带来很大的运营压力的,首先得有个仓库,然后还要在各个地方开店销售电脑,需要很多销售人员。

此时,就出现了代理商(也叫经销商),厂家将东西卖给经销商,代理商再直接对接消费者,负责销售电脑和售后,这样厂家就只需关心生产电脑就可以了,销售和售后就交给代理商。当消费者有售后问题,再由代理商去向厂家申请维修,例如更换硬件等。

就好比我们平时买可乐都是去便利店买,而不是直接去找可口可乐公司购买。

同时,代理商也可以对厂家有要求,例如有问题得提供售后;同时有些厂家的产品是很好卖的,而有些厂家的产品又不好卖,代理商也可以挑要接谁的产品。

当然,使用代理商也有成本,因为代理商也是要赚钱的,要负责运营,所以卖出的产品一般是比原厂家进货贵的。 ‍

# 在程序中模拟代理

‍ 首先是对厂家的标准,在 Java 中接口就是标准,因此我们新建一个接口:

package com.peterjxl.proxy;

/**
 * 对生产厂家要求的接口,例如厂家需要规模大、负责任等
 */
public interface IProducer {
  
    // 销售
    public void saleProduct(float money);
  
    // 售后
    public void afterService(float money);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后是厂家,这里我们定义一个厂家的实现类:

package com.peterjxl.proxy;

/**
 * 一个生产者
 */
public class Producer implements IProducer {
    @Override
    public void saleProduct(float money) {
        System.out.println("销售产品,并拿到钱:" + money);
    }

    @Override
    public void afterService(float money) {
        System.out.println("提供售后服务,并拿到钱:" + money);
    }

    /**
     * 销售
     * @param money
     */

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

然后我们新建一个消费者,向厂家购买产品。

在代理商出现之前,我们是直接向厂家购买东西的:

package com.peterjxl.proxy;

/**
 * 模拟一个消费者
 */
public class Client {
    public static void main(String[] args) {
        Producer producer = new Producer();
        producer.saleProduct(10000f);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12

‍ 接下来我们就使用动态代理的方式,实现消费者向代理商购买产品的案例。

# 动态代理的概念

作用:不修改源码的基础上对方法增强

特点:字节码随用随创建,随用随加载(和装饰者模式不同,装饰者模式必须先写好一个类)

分类:基于接口的动态代理、基于子类的动态代理。

# 基于接口的动态代理

涉及的类:Proxy

提供者:JDK 官方

如何创建代理对象:使用 Proxy 类中的 newProxyInstance 方法

创建代理对象的要求:被代理类最少实现一个接口,如果没有则不能使用。

Proxy.newProxyInstance() 方法的参数:

  • ClassLoader:类加载器,它是用于加载代理对象字节码的,和被代理对象使用相同的类加载器。固定写法:被代理的类对象.getClass().getClassLoader()
  • Class[]:字节码数组,它是用于让代理对象和被代理对象有相同方法。固定写法,代理谁就写谁的 getClass().getInterfaces() ,例如 producer.getClass().getInterfaces()
  • InvocationHandler:用于提供增强的代码,它是让我们写如何代理,我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁要增强,谁写。例如增强 Producer 的 saleProduct 方法 ‍ 至此,我们可以写出这样的代码:
Proxy.newProxyInstance(
    producer.getClass().getClassLoader(),
    Producer.class.getInterfaces(),
    new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return null;
        }
 });
1
2
3
4
5
6
7
8
9

‍ InvocationHandler 中只有一个方法,它的作用就是:执行被代理对象的任何接口方法都会经过该方法。
方法参数的含义:

  • proxy 代理对象的引用
  • method 当前执行的方法
  • args 当前执行方法所需的参数

然后我们就可以在该方法里面,写出要增强的代码了。例如在销售电脑之前,先抽取 20%利润

package com.peterjxl.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 模拟一个消费者
 */
public class Client {
    public static void main(String[] args) {
        Producer producer = new Producer();

        //定义代理对象
        IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(
                producer.getClass().getClassLoader(),
                Producer.class.getInterfaces(),
                new InvocationHandler() {
                    /**
                     * InvocationHandler 中只有一个方法,
                     * 它的作用就是:执行被代理对象的任何接口方法都会经过该方法
                     * 方法参数的含义
                     * @param proxy   代理对象的引用
                     * @param method  当前执行的方法
                     * @param args    当前执行方法所需的参数
                     * @return 和被代理对象方法有相同的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy,
                                         Method method,
                                         Object[] args) throws Throwable {
                        //1.获取方法执行的参数
                        Float money = (Float) args[0];

                        //2.判断当前方法是不是销售,是则抽取 20%的提成,否则不抽取
                        if ("saleProduct".equals(method.getName())) {
                            return method.invoke(producer, money * 0.8f);
                        } else {
                            return method.invoke(producer, money);
                            /**
                             *invoke 方法第一个参数 要执行的对象,后续参数:要执行的方法的参数
                             * 匿名内部类访问外部变量,该变量需要是 Final 修饰符
                             */
                        }
                    }
                });

        proxyProducer.saleProduct(10000f);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

运行结果:

销售产品,并拿到钱:8000.0
1

也就是该产品卖了 1w,厂家拿到了 8k,代理商拿到 2k。我们没有对原始的方法做改造,也实现了增强的效果。

由于被代理类最少实现一个接口,如果没有实现接口,我们可以使用子类的方式实现代理。 ‍

# 基于子类的动态代理

‍ 要求有第三方 jar 包的支持,这里我们用 cglib

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.1_3</version>
</dependency>
1
2
3
4
5

我们新建一个包 cglib,然后新建 Producer 类,注意取消了实现接口:

package com.peterjxl.cglib;
public class Producer {
    public void saleProduct(float money) {
        System.out.println("销售产品,并拿到钱:" + money);
    }
    public void afterService(float money) {
        System.out.println("提供售后服务,并拿到钱:" + money);
    }
}
1
2
3
4
5
6
7
8
9

‍ 再新建一个消费者类:

package com.peterjxl.cglib;
public class Client {
    public static void main(String[] args) {

    }
}
1
2
3
4
5
6

cglib 的相关概念:

  • 涉及的类:Enhancer

  • 提供者:第三方 cglib 库

  • 创建代理对象的要求:被代理类不能是最终类(Final)

  • 如何创建代理对象:使用 Enhancer 类中的 create 方法。create 方法的参数:

    • Class:用于指定被代理对象的字节码
    • Callback:一个接口,我们一般都是实现该接口的一个子接口 MethodInterceptor,通常情况下都是匿名内部类,然后在里面写具体的要增强的代码 ‍ 至此,我们可以写出这样的代码:
// 返回一个代理对象
Enhancer.create(producer.getClass(), new MethodInterceptor() {
    @Override
    public Object intercept(Object obj, 
                            Method method, 
                            Object[] args, 
                            MethodProxy proxy) throws Throwable {
        return null;
    }
});
1
2
3
4
5
6
7
8
9
10

‍ 其中,intercept 方法的前三个参数,和我们之前讲基于接口的动态代理是一样的,而 methodProxy 则是当前执行方法的代理对象,不过一般用不上。

然后我们就可以将之前增强的逻辑,复制过来:

Enhancer.create(producer.getClass(), new MethodInterceptor() {
    @Override
    public Object intercept(Object obj,
                            Method method,
                            Object[] args,
                            MethodProxy proxy) throws Throwable {
        //1.获取方法执行的参数
        Float money = (Float) args[0];

        //2.判断当前方法是不是销售,是则抽取 20%的提成,否则不抽取
        if ("saleProduct".equals(method.getName())) {
            return method.invoke(producer, money * 0.8f);
        } else {
            return method.invoke(producer, money);
        }
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

完整代码:

package com.peterjxl.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class Client {
    public static void main(String[] args) {
        Producer producer = new Producer();
        Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object obj,
                                    Method method,
                                    Object[] args,
                                    MethodProxy proxy) throws Throwable {
                Float money = (Float) args[0];
                if ("saleProduct".equals(method.getName())) {
                    return method.invoke(producer, money * 0.8f);
                } else {
                    return method.invoke(producer, money);
                }
            }
        });

        cglibProducer.saleProduct(10000f);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

‍ 运行结果:是一样的

销售产品,并拿到钱:8000.0
1

# 使用动态代理增强 service

学会了动态代理之后,我们就可以对 service 中的方法进行增强了:对每个方法都进行事务控制即可!

为此,我们新建一个工厂类,用来创建 service 的代理对象:

package com.peterjxl.factory;

import com.peterjxl.domain.Account;
import com.peterjxl.service.IAccountService;
import com.peterjxl.utils.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

/**
 * 用于创建 Service 的代理对象的工厂
 */
public class BeanFactory {
    private IAccountService accountService;
    private TransactionManager txManager;

    public final void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }

    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }

    public IAccountService getAccountService() {

        IAccountService proxyService = (IAccountService) Proxy.newProxyInstance(
		accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object returnValue = null;

                        System.out.println("开始执行代理");
                        try {
                            txManager.beginTransaction();
                            returnValue = method.invoke(accountService, args);
                            txManager.commit();
                        } catch (Exception e) {
                            txManager.rollback();
                            e.printStackTrace();
                            throw new RuntimeException();
                        } finally {
                            txManager.release();
                        }

                        return returnValue;
                    }
                });

        return proxyService;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

‍

# 修改 service 类

此时,我们 service 类中就再也不需要事务控制的语句了:

public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public List<Account> findAllAccount() {
        return accountDao.findAllAccount();
    }

    @Override
    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);
    }

    @Override
    public void saveAccount(Account account) {
        accountDao.saveAccount(account);
    }

    @Override
    public void updateAccount(Account account) {
        accountDao.updateAccount(account);
    }

    @Override
    public void deleteAccount(Integer accountId) {
        accountDao.deleteAccount(accountId);
    }

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        List<Account> accounts = accountDao.findAllAccount();
        // 2.1 根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        // 2.2 根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        // 2.3 转出账户减钱
        source.setMoney(source.getMoney() - money);
        // 2.4 转入账户加钱
        target.setMoney(target.getMoney() + money);
        // 2.5 更新转出账户
        accountDao.updateAccount(source);
        int i = 1 / 0;
        // 2.6 更新转入账户
        accountDao.updateAccount(target);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

‍

# 配置 IoC

‍

  1. 将 service 的关于事务控制工具类的注入给取消

    <bean id="accountService" class="com.peterjxl.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>
    
    1
    2
    3
  2. 配置工厂类的 bean

    <!-- 配置BeanFactory -->
    <bean id="beanFactory" class="com.peterjxl.factory.BeanFactory">
        <property name="accountService" ref="accountService"/>
        <property name="txManager" ref="txManager"/>
    </bean>
    
    1
    2
    3
    4
    5
  3. 配置代理 service 的 bean

    <bean id="proxyAccountService" class="com.peterjxl.factory.BeanFactory" factory-bean="beanFactory" factory-method="getAccountService"/>
    
    1

# 修改测试类

此时我们需要注入的就是代理对象了,需要用 Qualifier 注解指定 beanid

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    @Qualifier("proxyAccountService")
    private IAccountService as;

    @Test
    public  void testTransfer(){
        as.transfer("aaa","bbb",100f);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

‍

# 测试

此时我们再次运行测试方法,可以看到能正常控制住事务,说明我们的代理对象是配置成功的 ‍

# 总结

我们自己实现事务的控制,可以看到配置变的非常繁琐;为此,Spring 也提供了相关的机制,也就是 AOP。从下一篇博客开始,我们就学习 AOP。

本项目已将源码上传到 GitHub (opens new window) 和 Gitee (opens new window) 上。并且创建了分支 demo10,读者可以通过切换分支来查看本文的示例代码 ‍

上次更新: 2025/5/5 17:15:09
事务问题
AOP 的概念和入门

← 事务问题 AOP 的概念和入门→

最近更新
01
新闻合订本 2025-10
10-31
02
2025 年 10 月记
10-30
03
用 AI 批量优化思源笔记排版
10-15
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式