使用动态代理,抽取Service公共代码

本文通过实例展示了如何利用Java动态代理技术,抽取Service层的公共事务控制代码,降低业务方法与事务管理的耦合度,提升代码的可读性和可维护性。

原业务层代码:
AccountServiceImpl_OLD.java

public class AccountServiceImpl_OLD implements AccountService {

    private AccountDao accountDao;

    private TransactionManager transactionManager;

    // 实现set方法,使用Spring依赖注入
    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    // 实现set方法,以便Spring使用set方法完成注入
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public List<Account> findAll() {
        try {
            // 1、开启事务
            transactionManager.beginTransaction();
            // 2、执行操作
            List<Account> accounts = accountDao.findAll();
            // 3、提交事务
            transactionManager.commit();
            // 4、返回结果
            return accounts;
        } catch (Exception e) {
            // 5、回滚操作
            transactionManager.rollback();
            throw new RuntimeException(e);// 如果产生异常,程序不在执行。
        } finally {
            // 6、释放连接
            transactionManager.release();
        }
    }

    @Override
    public Account findAccountById(Integer id) {
        try {
            // 1、开启事务
            transactionManager.beginTransaction();
            // 2、执行操作
            Account account = accountDao.findAccountById(id);
            // 3、提交事务
            transactionManager.commit();
            // 4、返回结果
            return account;
        } catch (Exception e) {
            // 5、回滚操作
            transactionManager.rollback();
            throw new RuntimeException(e);// 如果产生异常,程序不在执行。
        } finally {
            // 6、释放连接
            transactionManager.release();
        }
    }

    @Override
    public void saveAccount(Account account) {
        try {
            // 1、开启事务
            transactionManager.beginTransaction();
            // 2、执行操作
            accountDao.saveAccount(account);
            // 3、提交事务
            transactionManager.commit();
        } catch (Exception e) {
            // 5、回滚操作
            transactionManager.rollback();
        } finally {
            // 6、释放连接
            transactionManager.release();
        }
    }

    @Override
    public void updateAccount(Account account) {
        try {
            // 1、开启事务
            transactionManager.beginTransaction();
            // 2、执行操作
            accountDao.updateAccount(account);
            // 3、提交事务
            transactionManager.commit();
        } catch (Exception e) {
            // 5、回滚操作
            transactionManager.rollback();
        } finally {
            // 6、释放连接
            transactionManager.release();
        }
    }

    @Override
    public void deleteAccount(Integer id) {
        try {
            // 1、开启事务
            transactionManager.beginTransaction();
            // 2、执行操作
            deleteAccount(id);
            // 3、提交事务
            transactionManager.commit();
        } catch (Exception e) {
            // 5、回滚操作
            transactionManager.rollback();
        } finally {
            // 6、释放连接
            transactionManager.release();
        }
    }

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        try {
            // 1、开启事务
            transactionManager.beginTransaction();
            // 2、执行操作
            // 2.1.根据sourceName查询转出账户
            Account sourceAccount = accountDao.findAccountByName(sourceName);
            // 2.2.根据targetName查询转入账户
            Account targetAccount = accountDao.findAccountByName(targetName);
            // 2.3.转出账户扣钱
            sourceAccount.setMoney(sourceAccount.getMoney() - money);
            // 2.4.转入账户加钱
            targetAccount.setMoney(targetAccount.getMoney() + money);
            // 2.5.更新转出账户
            accountDao.updateAccount(sourceAccount);

            int i = 1/0;

            // 2.6.更新转入账户
            accountDao.updateAccount(targetAccount);
            // 3、提交事务
            transactionManager.commit();
        } catch (Exception e) {
            // 5、回滚操作
            transactionManager.rollback();
            e.printStackTrace();
        } finally {
            // 6、释放连接
            transactionManager.release();
        }
    }
}

以上是原有业务层代码,每次调用执行sql语句之前都会开启事务,然后提交事务,若产生异常则回滚,最后将连接归还到连接池。

问题:
业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了。

下面,我们通过动态代理(此处演示基于接口的动态代理)实现抽取公共方法,增强业务层方法。

第一步:抽取原业务层代码,创建BeanFactory.java,用于创建Service的代理对象的工厂

