jdk动态代理_浅谈JDK动态代理(下)

介绍完JDK动态代理,今天和大家一起做个小案例:模拟Spring的事务管理。

主要内容:

  • 熟悉的陌生人

  • 山寨AOP事务需求分析

  • AOP事务具体代码实现


熟悉的陌生人

面试官如果问“请你谈谈你对Spring的理解”,估计很多人会脱口而出:IOC和AOP。IOC大概是大家对Spring最直接的印象,就是个大容器,装了很多bean,还会帮你做依赖注入。

475a765c1636a290ac9e1810ab843858.pngIOC

但是对于AOP,很多人其实没有太多概念,一时不知道Spring哪里用了AOP。好像事务用了切面,但具体又不了解。这样吧,我问你一个问题,我自己写了一个UserController,以及UserServiceImpl implements UserService,并且在UserController中注入Service层对象:

 @Autowiredprivate UserService userService;

那么,这个userService一定是我们写的UserServiceImpl的实例吗?

如果你听不懂我要问什么,说明你本身对Spring的了解还是太局限于IOC。

实际上,Spring依赖注入的对象并不一定是我们自己写的类的实例,也可能是userServiceImpl的代理对象。下面分别演示这两种情况:

  • 注入userServiceImpl对象

22c289f3e64a4aaaabcce8bc0806cfbd.png

注入的是UserServiceImpl类型

  • 注入userServiceImpl的代理对象(CGLib动态代理)

4497f715befb69eb67d94d64d8d9dd73.png

注入的是CGLib动态代理生成的userServiceImpl的代理对象

为什么两次注入的对象不同?

因为第二次我给UserServiceImpl加了@Transactional 注解。

ee09e557f74491b55ab498f3dac5cc33.png

此时Spring读取到这个注解,便知道我们要使用事务。而我们编写的UserService类中并没有包含任何事务相关的代码。如果给你,你会怎么做?动态代理嘛!

但是要用动态代理完成事务管理,还需要自己编写一个通知类,并把通知对象传入代理对象,通知负责事务的开启和提交,并在代理对象内部调用目标对象同名方法完成业务功能。

58c7c6c30e829f6ecd4e58ddda367719.png

我们能想到的方案,Spring肯定也知道。同样地,Spring为了实现事务,也编写了一个通知类,TransactionManager。利用动态代理创建代理对象时,Spring会把transactionManager织入代理对象,然后将代理对象注入到UserController。

所以我们在UserController中使用的userService其实是代理对象,而代理对象才支持事务。


山寨AOP事务需求分析

了解了Spring事务的大致流程后,我们再来分析一下自己如何编写一个山寨的AOP事务。

AOP事务,有两个概念:AOP和事务。

事务,大家已经很熟悉,这里主要讲讲什么是AOP。AOP,它是Aspect-Oriented Programming(面向切面编程)的英文缩写。什么是面向切面编程?有时直接介绍一个东西是什么,可能比较难。但是一说到它是干嘛的,大家就立即心领神会了。

我们的系统中,常常存在交叉业务,比如事务、日志等。UserService的method1要用到它,BrandService的method2也要用到它。一个交叉业务就是要切入系统的一个方面。具体用代码展示就是:

67c9fff803ec22549176f59886d6471c.png这个切面,可以是日志,也可以是事务

交叉业务的编程问题即为面向切面编程。AOP的目标就是使交叉业务模块化。可以将切面代码移动到原始方法的周围:

66ae119adab2fadac94bd474c161673a.png

原先不用AOP时,交叉业务直接写在方法内部的前后,用了AOP交叉业务写在方法调用前后。这与AOP的底层实现方式有关:动态代理其实就是代理对象调用目标对象的同名方法,并在调用前后加增强代码。不过这两种最终运行效果是一样的。

而所谓的模块化,我个人的理解是将切面代码做成一个可管理的状态。比如日志打印,不再是直接硬编码在方法中的零散语句,而是做成一个通知类,通过通知去执行切面代码。

所以,现在需求已经很明确,我们需要一个通知类(TransactionManager)执行事务,一个代理工厂帮助生成代理对象,然后利用动态代理将事务代码织入代理对象的各个方法中。

就好比下面三个Service,原先是没有开启事务的:

5729fd495dd8ffcb17049691ab894689.png

我们希望最终达到的效果是,我加了个@MyTransactional后,代理工厂给我返回一个代理对象:

4ee8bd9c8cb9823cdae04a6f61aa770d.png代理工厂使用动态代理,为每一个目标对象创建一个代理对象

细节分析:

84f18967ba50ec10f4b556226180de80.pngtxManager其实是在目标对象test()方法的前后执行事务,而不是方法内部的前后

也就是说,代理对象方法 = 事务 + 目标对象方法。

另外,还有个棘手的问题:事务操作,必须使用同一个Connection对象。如何保证?第一次从数据源获取Connection对象并开启事务后,将它存入当前线程的ThreadLocal中,等到了DAO层,还是从ThreadLocal中取,这样就能保证开启事务和操作数据库使用的Connection对象是同一个。

