动态代理(JDK的Proxy、CGLib的实现)

首先发现问题,然后解决问题,最后优化解决办法

从一个简单的转账业务来带入到这个问题中来

  • 首先转出账户中钱数会减少,然后转入账户钱会相应增加
  • 如果在这两步执行的过程中出现异常,就可能发生问题(转出钱减少了,但是转入没有增加)

代码实现转账业务:

POJO:

@Data
public class Account {
    private Integer id;
    private String name;
    private Double money;
}

Mapper:

public interface IAccountMapper {

    @Update("update accounts set money = #{money} where name = #{name}")
    int update(Account account);
    
    @Select("select * from accounts where name=#{name}")
    Account findByName(String name);
}

Service:

public interface IAccountService {
	//转账功能
    void transfer(String name1,String name2,Double money);
}

Service实现类:

@Service
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountMapper accountMapper;

    @Override
    public void transfer(String name1, String name2, Double money) {
        // 获得转账两人信息
        Account source = accountMapper.findByName(name1);
        Account target = accountMapper.findByName(name2);
        source.setMoney(source.getMoney()-money);
        target.setMoney(target.getMoney()+money);
        // 执行source减少和target增加的更新操作
        accountMapper.update(source);
        
        // 人为制造一个异常
        //System.out.println(10/0);
        
        accountMapper.update(target);
        System.out.println("转账业务完成");
    }
}

配置文件就省略了。

@Configuration
@ComponentScan({"com.zxy"})
@Import({MybatisConfig.class,DataSourceConfig.class})
public class SpringConfig {
}

现在来进行测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration( classes = {SpringConfig.class})
public class Test1 {
    @Autowired
    @Qualifier("accountServiceImpl")
    private IAccountService accountService;

    @Test
    public void test1(){
        // 张三转账1000给李四
        accountService.transfer("zhangsan","lisi",1000.0);
    }
}

当我们在业务类中 人为制造一个异常 就会发现:没有保证事务的情况下,张三的钱减少了,但是李四并不会增加,程序出现异常结束。

目前的解决方案

此时我们的代码并不能手动控制事务,要达到事务的效果,一定不可缺少的就是Connection对象,

Spring中也提供了事务同步管理器,(它使用ThreadLocal,将Connection对象和线程绑定,所以能够保证改事务中的Connection对象是同一个)

DataSourceUtils工具类帮助我们获得该线程中的Connection对象

配置类中增加:

    @Bean
    public Connection createConnection(@Qualifier("druidDataSource") DataSource dataSource){
        TransactionSynchronizationManager.initSynchronization();
        Connection connection = DataSourceUtils.getConnection(dataSource);
        return connection;
    }

不要导错包(import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.transaction.support.TransactionSynchronizationManager;

自定义事务类:

@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();
        }
    }
}

修改业务类:

@Service
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountMapper accountMapper;

    @Autowired
    private MyTransactionManager transactionManager;// 引入自定义事务类

    @Override
    public void transfer(String name1, String name2, Double money) {
        try {
            transactionManager.begin();
            // 获得转账两人信息
            Account source = accountMapper.findByName(name1);
            Account target = accountMapper.findByName(name2);
            source.setMoney(source.getMoney() - money);
            target.setMoney(target.getMoney() + money);
            // 执行source减少和target增加的更新操作
            accountMapper.update(source);
            // 制造异常
            System.out.println(10 / 0);
            accountMapper.update(target);
            transactionManager.commit();// 未发生异常,提交事务
            System.out.println("转账业务完成");
        }catch (Exception e){
            transactionManager.rollback();// 发生异常,回滚事务
            e.printStackTrace();
        }finally {
            transactionManager.close();// 关闭connection
        }
    }
}

此时在发生异常时,会回滚事务,就不会出现数据的不一致性

总结

我们解决了转账中事务问题,但是我们可以发现:我们的为了保证事务,改进了Service中的代码,现在只是一个转账的功能,如果所有业务都需要事务同步,那么上面的代码我们要在每一个方法中都要写一遍(开启事务、提交、回滚、关闭···),每写一个方法都要写重复的事务代码,很明显这不是一个合适的办法。现在我们可以使用代理模式来进一步改进的我们的代码

代理模式

使用一个代理,将原本对象包装起来,然后使用该代理对象“取代”原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否、何时将方法调用转换到原始对象上,从而调用的每一个方法都会经过代理类的包装(或者说是加强)

