代理模式
# 75.代理模式
本文我们来回顾下代理模式,这对于我们理解 AOP 很有帮助。
# 什么是代理
举个例子,假设有一个负责生产电脑的厂家,如果生产了 1w 台电脑,全部靠自己去销售和负责售后的话,会带来很大的运营压力的,首先得有个仓库,然后还要在各个地方开店销售电脑,需要很多销售人员。
此时,就出现了代理商(也叫经销商),厂家将东西卖给经销商,代理商再直接对接消费者,负责销售电脑和售后,这样厂家就只需关心生产电脑就可以了,销售和售后就交给代理商。当消费者有售后问题,再由代理商去向厂家申请维修,例如更换硬件等。
就好比我们平时买可乐都是去便利店买,而不是直接去找可口可乐公司购买。
同时,代理商也可以对厂家有要求,例如有问题得提供售后;同时有些厂家的产品是很好卖的,而有些厂家的产品又不好卖,代理商也可以挑要接谁的产品。
当然,使用代理商也有成本,因为代理商也是要赚钱的,要负责运营,所以卖出的产品一般是比原厂家进货贵的。
# 在程序中模拟代理
首先是对厂家的标准,在 Java 中接口就是标准,因此我们新建一个接口:
package com.peterjxl.proxy;
/**
* 对生产厂家要求的接口,例如厂家需要规模大、负责任等
*/
public interface IProducer {
// 销售
public void saleProduct(float money);
// 售后
public void afterService(float money);
}
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
*/
}
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);
}
}
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;
}
});
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);
}
}
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
也就是该产品卖了 1w,厂家拿到了 8k,代理商拿到 2k。我们没有对原始的方法做改造,也实现了增强的效果。
由于被代理类最少实现一个接口,如果没有实现接口,我们可以使用子类的方式实现代理。
# 基于子类的动态代理
要求有第三方 jar 包的支持,这里我们用 cglib
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
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);
}
}
2
3
4
5
6
7
8
9
再新建一个消费者类:
package com.peterjxl.cglib;
public class Client {
public static void main(String[] args) {
}
}
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;
}
});
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);
}
}
});
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);
}
}
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
# 使用动态代理增强 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;
}
}
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);
}
}
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
将 service 的关于事务控制工具类的注入给取消
<bean id="accountService" class="com.peterjxl.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"/> </bean>
1
2
3配置工厂类的 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配置代理 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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 测试
此时我们再次运行测试方法,可以看到能正常控制住事务,说明我们的代理对象是配置成功的
# 总结
我们自己实现事务的控制,可以看到配置变的非常繁琐;为此,Spring 也提供了相关的机制,也就是 AOP。从下一篇博客开始,我们就学习 AOP。
本项目已将源码上传到 GitHub (opens new window) 和 Gitee (opens new window) 上。并且创建了分支 demo10,读者可以通过切换分支来查看本文的示例代码
- 01
- 中国网络防火长城简史 转载10-12
- 03
- 公告:博客近期 RSS 相关问题10-02