08c6124a27bffc4edeaebbdf5a39e35c.png

7563e476237702edb24c2fd4b27254ef.png

487ba51122056643d9fb5cee1445b008.png开启事务后,Controller并不是直接调用我们自己写的Service,而是Spring提供的代理对象

这就是事务的实现原理。


AOP事务具体代码实现

ConnectionUtils工具类

 package com.demo.myaopframework.utils;import org.apache.commons.dbcp.BasicDataSource;import java.sql.Connection;/** * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定 */public class ConnectionUtils {    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();    private static BasicDataSource dataSource = new BasicDataSource();    //静态代码块,设置连接数据库的参数    static{        dataSource.setDriverClassName("com.mysql.jdbc.Driver");        dataSource.setUrl("jdbc:mysql://localhost:3306/test");        dataSource.setUsername("root");        dataSource.setPassword("123456");    }    /**     * 获取当前线程上的连接     * @return     */    public Connection getThreadConnection() {        try{            //1.先从ThreadLocal上获取            Connection conn = tl.get();            //2.判断当前线程上是否有连接            if (conn == null) {                //3.从数据源中获取一个连接,并且存入ThreadLocal中                conn = dataSource.getConnection();                tl.set(conn);            }            //4.返回当前线程上的连接            return conn;        }catch (Exception e){            throw new RuntimeException(e);        }    }    /**     * 把连接和线程解绑     */    public void removeConnection(){        tl.remove();    }}

AOP通知(事务管理器)

 package com.demo.myaopframework.utils;/** * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接 */public class TransactionManager {    private ConnectionUtils connectionUtils;    public void setConnectionUtils(ConnectionUtils connectionUtils) {        this.connectionUtils = connectionUtils;    }    /**     * 开启事务     */    public  void beginTransaction(){        try {            connectionUtils.getThreadConnection().setAutoCommit(false);        }catch (Exception e){            e.printStackTrace();        }    }    /**     * 提交事务     */    public  void commit(){        try {            connectionUtils.getThreadConnection().commit();        }catch (Exception e){            e.printStackTrace();        }    }    /**     * 回滚事务     */    public  void rollback(){        try {            connectionUtils.getThreadConnection().rollback();        }catch (Exception e){            e.printStackTrace();        }    }    /**     * 释放连接     */    public  void release(){        try {            connectionUtils.getThreadConnection().close();//还回连接池中            connectionUtils.removeConnection();        }catch (Exception e){            e.printStackTrace();        }    }}

自定义注解

 @Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface MyTransactional {}

Service

 public interface UserService {    void getUser();} public class UserServiceImpl implements UserService {    @Override    public void getUser() {        System.out.println("service执行...");}}

实例工厂

 public class BeanFactory {    public Object getBean(String name) throws Exception {        //得到目标类的Class对象        Class> clazz = Class.forName(name);        //得到目标对象        Object bean = clazz.newInstance();        //得到目标类上的@MyTransactional注解        MyTransactional myTransactional = clazz.getAnnotation(MyTransactional.class);        //如果打了@MyTransactional注解,返回代理对象,否则返回目标对象        if (null != myTransactional) {            ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();            TransactionManager txManager = new TransactionManager();            txManager.setConnectionUtils(new ConnectionUtils());            //装配通知和目标对象            proxyFactoryBean.setTxManager(txManager);            proxyFactoryBean.setTarget(bean);            Object proxyBean = proxyFactoryBean.getProxy();            //返回代理对象            return proxyBean;        }        //返回目标对象        return bean;}}

代理工厂

 public class ProxyFactoryBean {    //通知    private TransactionManager txManager;    //目标对象    private Object target;    public void setTxManager(TransactionManager txManager) {        this.txManager = txManager;}    public void setTarget(Object target) {        this.target = target;}    //传入目标对象target,为它装配好通知,返回代理对象    public Object getProxy() {        Object proxy = Proxy.newProxyInstance(                target.getClass().getClassLoader(),/*1.类加载器*/                target.getClass().getInterfaces(), /*2.目标对象实现的接口*/                new InvocationHandler() {/*3.InvocationHandler*/                    @Override                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                        try {                            //1.开启事务                            txManager.beginTransaction();                            //2.执行操作                            Object retVal = method.invoke(target, args);                            //3.提交事务                            txManager.commit();                            //4.返回结果                            return retVal;                        } catch (Exception e) {                            //5.回滚事务                            txManager.rollback();                            throw new RuntimeException(e);                        } finally {                            //6.释放连接                            txManager.release();                        }                    }                }        );        return proxy;}}

代码结构

ac5705c8484ec7e35c58244df2cfe7e0.png

得到普通UserService:

6366012299d509140a5d4ee4d1d79e44.png

给UserServiceImpl添加@MyTransactional注解,得到代理对象:

225e56b4dc670c425dae711d15a675e6.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值