准备前期代码
public interface IProducer {
/**
* 销售
* @param money
*/
public void saleProduct(float money);
/**
* 售后
* @param money
*/
public void afterProduct(float money);
}
/**
* 生产者
*/
public class Producer implements IProducer{
public void saleProduct(float money){
System.out.println("销售产品,赚了钱");
}
public void afterProduct(float money){
System.out.println("售后服务,赚了钱");
}
}
问题描述:生产者生产产品,代理商销售产品,从中提取一部分钱后,剩下的钱才是生产上拿到的钱,我们不能直接在saleProduct方法中实现这个功能,因为这样会破坏生产者的代码结构,因此就引出了动态代理的概念。动态代理的一个重要作用就是在不修改源码的基础上对方法进行增强。
动态代理
特点:字节码用的时候才创建、加载
作用:不修改源码的基础上对方法增强
分类:
- 基于接口的动态代理
- 基于子类的动态代理
基于接口的动态代理
涉及的类:Proxy
提供者:JDK官方
创建代理对象的方式:使用Proxy类中的newProxyInstance方法
创建代理对象的要求:被代理类最少实现一个接口,如果没有则不能使用该方式
newProxyInstance方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {...}
参数1:类加载器,用于加载代理对象字节码。和被代理对象使用相同的类加载器。代理那个类,就写那个类的字节码
参数2:字节码数组,用于让代理对象和被代理对象有相同方法。代理那个类,就写那个类的对应接口的字节码
参数3:提供增强的代码,写代理的具体逻辑。
实现:
public class Client {
public static void main(String[] args) {
//匿名内部类访问外部成员时,外部成员必须用final关键字修饰
final Producer producer = new Producer();
IProducer proxyProducer = (IProducer)Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 执行被代理对象的任何接口方法都会经过该方法(拦截)
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强代码
Object returnValue = null;
//1 获取方法执行的参数
float money = (float)args[0];
//2 判断当时方法是不是销售
if("saleProduct".equals(method.getName())) {
//3 代理商拿百分之20的钱
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(1000f);
}
}
结果:销售产品,赚了钱800.0
缺点:被代理类最少实现一个接口,如果没有则不能使用该方式
基于子类的动态代理
基于子类的动态代理解决了基于接口的动态代理的缺点
涉及的类:Enhancer
提供者:第三方cglib库
创建代理对象的方式:使用Enhancer类中的create方法
创建代理对象的要求:被代理类不能是最终类
create方法参数:
- Class:字节码,用于指定被代理对象的字节码
- Callback:用于提供增强的代码
实现:
Producer类不再需要实现IProducer接口:
public class Producer{
public void saleProduct(float money){
System.out.println("销售产品,赚了钱"+money);
}
public void afterProduct(float money){
System.out.println("售后服务,赚了钱"+money);
}
}
public class Client {
public static void main(String[] args) {
//匿名内部类访问外部成员时,外部成员必须用final关键字修饰
final Producer producer = new Producer();
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何接口方法都会经过该方法(拦截)
* @param o 代理对象的引用
* @param method 当前执行的方法
* @param objects 当前执行方法所需的参数
* @param methodProxy 当前执行方法的代理对象
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//提供增强代码
Object returnValue = null;
//1 获取方法执行的参数
float money = (float)objects[0];
//2 判断当时方法是不是销售
if("saleProduct".equals(method.getName())) {
//3 代理商拿百分之20的钱
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(1000f);
}
}
结果:销售产品,赚了钱800.0
动态代理在spring事务控制中的使用案例
加入在service层有这样的一个实现类,其中的transfer方法模拟的是A向B用户转账,A的余额减少,B的余额增加,这个业务逻辑肯定是要用到事务的,如txManager属性提供的就是一个事务管理对象。在transfer方法中进行控制事务。此外,不用管accountDao和txManager的具体类实现,只需要看注释知道他们的功能就好,不影响理解。
public class AccountServiceImpl implements IAccountService {
//dao层的对象,提供持久化接口
private IAccountDao accountDao;
//事务管理器对象,提供开启事务、提交事务、回滚事务、释放数据库连接资源到数据库池中
private TransactionManager txManager;
public void transfer(String sourceName, String targetName, Float money) {
try {
txManager.beginTransaction();
Account source = accountDao.findAccountByName(sourceName);
Account target = accountDao.findAccountByName(targetName);
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
accountDao.updateAccount(source);
accountDao.updateAccount(target);
txManager.commitTransaction();
} catch (SQLException e) {
try {
txManager.rollbackTransaction();
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
try {
txManager.releaseTransaction();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
可以看到,这种在业务层控制事务的方法很麻烦,有多少个业务接口,就要写多少个事务连接、释放等代码。我们可以利用动态代理技术,代理service层接口。
下面实现的是一个用于创建Service的代理对象的工厂,使用的是基于接口的动态代理方式,因为我们的service层一般都会提供一个接口和一个具体实现类。
/**
* 用于创建Service的代理对象的工厂
*/
public class BeanFactory {
private IAccountService accountService;
private TransactionManager txManager;
public void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public IAccountService getAccountService() {
//执行accountService中的任何方法都会经过该方法
return (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
//执行service层方法时都会经过该方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtValue = null;
try {
//开启事务
txManager.beginTransaction();
//执行代理的方法
rtValue = method.invoke(accountService, args);
//提交事务
txManager.commitTransaction();
return rtValue;
} catch (SQLException e) {
try {
//回滚事务
txManager.rollbackTransaction();
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
try {
//释放数据库连接资源到数据库池中
txManager.releaseTransaction();
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
});
}
}
然后在xml中添加如下配置,通过xml的方式注入BeanFactory 对象到IOC容器中
//其他配置
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
<!--配置BeanFactory-->
<bean id="beanFactory" class="com.example.factory.BeanFactory">
<property name="accountService" ref="accountService"></property>
<property name="txManager" ref="txManager"></property>
</bean>
//其他配置
修改service层的代码:
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(String sourceName, String targetName, Float money) {
Account source = accountDao.findAccountByName(sourceName);
Account target = accountDao.findAccountByName(targetName);
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
accountDao.updateAccount(source);
accountDao.updateAccount(target);
}
}
这样,执行service层方法前,都会先去执行BeanFactory的getAccountService方法,实现不修改transfer方法而对transfer方法的增强功能。
优秀文章推荐:
Java Proxy和CGLIB动态代理原理
JAVA静态代理和动态代理理解
Spring AOP中JDK和CGLib动态代理哪个更快?