天天看点

Proxy--动态代理(JDK与CGLIB)一 转账案例1.1 基础功能3.2 传统事务二 、Proxy优化转账案例2.1 JDK动态代理方式2.2 CGLIB动态代理方式

一 转账案例

需求

使用spring框架整合DBUtils技术,实现用户转账功能

1.1 基础功能

步骤分析

  • 创建java项目,导入坐标
  • 编写Account实体类
  • 编写AccountDao接口和实现类
  • 编写AccountService接口和实现类
  • 编写spring核心配置文件
  • 编写测试代码

转账案例

1 )创建java项目,导入坐标

<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>
</dependencies>

           

2 ) 编写Account实体类

public class Account {

    private Integer id;
    private String name;
    private Double money;
           

3 )编写AccountDao接口和实现类

public interface AccountDao {

    // 转出操作
    public void out(String outUser, Double money);
    // 转入操作
    public void in(String inUser, Double money);

}
           
@Repository("accountDao") //生成该类实例存到ioc容器
public class AccountDaoImpl implements AccountDao {

    @Autowired //注入queryRunner对象到ioc容器
    private QueryRunner queryRunner;

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

    public void in(String inUser, Double money) {

        String sql = "update account set money = money + ? where name = ?";
        try {
            queryRunner.update(sql,money,inUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
           

4 )编写AccountService接口和实现类

public interface AccountService {

    //转账方法
    public void transfer(String outUser,String inUser,Double money);
           
@Service("accountService")
public class AccountServiceImpl implements AccountService {

    //需要用到dao层的对象,所以就需要注入dao对象
    @Autowired
    private AccountDao accountDao;

    /*转账方法*/
    public void transfer(String outUser, String inUser, Double money) {
        accountDao.in(inUser,money);
        accountDao.out(outUser,money);
    }
}
           

5 )编写spring核心配置文件

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

    <!--加载注解扫描-->
    <!--开启组件扫描-->
    <context:component-scan base-package="com.lagou"/>
    <!--加载jdbc配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--配置dataSource-->
    <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>
</beans>
           

6 )编写测试代码

package com.lagou.test;

import com.lagou.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/*需要加载配置文件*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer(){

        accountService.transfer("tom","jerry",100.0);
    }
}
           

7 )问题分析

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

3.2 传统事务

步骤分析

  1. 编写线程绑定工具类
  2. 编写事务管理器
  3. 修改service层代码
  4. 修改dao层代码

1 )编写线程绑定工具类

/**
 * 线程绑定工具类,从dataSource中获取一个connection,并把connection与线程进行绑定
 * */

@Component //创建ConnectionUtils的实例存入Ioc容器
public class ConnectionUtils {

    @Autowired
    private DataSource dataSource;

    /*ThreadLocal: 线程内部的存储类,可以在指定的线程内部存储数据 以key-value形式存储
    * key---> ThreadLocal(就是当前线程)
    * value---> 任意类型的值*/
    private ThreadLocal<Connection> threadLocal;

    /*获取当前线程的连接,如果获取到的为空,那么就要从数据源中获取,放到ThreadLocal中,即当前线程*/
    public Connection getThreadConnection(){

        // 1.先从线程上ThreadLocal获取
        Connection connection = threadLocal.get();

        // 2. 判断con连接是否为空,为空就从数据源获取
        if (connection == null){
            //从数据源获取
            try {
                connection = dataSource.getConnection();

                // 把con连接放入ThreadLocal中
                threadLocal.set(connection);

            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        return connection;
    }

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

           

2 )编写事务管理器

/**
* 事务管理器工具类: 开启事务,提交事务,回滚事务,释放资源
* */
@Component  //存入Ioc容器方便使用其实例调用相应方法
public class TransactionManager  {

    //调用ConnectionUtils的方法,所以需要用到ConnectionUtils的实例
    @Autowired
    private ConnectionUtils connectionUtils;

    /*开启事务*/
    public void beginTransaction(){
        Connection threadConnection = connectionUtils.getThreadConnection();
        try {
            threadConnection.setAutoCommit(false);//改成手动提交事务
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /*提交事务*/
    public void commitTransaction(){
        Connection threadConnection = connectionUtils.getThreadConnection();
        try {
            threadConnection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    /*回滚事务*/
    public void rollbackTransaction(){
        Connection threadConnection = connectionUtils.getThreadConnection();
        try {
            threadConnection.rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    /*释放资源*/
    public void closeTransaction(){
        Connection threadConnection = connectionUtils.getThreadConnection();
        try {
            //改成自动提交
            threadConnection.setAutoCommit(true);
            
            //关闭连接
            threadConnection.close();
            
            //解除线程绑定
            connectionUtils.removeThreadConnection();
            
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

           

3 )修改service层代码

@Service("accountService")
public class AccountServiceImpl implements AccountService {

    //需要用到dao层的对象,所以就需要注入dao对象
    @Autowired
    private AccountDao accountDao;

    // 需要用到TransactionManager对象
    @Autowired
    private TransactionManager transactionManager;

    /*转账方法*/
    public void transfer(String outUser, String inUser, Double money) {

        try {
            // 1.开启事务
            transactionManager.beginTransaction();

            // 2.操作事务
            accountDao.in(inUser,money);
            accountDao.out(outUser,money);

            // 3.提交事务
            transactionManager.commitTransaction();

        } catch (Exception e) {
            e.printStackTrace();
            // 4.回滚事务
            transactionManager.rollbackTransaction();
        } finally {
            // 5.释放资源
            transactionManager.closeTransaction();
        }

    }
}

           

4 )修改dao层代码

@Repository("accountDao") //生成该类实例存到ioc容器
public class AccountDaoImpl implements AccountDao {

    @Autowired //注入queryRunner对象到ioc容器
    private QueryRunner queryRunner;

    @Autowired
    private ConnectionUtils connectionUtils;

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

    public void in(String inUser, Double money) {

        String sql = "update account set money = money + ? where name = ?";
        try {
            queryRunner.update(connectionUtils.getThreadConnection(),sql,money,inUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
           

5 )问题分析

上面代码,通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制,也产生了

一个新的问题: 业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了,违背了面向对象的开发思想。

二 、Proxy优化转账案例

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

常用的动态代理技术

JDK 代理 : 基于接口的动态代理技术·:利用拦截器(必须实现invocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,从而实现方法增强

CGLIB代理:基于父类的动态代理技术:动态生成一个要代理的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截技术拦截所有的父类方法的调用,顺势织入横切逻辑,对方法进行增强

Proxy--动态代理(JDK与CGLIB)一 转账案例1.1 基础功能3.2 传统事务二 、Proxy优化转账案例2.1 JDK动态代理方式2.2 CGLIB动态代理方式

2.1 JDK动态代理方式

Jdk工厂类

采用jdk动态代理技术生成目标类的代理对象
* newProxyInstance()方法的参数:
       **ClassLoader loader**  类加载器,借助被代理对象获取类加载器
*      **Class<?> interface**  被代理类所需要实现的全部接口
*      **InvocationHandler h**  当代理对象调用接口的任意方法时,都会执行InvocationHandler的invoke()方法        
           
***实现事务与业务代码的解耦,把事务代码放到代理对象的invoke()方法中,接口方法中只保留业务代码***

@Component
public class JdkProxyFactory {

    @Autowired
    private AccountService accountService;//被代理对象

    @Autowired
    private TransactionManager transactionManager;//管理事务对象

    /*采用jdk动态代理技术生成目标类的代理对象
    * newProxyInstance()方法的参数:ClassLoader loader  类加载器,借助被代理对象获取类加载器
    *      Class<?> interface  被代理类所需要实现的全部接口
    *      InvocationHandler h  当代理对象调用接口的任意方法时,都会执行InvocationHandler的invoke()方法        */
    public AccountService createAccountServiceJdkProxy(){

        AccountService accountServiceProxy =(AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
            //第三个参数,需要InvocationHandler的实现类,这里使用了匿名内部类,并重写了invoke方法
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /*参数:proxy : 当前的代理对象引用
                 *      method:被调用的目标方法的引用
                 *      args: 被调用目标方法的参数*/

                //会执行被代理类也就是accountService里面的方法

                /*实现类事务与业务的分离,解耦*/
                Object result = null;
                try {
                    //**在执行接口的方法之前**,开启事务
                  transactionManager.beginTransaction();

                    //**执行事务操作**
                     result = method.invoke(accountService, args);

                    //**在执行接口的方法之后,**提交事务
                    transactionManager.commitTransaction();

                } catch (Exception e) {
                    e.printStackTrace();
                    //回滚事务
                    transactionManager.rollbackTransaction();
                } finally {
                    //释放资源
                    transactionManager.closeTransaction();
                }

                return result;
            }
        });

        return accountServiceProxy;
    }

}

           

接口代码

@Service("accountService")
public class AccountServiceImpl implements AccountService {

    //需要用到dao层的对象,所以就需要注入dao对象
    @Autowired
    private AccountDao accountDao;

   /* // 需要用到TransactionManager对象
    @Autowired
    private TransactionManager transactionManager;*/

    /*转账方法*/
    public void transfer(String outUser, String inUser, Double money) {

          /*  // 1.开启事务
            transactionManager.beginTransaction();*/

            // 2.操作事务
            accountDao.in(inUser,money);
            //int i = 10/0;
            accountDao.out(outUser,money);

            /*// 3.提交事务
            transactionManager.commitTransaction();

            // 4.回滚事务
            transactionManager.rollbackTransaction();

            // 5.释放资源
            transactionManager.closeTransaction();*/

    }
}
           

测试代码

@Test
    public void testTransferProxy(){

        //当前返回的时AccountService的代理对象Proxy
        AccountService accountServiceJdkProxy = jdkProxyFactory.createAccountServiceJdkProxy();
        //当代理对象调用被代理对象实现的接口的任意方法时,都会执行invoke()方法
        accountServiceJdkProxy.transfer("tom","jerry",200.0);
    }
           

2.2 CGLIB动态代理方式

Cglib工厂类

/**
 *  该类使用cglib动态代理的方式,对目标类AccountServiceImpl进行方法的transfer的动态增强
 */
@Component
public class CglibProxyFactory {

    @Autowired
    private AccountService accountService;
    @Autowired
    private TransactionManager transactionManager;
    public AccountService createAccountServiceCglibProxy(){

        //create()方法的参数
        //参数1:目标类的字节码对象
        //参数2:动作类,当代理对象调用目标对象的原方法时,都会调用代理对象的intercept()方法
        AccountService accountServiceProxy =(AccountService) Enhancer.create(accountService.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //o 代表生成的代理对象,method 代表目标方法的引用, objects 方法入参 methodProxy 方法代理

                try {
                    //开启事务
                    transactionManager.beginTransaction();

                    //执行事务操作
                    method.invoke(accountService,objects);
                    //提交事务
                    transactionManager.commitTransaction();
                } catch (Exception e) {
                    e.printStackTrace();
                    //回滚
                    transactionManager.rollbackTransaction();
                } finally {
                    //释放资源
                    transactionManager.closeTransaction();
                }

                return null;
            }
        });

        return accountServiceProxy;
    }
}
           

测试代码

@Test
    public void testTransferProxyCglib(){

        //当前返回的时AccountService的代理对象Proxy
        AccountService accountServiceCglibProxy = cglibProxyFactory.createAccountServiceCglibProxy();
        //当代理对象调用被代理对象实现的接口的任意方法时,都会执行intercept方法
        accountServiceCglibProxy.transfer("tom","jerry",100.0);
    }
           

继续阅读