spring 动态代理

感觉不错 三连支持 一起进步!

动态代理



动态代理:

● 特点:字节码随用随创建,随用随加载
● 作用:不修改源码的基础上对方法增强
● 分类:
○ 基于接口的动态代理
○ 基于子类的动态代理

一、基于接口的动态代理

基于接口的动态代理,要求被代理对象的类最少实现一个接口,否则不能创建代理对象。

● 涉及的类:java.lang.reflect.Proxy
● 提供者:jdk官方

如何创建代理对象:使用Proxy的newProxyInstance方法

创建代理对象的要求:被代理的类最少实现一个接口,如果没有则不能创建。

newProxyInstance方法的参数:

● classLoader:类加载器
它是用于加载代理对象字节码文件的,和被代理对象使用相同的类加载器。是固定写法。

● Class[]:字节码数组
它是用于让代理对象和被代理对象有相同的方法。固定的写法。

● invocationHandler:用于提供增强的代码
它是让我们写如何代理。一般都是写一个该接口的实现类。通常情况下都是匿名内部类,但不是必须。此接口的实现类都是谁用谁写

IProducer接口:

public interface IProducer {
    Float sellProduct(Float money);
    void afterSell(Float money);
}

IProducer的实现类Produer类

// 基于接口代理要求被代理类最少实现一个接口。如果此处不实现IProducer接口则无法创建被代理对象
public class Producer implements IProducer {
    public Float sellProduct(Float money) {
        System.out.println("销售商品");
        return money;
    }
    public void afterSell(Float money) {
        System.out.println("售后服务");
    }
}

编写main方法测试,并在main方法中加入代理:

    public static void main(String[] args) {
        final Producer producer = new Producer();
        
        // 返回的是Object类型对象,需要强转
        IProducer proxyProducer = 
            (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 执行被代理对象的任何方法都会经过此方法
                     * @param proxy 代理对象的引用(一般用不上)
                     * @param method 当前执行的方法
                     * @param args 当前执行方法的参数
                     * @return 和被代理对象方法有相同类型的返回值
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) 
                        throws Throwable {
                        // 编写增强的代码
                        Object result = null;
                        // 获取方法的传入参数
                        Float money = (Float) args[0];
                        // 判断方法名
                        if("sellProduct".equals(method.getName())) {
                            // 执行方法(此处代理将原本传入的金额打了8折)
                            result = method.invoke(producer, money * 0.8f);
                        }
                        return result;
                    }
                });

        // 通过创建的代理对象执行对应的方法
        Float result = proxyProducer.sellProduct(1000f);
        System.out.println(result);
    }

二、基于子类的动态代理

基于子类的动态代理:

● 涉及的类:Enhancer
● 提供者:第三方cglib

如何创建对象:使用Enhancer的create方法

创建代理对象的要求:被代理类不能是最终类

create方法的参数:

● class:字节码
指定被代理对象的字节码

● callback:提供增强的代码
我们一般写的都是该接口的子接口实现类:MethodInterceptor

pom中引入cglib:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.1_3</version>
</dependency>

编写Producer类:

// cglib不要求Producer实现接口,但是Producer类不能是final类
public class Producer{
    public Float sellProduct(Float money) {
        System.out.println("销售商品");
        return money;
    }

    public void afterSell(Float money) {
        System.out.println("售后服务");
    }
}

编写main方法测试,并在main方法中加入代理

public static void main(String[] args) {
        final Producer producer = new Producer();
		// 使用cglib创建代理对象,返回Object类型需要强转
        Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 执行被代理对象的任何方法都会经过该方法
             * @param proxy 代理对象的引用(一般用不上)
             * @param method 当前执行的方法
             * @param args 当前执行方法传入的参数
             * @param methodProxy 当前执行方法的代理对象(一般用不上)
             * @return 和被代理对象有相同类型的返回值
             * @throws Throwable
             */
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                // 编写增强的代码
                Object result = null;
                // 获取方法的传入参数
                Float money = (Float) args[0];
                // 判断方法名
                if("sellProduct".equals(method.getName())) {
                    // 执行方法(此处代理将原本传入的金额打了8折)
                    result = method.invoke(producer, money * 0.8f);
                }
                return result;
            }
        });
        float money = cglibProducer.sellProduct(1000f);
        System.out.println(money);
    }

三、示例

动态代理改造service

1.改造前service

QueryRunner在构造方法中传入数据源,在每次执行sql时不再指定数据源,此时在每次执行sql时都会创建一个数据库连接并在执行完之后提交。对于转账交易这类需要执行多条更新sql的逻辑无法进行事务控制,需要对程序作出修改。

  1. 创建连接工具类,用于从数据库获取数据库连接,并实现和ThreadLocal线程的绑定
public class ConnectionUtil {

