从转账案例到动态代理

1. 转账案例

1.1 数据库准备

CREATE TABLE `account` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `money` double(255,0) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

INSERT INTO `account` VALUES (1, 'zhangsan', 1000);
INSERT INTO `account` VALUES (2, 'lisi', 1000);

复制代码

1.2 项目搭建

1.2.1 pom.xml

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.15</version>
    </dependency>
    <dependency>
        <groupId>commons-dbutils</groupId>
        <artifactId>commons-dbutils</artifactId>
        <version>1.6</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.20</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
复制代码

1.2.2 applicationContext.xml

<!--开启组件扫描-->
<context:component-scan base-package="com.lagou"/>

<!--加载jdbc配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!--把数据库连接池交给IOC容器-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>

<!--把QueryRunner交给IOC容器-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
    <constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
复制代码

1.2.3 实体类

@Data
public class Account {
    private Integer id;
    private String name;
    private Double money;
}
复制代码

1.2.4 dao层

(1)接口

/**
 * @author 振帅
 * Created on 2021/06/22 15:17
 */
public interface AccountDao {

    //转出
    public void out(String outUser, Double money);

    //转入
    public void in(String inUser, Double money);
}
复制代码

(2)实现

@Repository
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private QueryRunner queryRunner;

    @Override
    public void out(String outUser, Double money) {
        try {
            queryRunner.update("update account set money = money - ? where name = ?",money,outUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void in(String inUser, Double money) {
        try {
            queryRunner.update("update account set money = money + ? where name = ?",money,inUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
复制代码

1.2.5 service层

(1)接口

/**
 * @author 振帅
 * Created on 2021/06/22 17:27
 */
public interface AccountService {

    public void transfer(String outUsername, String inUsername, Double money);
}

复制代码

(2)实现

/**
 * @author 振帅
 * Created on 2021/06/22 17:29
 */
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    public void transfer(String outUsername, String inUsername, Double money) {
        //转出
        accountDao.out(outUsername,money);

        //int i = 1 / 0;

        //转入
        accountDao.in(inUsername,money);
    }
}
复制代码

1.2.6 测试

/**
 * @author 振帅
 * Created on 2021/06/22 17:26
 */
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAccount {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer(){
        accountService.transfer("zhangsan", "lisi", 100d);
    }

}
复制代码

1.2.7 问题分析

上面的代码事务在dao层,转出转入操作都是一个独立的事务,但实际开发,应该把业务逻辑控制在一个事务中,所以应该将事务挪到service层。

1.2.8 传统事务

(1)线程绑定工具类

业务逻辑控制在一个事务就需要两次操作都是同一个Connection

ThreadLocal

threadlocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据

ThreadLocal相当于维护了一个map,key就是当前的线程,value就是需要存储的对象

package com.lagou.utils;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * @Author 振帅
 * @Date 2021/06/22 21:08
 *  连接工具类,从数据源中获取一个连接,并将实现和线程的绑定
 */
@Component
public class ConnectionUtils {

    private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    @Autowired
    private DataSource dataSource;

    /**
     * 获取当前线程上的连接
     * @return
     */
    public Connection getThreadConnection() {
        // 先从ThreadLocal上获取
        Connection connection = threadLocal.get();
        if (connection == null) {
            try {
                //从数据源中获取一个连接,并存入到ThreadLocal中
                connection = dataSource.getConnection();
                threadLocal.set(connection);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        return  connection;
    }

    /**
     * 解除当前线程的连接绑定
     */
    public void removeThreadConnection() {
        threadLocal.remove();
    }
}

复制代码

(2)事务管理器

事务管理器工具类,包含:开启事务、提交事务、回滚事务、释放资源

package com.lagou.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.SQLException;

/**
 * @Author 振帅
 * @Date 2021/06/22 21:30
 * 事务管理器工具类,包含:开启事务、提交事务、回滚事务、释放资源
 */
@Component
public class TransactionManager {

    @Autowired
    private ConnectionUtils connectionUtils;

    /**
     * 开启事务
     */
    public void beginTransaction() {
        try {
            //关闭事务自动提交
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    public void commit() {
        try {
            connectionUtils.getThreadConnection().commit();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    public void rollback() {
        try {
            connectionUtils.getThreadConnection().rollback();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    /**
     * 释放资源
     */
    public void release() {
        try {
            //改回自 动提交事务
            connectionUtils.getThreadConnection().setAutoCommit(true);
            //归还到连接池
            connectionUtils.getThreadConnection().close();
            //解除线程绑定
            connectionUtils.removeThreadConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

}

复制代码

(3)修改dao层代码

注入事务管理器

/**
 * @author 振帅
 * Created on 2021/06/22 16:25
 */
@Repository
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private QueryRunner queryRunner;

    @Autowired
    ConnectionUtils connectionUtils;

    @Override
    public void out(String outUser, Double money) {
        try {
            queryRunner.update(connectionUtils.getThreadConnection(),"update account set money = money - ? where name = ?",money,outUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void in(String inUser, Double money) {
        try {
            queryRunner.update(connectionUtils.getThreadConnection(),"update account set money = money + ? where name = ?",money,inUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
复制代码

(4)修改service层代码

注入线程绑定工具类

/**
 * @author 振帅
 * Created on 2021/06/22 17:29
 */
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Autowired
    private TransactionManager transactionManager;

    @Override
    public void transfer(String outUsername, String inUsername, Double money) {

        try {
            // 1.开启事务
            transactionManager.beginTransaction();
            //2.业务操作
            accountDao.out(outUsername,money);
            //int i = 1 / 0;
            accountDao.in(inUsername,money);
            //3.提交事务
            transactionManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            //4.回滚事务
            transactionManager.rollback();
        } finally {
            // 5.释放资源
            transactionManager.release();
        }
    }
}
复制代码

1.2.9 问题分析

通过对业务层改造,已经可以实现事务控制,但是由于我们添加了事务控制,也产生了一个新的问题: 业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了,违背了面向对象的开发思想。

2. Proxy优化转账案例

2.1 什么是动态代理

  • 动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。
  • 代理类在程序运行期间,创建的代理对象称之为动态代理对象。这种情况下,创建的代理对象,并不是事先在Java代码中定义好的。而是在运行期间,根据我们在动态代理对象中的“指示”,动态生成的。也就是说,你想获取哪个对象的代理,动态代理就会为你动态的生成这个对象的代理对象。动态代理可以对被代理对象的方法进行功能增强。有了动态代理的技术,那么就可以在不修改方法源码的情况下,增强被代理对象的方法的功能,在方法执行前后做任何你想做的事情。

我们可以将业务代码和事务代码进行拆分,通过动态代理的方式,对业务方法进行事务的增强。这样
就不会对业务层产生影响,解决了耦合性的问题。

2.2 常用的动态代理技术

  • JDK 代理 : 基于接口的动态代理技术·:利用拦截器(必须实现invocationHandler)加上反射机制生成

一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,从而实现方法增强。

  • CGLIB代理:基于父类的动态代理技术:动态生成一个要代理的子类,子类重写要代理的类的所有不是

final的方法。在子类中采用方法拦截技术拦截所有的父类方法的调用,顺势织入横切逻辑,对方法进行
增强。

2.3 JDK动态代理方式

  • 提供者:JDK
  • 基于接口的动态代理
  • 使用JDK官方的Proxy类创建代理对象
  • 代理的目标对象必须实现接口

Proxy.newProxyInstance(类加载器,对象的接口,处理器);

package com.lagou.utils;

import com.lagou.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Author 振帅
 * @Date 2021/06/22 22:02
 */
@Component
public class JdkProxyFactory {

    @Autowired
    AccountService accountService;

    @Autowired
    private TransactionManager transactionManager;

    public AccountService createAccountServiceJdkProxy(){


        /**
         * 创建对象的代理对象
         * 参数一:类加载器
         * 参数二:对象的接口
         * 参数三:调用处理器,代理对象中的方法被调用,都会在执行方法。对所有被代理对象的方法进行拦截
         */
        AccountService instance = (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                Object result = null;
                try {
                    // 1.开启事务
                    transactionManager.beginTransaction();
                    System.out.println("before");
                    //2.业务操作
                    result = method.invoke(accountService, args);
                    System.out.println("after");
                    //3.提交事务
                    transactionManager.commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    //4.回滚事务
                    transactionManager.rollback();
                } finally {
                    // 5.释放资源
                    transactionManager.release();
                }
                return result;
            }
        });

        return instance;

    }
}

复制代码

2.4 CGLIB动态代理方式

  • 提供者:第三方 CGLib
  • 基于父类的动态代理
  • 使用CGLib的Enhancer类创建代理对象
  • 被代理对象不能用final修饰

Enhancer.create(对象的字节码文件,方法的拦截器);

package com.lagou.utils;

import com.lagou.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @Author 振帅
 * @Date 2021/06/22 22:20
 */
@Component
public class CGLBProxyFactory {

    @Autowired
    AccountService accountService;

    @Autowired
    private TransactionManager transactionManager;

    public AccountService createAccountServiceCGLBProxy() {

        /**
         * 使用CGLib的Enhancer创建代理对象
         * 参数一:对象的字节码文件
         * 参数二:方法的拦截器
         */
        AccountService o = (AccountService) Enhancer.create(accountService.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object result = null;
                try {
                    // 1.开启事务
                    transactionManager.beginTransaction();
                    System.out.println("before");
                    //2.业务操作
                    result = method.invoke(accountService, objects);
                    System.out.println("after");
                    //3.提交事务
                    transactionManager.commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    //4.回滚事务
                    transactionManager.rollback();
                } finally {
                    // 5.释放资源
                    transactionManager.release();
                }
                return result;
            }
        });

        return o;

    }

}

复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享