本教程源码请访问:tutorial_demo
一、什么是动态代理
1.1、概念
动态代理的用途与装饰模式很相似,就是为了对某个对象进行增强。所有使用装饰者模式的案例都可以使用动态代理来替换。
特点:字节码随用随创建,随用随加载;
作用:不修改源码的基础上对方法增强;
学习目的:为了学习AOP的原理做准备。
1.2、实现方式
两种方式:
- 基于接口的动态代理,JDK官方提供,被代理类最少实现一个接口,如果没有则不能使用;
- 基于子类的动态代理,第三方cglib库提供。
我们这篇教程使用基于接口的动态代理方式讲解,所有案例都使用这种方式。
1.3、需要明确的几个概念
目标对象:被增强的对象。
代理对象:需要目标对象,然后在目标对象上添加了增强后的对象。
目标方法:被增强的方法。
代理对象 = 目标对象 + 增强
到现在为止,我们需要知道有一种方式可以在不改变目标对象方法的前提下,对方法进行增强,这个方式就是动态代理。使用它,我们需要提供目标对象和增强生成代理对象。
得到了代理对象就相当于有了一个强化版的目标对象,运行相关方法,除了运行方法本身,增强的内容也会被运行,从而实现了在不改变源码的前提下,对方法进行增强。
1.4、基于接口的动态代理方式详解
1.4.1、如何生成代理对象
使用Proxy类中的newProxyInstance方法。
1.4.2、newProxyInstance方法参数详解
ClassLoader loader
:
类加载器类型,你不用去理睬它,你只需要知道怎么可以获得它就可以了,获取方法:
this.class.getClassLoader();
只要你有一个Class对象就可以获取到ClassLoader对象。
Class[] interfaces
:
指定newProxyInstance()方法返回的对象要实现哪些接口,因为是数组,可以指定多个接口。
InvocationHandler h
:
三个参数中最重要的一个参数,是一个接口,叫调用处理器。这个接口只有一个方法,即invoke()方法。它是对代理对象所有方法的唯一实现。也就是说,无论你调用代理对象上的哪个方法,其实都是在调用InvocationHandler的invoke()方法。
1.4.3、invoke()方法参数详解
执行被代理对象的任何接口方法都会经过该方法。
Object proxy
:代理对象,也就是Proxy.newProxyInstance()方法返回的对象,通常我们用不上它。
Method method
:表示当前被调用方法的反射对象,例如m.fun(),那m么method就是fun()方法的反射对象;
Object[] args
:表示当前被调用方法的参数,当然m.fun()这个调用是没有参数的,所以args是一个长度为0的数组。
二、动态代理案例
下面通过一个案例,说明动态代理的用途。
2.1、创建Maven工程并添加坐标
4.0.0
org.codeaction
proxy
1.0-SNAPSHOT
junit
junit
4.13
test
2.2、创建一个IWaiter接口
package org.codeaction.proxy;
//表示服务员的接口
public interface IWaiter {
//提供服务的方法
void serve();
}
2.3、创建一个IWaiter接口的实现类
package org.codeaction.proxy;
//表示男服务员
public class ManWaiter implements IWaiter {
@Override
public void serve() {
System.out.println("服务...");
}
}
目前存在的问题,我希望让ManWaiter提供服务的时候(调用serve方法)打印如下信息:
你好...
服务...
再见...
我们可以这样做:
package org.codeaction.proxy;
//表示男服务员
public class ManWaiter implements IWaiter {
@Override
public void serve() {
System.out.println("你好...");
System.out.println("服务...");
System.out.println("再见...");
}
}
但是这样我们修改了serve方法,如果将来有其他需求,我们还要再修改serve方法,这显然很繁琐,是不可取的,我们可以使用动态代理的方式在不修改源码的基础上对serve方法进行增强。
2.4、创建测试类使用动态代理
package org.codeaction.test;
import org.codeaction.proxy.IWaiter;
import org.codeaction.proxy.ManWaiter;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyTest {
@Test
public void TestProxy() {
//目标对象
IWaiter manWaiter = new ManWaiter();
/**
* 三个参数,用来创建代理对象
*/
ClassLoader loader = this.getClass().getClassLoader();
Class[] interfaces = {IWaiter.class};
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object resultValue = null;
System.out.println("你好...");
resultValue = method.invoke(manWaiter, args);//调用目标对象的目标方法
System.out.println("再见...");
return resultValue;
}
};
//得到代理对象,代理对象就是在目标对象的基础上进行了增强的对象
IWaiter waiter = (IWaiter) Proxy.newProxyInstance(loader, interfaces, handler);
//前面添加“您好”,后面添加“再见”
waiter.serve();
}
}
运行测试方法,输出如下:
你好...
服务...
再见...
通过上面的代码及运行结果我们发现:
- 使用动态代理需要提供:目标对象、三大参数;
- 生成的代理对象是实现了三大参数中第二个参数的所有接口的对象;
- 运行代理对象的方法,就是运行invoke方法;
- 在invoke方法中实现增强。
三、动态代理使用代理工厂实现
上面的案例中,目标对象和增强绑定在了一起,无法自由切换,不灵活,接下来我们创建一个代理工厂来实现动态代理。
3.1、创建前置增强接口
package org.codeaction.proxy;
//前置增强
public interface BeforeAdvice {
void before();
}
3.2、创建后置增强接口
package org.codeaction.proxy;
//后置增强
public interface AfterAdvice {
void after();
}
3.3、创建代理工厂
package org.codeaction.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 这个类用来生成代理对象
* 需要的参数:
* * 目标对象
* * 增强
* 怎么用?
* 1.创建代理工厂
* 2.给工厂设置三样东西:
* * 目标对象:setTargetObject(xxx);
* * 前置增强:setBeforeAdvice(该接口的实现)
* * 后置增强:setAfterAdvice(该接口的实现)
* 3.调用createProxy()得到代理对象
* * 执行代理对象方法时:
* > 执行BeforeAdvice的before()
* > 目标对象的目标方法
* > 执行AfterAdvice的after()
*/
public class ProxyFactory {
private Object targetObject;//目标对象
private BeforeAdvice beforeAdvice;//前置增强
private AfterAdvice afterAdvice;//后置增强
public Object getTargetObject() {
return targetObject;
}
public void setTargetObject(Object targetObject) {
this.targetObject = targetObject;
}
public BeforeAdvice getBeforeAdvice() {
return beforeAdvice;
}
public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
this.beforeAdvice = beforeAdvice;
}
public AfterAdvice getAfterAdvice() {
return afterAdvice;
}
public void setAfterAdvice(AfterAdvice afterAdvice) {
this.afterAdvice = afterAdvice;
}
//用来生成代理对象
public Object createProxyObject() {
//三大参数
ClassLoader classLoader = this.getClass().getClassLoader();
Class[] interfaces = this.targetObject.getClass().getInterfaces();
InvocationHandler handler = new InvocationHandler() {
//在调用代理对象的方法时会执行这里的内容
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object resultValue = null;
if(beforeAdvice != null) {
//执行前置增强
beforeAdvice.before();
}
//执行目标对象的目标方法
resultValue = method.invoke(targetObject, args);
if(afterAdvice != null) {
//执行后置增强
afterAdvice.after();
}
//返回目标对象的返回值
return resultValue;
}
};
//得到代理对象
return Proxy.newProxyInstance(classLoader, interfaces, handler);
}
}
3.4、在测试类中添加测试方法
@Test
public void testProxyFactory() {
//创建工厂
ProxyFactory factory = new ProxyFactory();
//设置目标对象
factory.setTargetObject(new ManWaiter());
//设置前置增强
factory.setBeforeAdvice(new BeforeAdvice() {
@Override
public void before() {
System.out.println("你好...");
}
});
//设置后置增强
factory.setAfterAdvice(new AfterAdvice() {
@Override
public void after() {
System.out.println("再见...");
}
});
//创建代理对象
IWaiter waiter = (IWaiter) factory.createProxyObject();
//执行代理对象方法
waiter.serve();
}
运行测试方法,输出如下:
你好...
服务...
再见...
四、使用代理工厂的方式修改上一节的代码
在上一篇文章我们将纯注解方式结合Apache Commons DbUtils实现单表的CRUD操作的代码修改成了支持事务的版本,每一个Service方法都要开启事务,提交事务,回滚事务代码冗余,如果JdbcUtils中相关方法的方法名修改,那么Service中每个调用位置都有修改,为了解决上面的问题,我们使用动态代理的方式修改上一节的代码。
4.1、创建前置增强接口
package org.codeaction.proxy;
public interface BeforeAdvice {
void before() throws Exception;
}
4.2、创建后置增强接口
package org.codeaction.proxy;
public interface AfterAdvice {
void after() throws Exception;
}
4.3、创建特殊增强接口
这个是用来进行回滚的,就教他特殊增强吧。
package org.codeaction.proxy;
public interface ActAdvice {
void act() throws Exception;
}
4.4、创建代理工厂
package org.codeaction.proxy;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@Component
public class ProxyFactory {
private Object targetObject;
private BeforeAdvice beforeAdvice;
private AfterAdvice afterAdvice;
private ActAdvice actAdvice;
public ActAdvice getActAdvice() {
return actAdvice;
}
public void setActAdvice(ActAdvice actAdvice) {
this.actAdvice = actAdvice;
}
public Object getTargetObject() {
return targetObject;
}
public void setTargetObject(Object targetObject) {
this.targetObject = targetObject;
}
public BeforeAdvice getBeforeAdvice() {
return beforeAdvice;
}
public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
this.beforeAdvice = beforeAdvice;
}
public AfterAdvice getAfterAdvice() {
return afterAdvice;
}
public void setAfterAdvice(AfterAdvice afterAdvice) {
this.afterAdvice = afterAdvice;
}
public Object createProxyObject() {
ClassLoader classLoader = this.getClass().getClassLoader();
Class[] interfaces = this.targetObject.getClass().getInterfaces();
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object resultValue = null;
try {
if(beforeAdvice != null) {
beforeAdvice.before();
}
resultValue = method.invoke(targetObject, args);
if(afterAdvice != null) {
afterAdvice.after();
}
} catch (Exception e) {
e.printStackTrace();
if(actAdvice != null) {
actAdvice.act();
}
}
return resultValue;
}
};
return Proxy.newProxyInstance(classLoader, interfaces, handler);
}
}
4.5、修改Service接口的实现类AccountServiceImpl
去掉所有的和事务相关的代码,让Service只关注业务
package org.codeaction.service.impl;
import org.codeaction.dao.IAccountDao;
import org.codeaction.domain.Account;
import org.codeaction.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
@Override
public List findAll() throws Exception {
return accountDao.findAll();
}
@Override
public Account findById(Integer id) throws Exception {
return accountDao.findById(id);
}
@Override
public void save(Account account) throws Exception {
accountDao.save(account);
}
@Override
public void update(Account account) throws Exception {
accountDao.update(account);
}
@Override
public void delete(Integer id) throws Exception {
accountDao.delete(id);
}
@Override
public void transfer(Integer srcId, Integer dstId, Float money) throws Exception {
Account src = accountDao.findById(srcId);
Account dst = accountDao.findById(dstId);
if(src == null) {
throw new RuntimeException("转出用户不存在");
}
if(dst == null) {
throw new RuntimeException("转入用户不存在");
}
if(src.getMoney() < money) {
throw new RuntimeException("转出账户余额不足");
}
src.setMoney(src.getMoney() - money);
dst.setMoney(dst.getMoney() + money);
accountDao.update(src);
//int x = 1/0;
accountDao.update(dst);
}
}
4.6、修改主配置类
package org.codeaction.config;
import org.codeaction.proxy.ActAdvice;
import org.codeaction.proxy.AfterAdvice;
import org.codeaction.proxy.BeforeAdvice;
import org.codeaction.proxy.ProxyFactory;
import org.codeaction.service.IAccountService;
import org.codeaction.util.JdbcUtils;
import org.springframework.context.annotation.*;
import java.sql.SQLException;
@Configuration
@ComponentScan(basePackages = "org.codeaction")
@PropertySource("classpath:jdbc.properties")
@Import(JdbcConfig.class)
public class MyConfig {
/**
*
* @param factory 代理工厂
* @param accountService 目标对象
* @return
*/
@Bean("proxyAccountService")
public IAccountService createProxyAccountService(ProxyFactory factory, IAccountService accountService) {
factory.setTargetObject(accountService);
factory.setBeforeAdvice(new BeforeAdvice() {
@Override
public void before() throws Exception {
//开启事务
JdbcUtils.beginTransaction();
}
});
factory.setAfterAdvice(new AfterAdvice() {
@Override
public void after() throws Exception {
//提交事务
JdbcUtils.commitTransaction();
}
});
factory.setActAdvice(new ActAdvice() {
@Override
public void act() throws Exception {
//回滚
JdbcUtils.rollbackTransaction();
}
});
//生成代理对象
return (IAccountService)factory.createProxyObject();
}
}
4.7、修改测试类
package org.codeaction.test;
import org.codeaction.config.MyConfig;
import org.codeaction.domain.Account;
import org.codeaction.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MyConfig.class)
public class MyTest {
//注入代理工厂对象
@Autowired
@Qualifier("proxyAccountService")
private IAccountService accountService;
@Test
public void testFindAll() throws Exception {
List accounts = accountService.findAll();
for (Account account : accounts) {
System.out.println(account);
}
}
@Test
public void testFindById() throws Exception {
Account account = accountService.findById(3);
System.out.println(account);
}
@Test
public void testSave() throws Exception {
Account account = new Account();
account.setName("abc");
account.setMoney(10000F);
accountService.save(account);
System.out.println(account);
}
@Test
public void testDelete() throws Exception {
accountService.delete(4);
}
@Test
public void testUpdate() throws Exception {
Account account = new Account();
account.setId(5);
account.setName("ab111111111c111");
account.setMoney(10000F);
accountService.update(account);
}
@Test
public void testTrans() throws Exception {
accountService.transfer(1, 2, 10F);
}
}
注意这里注入的accountServie是代理工厂类的对象,运行测试方法,测试。