    private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
    // 添加set方法,使用xml配置Spring注入
    private DataSource dataSource;
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 获取当前线程上的连接
     */
    public Connection getThreadConnection() {
        try {
            Connection connection = threadLocal.get();
            if (connection == null) {
                connection = dataSource.getConnection();
                threadLocal.set(connection);
            }
            return connection;
        }catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 把连接和线程解绑
     */
    public void removeConnection() {
        threadLocal.remove();
    }
}
  1. 添加事务管理工具类,包括开启事务、提交事务、回滚事务、释放连接等操作。
public class TransactionManager {

    // 添加set方法,使用xml配置Spring注入
    private ConnectionUtil connectionUtil;
    public void setConnectionUtil(ConnectionUtil connectionUtil) {
        this.connectionUtil = connectionUtil;
    }

    /**
     * 开启事务
     */
    public void beginTransaction() {
        try {
            connectionUtil.getThreadConnection().setAutoCommit(false);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 提交事务
     */
    public void commit() {
        try {
            connectionUtil.getThreadConnection().commit();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 回滚事务
     */
    public void rollback() {
        try {
            connectionUtil.getThreadConnection().rollback();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 释放连接
     */
    public void release() {
        try {
            // close并不会真正把连接关闭,只是把该连接还回了连接池中
            connectionUtil.getThreadConnection().close();
            // 该ThreadLocal中的连接已经被还回连接池,下次该线程如果还使用当前连接对象就用不了了。
            // 所以在线程绑定的连接关闭后,线程要和连接进行解绑。下次该线程要使用连接时重新从连接池获取连接绑定到线程。
            connectionUtil.removeConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

3.修改dao层的QueryRunner操作,为每次sql执行指定数据库连接。

public class AccountDaoImpl implements IAccountDao {

    private QueryRunner runner;
    private ConnectionUtil connectionUtil;

    public void setConnectionUtil(ConnectionUtil connectionUtil) {
        this.connectionUtil = connectionUtil;
    }

    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }

    public List<Account> findAllAccount() {
        try {
            // 调用QueryRunner重载的带有数据源连接参数的query方法
            return runner.query(connectionUtil.getThreadConnection(), "select * from account", new BeanListHandler<Account>(Account.class));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public Account findAccountByName(String accountName) {
        try {
            List<Account> accounts = runner.query(connectionUtil.getThreadConnection(), "select * from account where name = ?", new BeanListHandler<Account>(Account.class), accountName);
            if(accounts == null || accounts.size() == 0) {
                return null;
            }
            if(accounts.size() > 1) {
                throw new RuntimeException("查询的账户数量不止一个");
            }
            return accounts.get(0);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
  1. 为service层的代码包裹事务。
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;
    private TransactionManager transactionManager;
    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }
    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public List<Account> findAllAccount() {
        try {
            transactionManager.beginTransaction();
            List<Account> accounts = accountDao.findAllAccount();
            transactionManager.commit();
            return accounts;
        } catch (Exception e) {
            transactionManager.rollback();
            throw new RuntimeException(e);
        } finally {
            transactionManager.release();
        }
    }

    public void transfer(String sourceName, String targetName, Float money) {
        try {
            transactionManager.beginTransaction();
            Account sourceAccount = accountDao.findAccountByName(sourceName);
            Account targetAccount = accountDao.findAccountByName(targetName);
            sourceAccount.setMoney(sourceAccount.getMoney() - money);
            targetAccount.setMoney(targetAccount.getMoney() + money);
            accountDao.updateAccount(sourceAccount);
            // int i = 1 / 0;
            accountDao.updateAccount(targetAccount);
            transactionManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            transactionManager.rollback();
        } finally {
            transactionManager.release();
        }
    }
}

2.使用动态代理重构带有事务的service示例

  1. 将AccountServiceImpl中每个方法因为事务管理添加的重复性代码移除
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;
    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public List<Account> findAllAccount() {
        return accountDao.findAllAccount();
    }
    
    public void transfer(String sourceName, String targetName, Float money) {
        Account sourceAccount = accountDao.findAccountByName(sourceName);
        Account targetAccount = accountDao.findAccountByName(targetName);
        sourceAccount.setMoney(sourceAccount.getMoney() - money);
        targetAccount.setMoney(targetAccount.getMoney() + money);
        accountDao.updateAccount(sourceAccount);
        int i = 1 / 0;
        accountDao.updateAccount(targetAccount);
    }
}
  1. 编写BeanFactory类,作为service的代理对象的创建工厂
public class BeanFactory {
    private IAccountService accountService;
    private TransactionManager transactionManager;
    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }
    public final void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }

    // 创建service的代理对象
    public IAccountService getAccountService() {
        return (IAccountService) Proxy.newProxyInstance(
            accountService.getClass().getClassLoader(), 
            accountService.getClass().getInterfaces(), 
            new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) 
                throws Throwable {
                Object returnValue = null;
                try {
                    transactionManager.beginTransaction();
                    returnValue = method.invoke(accountService, args);
                    transactionManager.commit();
                    return returnValue;
                } catch (Exception e) {
                    transactionManager.rollback();
                    throw new RuntimeException(e);
                } finally {
                    transactionManager.release();
                }
            }
        });
    }
}
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值