使用JDK官方的Proxy来实现动态代理

增加一个AccountServiceImpl的代理类

@Component
public class ProxyAccountServiceFactory {

    @Autowired
    @Qualifier("accountServiceImpl")
    private IAccountService accountService;

    @Autowired
    private MyTransactionManager transactionManager;

    @Bean("accountServiceProxy")
    public IAccountService createAccountServiceProxy(){
        IAccountService accountServiceProxy = (IAccountService) Proxy.newProxyInstance(
                accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object rtValue = null;
                        try {
                            transactionManager.begin();// 开启事务
                            // ※※※执行原本业务类中的方法
                            rtValue = method.invoke(accountService,args);
                            transactionManager.commit();// 提交事务
                        }catch (Exception e){
                            transactionManager.rollback();// 回滚事务
                            System.out.println("发生异常,事务已回滚--"+e);
                        }finally {
                            transactionManager.close();// 关闭连接
                        }
                        return rtValue;
                    }
                }
        );
        return accountServiceProxy;
    }
}

原本的AccountServiceImpl类还是回到初始无法处理异常的状态。

测试类调用代理类来完成业务:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration( classes = {SpringConfig.class})
public class Test1 {

    @Autowired
    @Qualifier("accountServiceProxy")// **修改为代理类**
    private IAccountService accountService;

    @Test
    public void test1(){
        accountService.transfer("zhangsan","lisi",100.0);
    }
}

补充

  • Proxy.newProxyInstance返回某一个对象的代理对象

  • 需要参数:

    • ClassLoader loader:类加载器
    • Class<?>[] interfaces:类实现的全部接口
    • InvocationHandler h:方法拦截器
  • 实现过程:

    • 生成一个实现了interfaces里所有接口并且继承了Proxy的代理类的字节码,然后使用loader加载这个代理类
    • 使用代理类父类的构造构造函数Proxy来创造一个代理类的实例,将我们自定义的h传入
      • 我们的h(例子中的匿名内部类)重写了invoke方法,所以代理类调用每一个方法时都会经过invoke方法,从而完成了对每一个原方法的包装
    • 因为代理类实现了interfaces接口,所以返回的实例可以直接强转为接口类型
  • 注意:

    • 被代理的类必须实现了接口,因为动态代理类在实现的时候继承了Proxy类,java不支持多继承,因此动态代理类只能根据接口来定义方法

使用第三方包CGLIB实现动态代理

导入依赖:

        <!-- https://mvnrepository.com/artifact/cglib/cglib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

编写CglibAccountServiceFactory代理类

@Component
public class CglibAccountServiceFactory {

    @Autowired
    @Qualifier("accountServiceImpl")
    private IAccountService accountService;

    @Autowired
    private MyTransactionManager transactionManager;

    @Bean("accountServiceCglib")
    public IAccountService createAccountServiceProxy(){
        IAccountService accountServiceCglib =(IAccountService) Enhancer.create(
                accountService.getClass(),
                new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        Object rtValue = null;
                        try {
                            transactionManager.begin();// 开启事务
                            rtValue = method.invoke(accountService,objects);
                            transactionManager.commit();// 提交事务
                        }catch (Exception e){
                            transactionManager.rollback();// 回滚事务
                            System.out.println("发生异常,事务已回滚--"+e);
                        }finally {
                            transactionManager.close();// 关闭连接
                        }
                        return rtValue;
                    }
                }
        );
        return accountServiceCglib;
    }
}

修改测试类中使用的AccountService:

    @Autowired
    @Qualifier("accountServiceCglib")
    private IAccountService accountService;

补充

  • Enhancer.create:创建一个类的代理对象

  • 参数:

    • Class type:类型
    • Callback callback:方法拦截器
  • 注意:

    • 如果委托类被 final 修饰, 那么它不可被代理; 如果委托类中存在被 final 修饰的方法, 那么该方法也不可被代理。
JDK中的Proxy和CGLib对比:

使用JDK代理需要提供interfaces列表(代理类实现接口)

使用CGLib需要提供superclass(代理类继承父类)

JDK代理只能对实现了接口的类生成代理

补充:

CGLib大多是对字节码的操作,适合单例模式,生成的类会放在堆中

JDK1.8后JDK代理的效率就高过了CGLib

Spring中同时使用JDK代理和CGLib,Bean有接口使用JDK代理,没有接口使用CGLib



如果有需要请继续阅读:下一篇Spring中的AOP ※

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值