public class BeanFactory {
    private AccountService accountService;

    private TransactionManager transactionManager;

    // 实现set方法,使用Spring依赖注入
    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    /**
     * getAccountService方法的匿名内部类引用了accountService,此处应该用final修饰
     * @param accountService
     */
    public final void setAccountService(AccountService accountService) {
        this.accountService = accountService;
    }

    /**
     * 获取Service的代理对象,不能直接返回AccountService
     * @return
     */
    public AccountService getAccountService() {
        AccountService proxyAccountService = (AccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
            /**
             * 添加事务的支持
             * @param proxy
             * @param method
             * @param args
             * @return 返回值
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object rtValue = null;
                try {
                    // 1、开启事务
                    transactionManager.beginTransaction();
                    // 2、执行操作
                    rtValue = method.invoke(accountService, args);
                    // 3、提交事务
                    transactionManager.commit();
                    // 4、返回结果
                    return rtValue;
                } catch (Exception e) {
                    // 5、回滚操作
                    transactionManager.rollback();
                    throw new RuntimeException(e);// 如果产生异常,程序不在执行。
                } finally {
                    // 6、释放连接
                    transactionManager.release();
                }
            }
        });
        return proxyAccountService;
    }
}

第二步:抽取业务层代码之后

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;

    // 实现set方法,以便Spring使用set方法完成注入
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public List<Account> findAll() {
        return accountDao.findAll();
    }

    @Override
    public Account findAccountById(Integer id) {
        return accountDao.findAccountById(id);
    }

    @Override
    public void saveAccount(Account account) {
        accountDao.saveAccount(account);
    }

    @Override
    public void updateAccount(Account account) {
        accountDao.updateAccount(account);
    }

    @Override
    public void deleteAccount(Integer id) {
        deleteAccount(id);
    }

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        // 2.1.根据sourceName查询转出账户
        Account sourceAccount = accountDao.findAccountByName(sourceName);
        // 2.2.根据targetName查询转入账户
        Account targetAccount = accountDao.findAccountByName(targetName);
        // 2.3.转出账户扣钱
        sourceAccount.setMoney(sourceAccount.getMoney() - money);
        // 2.4.转入账户加钱
        targetAccount.setMoney(targetAccount.getMoney() + money);
        // 2.5.更新转出账户
        accountDao.updateAccount(sourceAccount);

//        int i = 1/0;

        // 2.6.更新转入账户
        accountDao.updateAccount(targetAccount);
    }
}

第三步:
配置 Spring 的 IOC 容器
beam.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--配置代理的service-->
    <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
    
    <!--配置beanFactory-->
    <bean id="beanFactory" class="com.hxh.factory.BeanFactory">
        <!--注入service-->
        <property name="accountService" ref="accountService"></property>
        <!--注入事务管理器-->
        <property name="transactionManager" ref="transactionManager"></property>
    </bean>

    <!--配置Service-->
    <bean id="accountService" class="com.hxh.service.impl.AccountServiceImpl">
        <!--注入dao对象-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!--配置dao-->
    <bean id="accountDao" class="com.hxh.dao.impl.AccountDaoImpl">
        <!--注入QueryRunner-->
        <property name="runner" ref="runner"></property>
        <!--注入ConnectionUtils-->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

    <!--配置QueryRunner对象。QueryRunner没有提供set注入方式,只能使用构造函数注入
    QueryRunner默认是单例的。为了避免多个dao使用同一个对象,线程互相干扰。使用scope属性是QueryRunner为多例对象,每次使用时都重新创建新对象
    -->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>


    <!--配置ConnectionUtils-->
    <bean id="connectionUtils" class="com.hxh.utils.ConnectionUtils">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置事务管理器TransactionManager-->
    <bean id="transactionManager" class="com.hxh.utils.TransactionManager">
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

    <!--配置数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="88888888"></property>
    </bean>
</beans>

第四步:
通过ApplicationContext获取的业务层bean为 proxyAccountService
测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    @Qualifier("proxyAccountService")
    private AccountService as;
    
    @Test
    public void testTransfer() {
        as.transfer("A", "B", 100f);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值