从01开始 从01开始
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)

peterjxl

人生如逆旅,我亦是行人
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)
  • 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新特性
    • Java
  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java并发

  • Java源码

  • JVM

  • 韩顺平

  • Java
  • 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,读者可以通过切换分支来查看本文的示例代码

‍

在GitHub上编辑此页 (opens new window)
上次更新: 2023/6/7 11:49:40
事务问题
AOP的概念和入门

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

Theme by Vdoing | Copyright © 2022-2023 粤ICP备2022067627号-1 粤公网安备 44011302003646号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式