Spring中的动态代理
一、模拟转账(引出问题进而引出动态代理)
1.1 要求:
- 转出账户减少金额
- 转入账户增加金额
- 模拟人为制造异常,在转入之前发生异常
1.2 实现(以下代码未贴出全部,仅供参考)
1.2.1 POJO
@Data
public class Account implements Serializable {
private Integer id;
private String name;
private Double money;
}
1.2.2 Mapper
public interface IAccountMapper {
/**
* 根据姓名查询账户
* @param name
* @return
*/
public Account findByName(String name);
/**
* 更新账户
* @param account
* @return
*/
public int update(Account account);
}
1.2.3 业务层
package com.bdit.service.impl;
import com.bdit.mapper.IAccountMapper;
import com.bdit.pojo.Account;
import com.bdit.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* ClassName: AccountServiceImpl
* Package: com.bdit.service.impl
* date: 2020/11/16 16:04
*/
@Service
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountMapper accountMapper;
/**
*
* @param source 转出
* @param target 转入
* @param money 金额
*/
@Override
public void transfer(String source, String target, double money) {
//先查询转出账户的信息
Account accountSource= accountMapper.findByName(source);
//再查询转入账户的信息
Account accountTarget=accountMapper.findByName(target);
//更新转出账户的信息
accountSource.setMoney(accountSource.getMoney()-money);
accountMapper.update(accountSource);
//人为制造异常
System.out.println(10/0);
//更新转入账户的信息
accountTarget.setMoney(accountTarget.getMoney()+money);
accountMapper.update(accountTarget);
}
}
1.2.4 解决问题(仅仅参考)
package com.bdit.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.datasource.DataSourceUtils;
import
org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.sql.DataSource;
import java.sql.Connection;
/**
* ClassName: JdbcConfig
* Package: com.bdit.config
* Description: 数据源配置类
* author: BDIT
自定义事务管理类
* date: 2020/11/16 14:42
* Version 1.0
*/
@PropertySource(value = {"classpath:db.properties"})
public class DataSourceConfig {
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean(value = "druidDataSource")
public DataSource createDataSource(){
DruidDataSource druidDataSource=new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
@Bean
public Connection createConnection(@Qualifier("druidDataSource") DataSource
dataSource){
/*
* 保证当前的事务代码中的Connection对象与MyBatis中的SqlSession对象中的Connection
对象是同一个
*
* 使用ThreadLocal技术
*
* 通过Spring框架来实现这个操作
* */
//1 Spring框架提供了事务同步管理器,实现SqlSession对象中的Connection与事务中的
Connection对象一致
//spring事务同步管理器也是通过ThreadLocal实现了当前线程与Connection对象进行绑定。
TransactionSynchronizationManager.initSynchronization();
//Spring框架提供了一个DataSourceUtils工具类,getConnection()方法,
// 实现了从ThreadLocal中获取当前线程绑定的Connection对象
Connection connection=DataSourceUtils.getConnection(dataSource);
return connection;
}
}
1.2.5 自定义事务管理类
package com.bdit.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
/**
* ClassName: MyTransactionManager
* Package: com.bdit.tx
* Description: 事务类
* date: 2020/11/16 16:40
* Version 1.0
*/
@Component
public class MyTransactionManager {
@Autowired
private Connection connection;
public void begin(){
try {
connection.setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void commit(){
try {
connection.commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void rollback(){
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void close(){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
业务层引入事务管理类
package com.bdit.service.impl;
import com.bdit.mapper.IAccountMapper;
import com.bdit.pojo.Account;
import com.bdit.service.IAccountService;
import com.bdit.tx.MyTransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* ClassName: AccountServiceImpl
* Package: com.bdit.service.impl
* Description: TODO
* date: 2020/11/16 16:04
* Version 1.0
*/
@Service
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountMapper accountMapper;
@Autowired
private MyTransactionManager myTransactionManager;
/**
*
* @param source 转出
* @param target 转入
* @param money 金额
*/
@Override
public void transfer(String source, String target, double money) {
myTransactionManager.begin();
try{
//先查询转出账户的信息
Account accountSource = accountMapper.findByName(source);
//再查询转入账户的信息
Account accountTarget = accountMapper.findByName(target);
//更新转出账户的信息
accountSource.setMoney(accountSource.getMoney() - money);
accountMapper.update(accountSource);
//人为制造异常
System.out.println(10 / 0);
//更新转入账户的信息
accountTarget.setMoney(accountTarget.getMoney() + money);
accountMapper.update(accountTarget);
myTransactionManager.commit();
}catch (Exception ce){
myTransactionManager.rollback();
}
}
}
二、问题产生
业务层的方法变得臃肿了,里面充斥着很多重复的代码,并且业务层方法和事务控制的代码耦合了;如果我们 按照这种方式进行事务的提交、回滚等操作,都需要进行业务层代码的调整,而且导致所有的业务类都要和事 务进行耦合。 如果要想优化以上解决方案,我们需要 代理(动态代理和静态代理)
2.1 代理模式
-
代理
- 指的是位一个目标对象提供一个代理对象,并由代理对象控制对目标对象的引用,使用代理的对象,就是为了在不修改原代理的基础上,进行增强目标对象的业务逻辑性。
-
静态代理
- 特点:为每一个业务增强都提供一个代理类,由代理类来创建代理对象
-
动态代理
- 特点: 代理对象直接由代理生成工具动态生成。
- 原理:**使用一个代理将原本的对象包装起来,**然后使用该代理对象“取代”原始对象。任何 对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
- 方式:
- 基于接口实现动态代理:JDK动态代理
- 基于继承实现动态代理:Cglib、Javassist动态代理
-
JDK 动态代理
- JDK动态代理是使用 java.lang.reflect 包下的代理类来实现,JDK 动态代理必须有接口
-
CGLIB 动态代理
- CGLIB动态代理的原理是生成目标类的子类, 这个子类对象就是代理对象, 代理对象是被增强过的。
注:不管有没有接口都可以使用 CGLIB 动态代理,而不是只有在没有接口的情况下才能使用
-
JDK 和 Cglib 的区别:
- JDK 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体的方法前调用了 InvokeHandler 来处理
- Cglib 动态代理是利用 ASM 开源包,对代理对象类的class文件进行加载,通过修改其字节码生成子类来处理
注:
- 如果对象实现了接口(是个实现类),默认情况下采用 JDK 进行动态代理实现AOP(java默认使用 JDK 动态代理) ,但也可以强制使用 Cglib进行动态代理
- 如果对象没有实现接口(不是个实现类),则必须采用 Cglib ,Spring 会自动在 JDK 和 Cglib 之间进行转换
三、解决问题
3.1 使用 JDK 官方的 Proxy来实现动态代理
package com.bdit.com.bdit.proxy;
import com.bdit.service.IAccountService;
import com.bdit.tx.MyTransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* ClassName: ProxyAccountServiceFactory
* Package: com.bdit.com.bdit.proxy
* Description: 基于JDK的动态代理,作用:用于创建AccountService动态代理对象的 工厂类
* date: 2020/11/17 9:53
* Version 1.0
*/
@Component
public class ProxyAccountServiceFactory {
//指定被代理对象(AccountServiceImpl)对象
@Autowired
@Qualifier("accountServiceImpl")
private IAccountService accountService;
@Autowired
private MyTransactionManager myTransactionManager;
@Bean("proxyAccountService")
public IAccountService createAccountServiceProxy(){
//创建IAccountService对象
/**
* 参数1:被代理对象的类加载器
* 参数2:被代理对象,所实现的所有接口
* 参数3:指定原有方法,如何进行增强
*/
IAccountService proxyAccountService= (IAccountService)
Proxy.newProxyInstance(
accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* invoke方法,就是对原有功能方法的增强
* @param proxy 就是创建出来的动态代理对象的引用
* @param method 被代理对象所要执行的方法
* @param args 被代理对象所要执行的方法所接收的实际参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[]
args) throws Throwable {
System.out.println("=====被代理对象:"+accountService);
System.out.println("=====被增强的方法:"+method.getName());
System.out.println("=====被增强方法的实际参数:"+
Arrays.toString(args));
Object rtValue=null;
try{
//开启事务
myTransactionManager.begin();
//调用被代理对象的原有方法
rtValue=method.invoke(accountService,args);
//提交事务
myTransactionManager.commit();
}catch (Exception ce){
ce.printStackTrace();
//发生异常
myTransactionManager.rollback();
}finally {
myTransactionManager.close();
}
return rtValue;
}
}
);
return proxyAccountService;
}
}
业务层代码
package com.bdit.service.impl;
import com.bdit.mapper.IAccountMapper;
import com.bdit.pojo.Account;
import com.bdit.service.IAccountService;
import com.bdit.tx.MyTransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* ClassName: AccountServiceImpl
* Package: com.bdit.service.impl
* Description: TODO
* author: BDIT
* date: 2020/11/16 16:04
* Version 1.0
*/
@Service
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountMapper accountMapper;
/**
* @param source 转出
* @param target 转入
* @param money 金额
*/
@Override
public void transfer(String source, String target, double money) {
//先查询转出账户的信息
Account accountSource = accountMapper.findByName(source);
//再查询转入账户的信息
Account accountTarget = accountMapper.findByName(target);
//更新转出账户的信息
accountSource.setMoney(accountSource.getMoney() - money);
accountMapper.update(accountSource);
//人为制造异常
System.out.println(10 / 0);
//更新转入账户的信息
accountTarget.setMoney(accountTarget.getMoney() + money);
accountMapper.update(accountTarget);
}
@Override
public Account findByName(String name) {
Account account = null;
account = accountMapper.findByName(name);
return account;
}
@Override
public boolean update(Account account) {
int i = 0;
i = accountMapper.update(account);
if (i > 0) {
return true;
} else {
return false;
}
}
@Override
public boolean save(Account account) {
int i = 0;
i = accountMapper.save(account);
if (i > 0) {
return true;
} else {
return false;
}
}
@Override
public boolean delete(Integer id) {
int i = 0;
i = accountMapper.delete(id);
if (i > 0) {
return true;
} else {
return false;
}
}
@Override
public Account findById(Integer id) {
Account account = null;
account = accountMapper.findById(id);
return account;
}
@Override
public List<Account> findAll() {
List<Account> list = new ArrayList<>();
list = accountMapper.findAll();
return list;
}
}
测试类
package com.bdit.test;
import com.bdit.config.SpringConfig;
import com.bdit.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.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* ClassName: AccountTest
* Package: com.bdit.test
* Description: TODO
* date: 2020/11/16 16:12
* Version 1.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfig.class})
public class AccountTest {
@Autowired
@Qualifier("proxyAccountService")
private IAccountService accountService;
@Test
public void test1(){
accountService.transfer("zhangsan","lisi",500);
}
}
3.2 使用 Cglib 动态代理
pom.xml
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
package com.bdit.com.bdit.proxy;
import com.bdit.service.IAccountService;
import com.bdit.tx.MyTransactionManager;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* ClassName: CglibAccountServiceFactory
* Package: com.bdit.com.bdit.proxy
* Description: TODO
* date: 2020/11/17 10:37
* Version 1.0
*/
@Component
public class CglibAccountServiceFactory {
//指定被代理对象(AccountServiceImpl)对象
@Autowired
@Qualifier("accountServiceImpl")
private IAccountService accountService;
@Autowired
private MyTransactionManager myTransactionManager;
//用于创建IAccountService动态代理对象
@Bean("cglibAccountService")
public IAccountService createAccountService(){
IAccountService cglibAccountService=(IAccountService) Enhancer.create(
accountService.getClass(),
new MethodInterceptor() {
/**
* intercept 方法,就是对原有功能方法的增强
* @param o 就是创建出来的动态代理对象的引用
* @param method 被代理对象所要执行的方法
* @param objects 被代理对象所要执行方法,所接收的实际参数
* @param methodProxy 所要执行的方法的代理对象 method.invoke
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[]
objects, MethodProxy methodProxy) throws Throwable {
System.out.println("=====被代理对象:"+accountService);
System.out.println("=====被增强的方法:"+method.getName());
System.out.println("=====被增强方法的实际参数:"+
Arrays.toString(objects));
Object obj=null;
try{
//开启
myTransactionManager.begin();
//调用被代理对象所要执行的方法
obj=method.invoke(accountService,objects);
myTransactionManager.commit();
}catch (Exception ce){
ce.printStackTrace();
myTransactionManager.rollback();
}finally {
myTransactionManager.close();
}
return obj;
}
}
);
return cglibAccountService;
}
}