• Java的动态代理Proxy


    动态代理类是实现在运行时指定的接口列表的类,这样通过类实例上的一个接口的方法调用将被编码并通过统一接口分派到另一个对象。

    先问一个问题?

    数据库操作需要以下流程: 获取数据库连接->执行sql->提交事务->异常回滚事务->释放连接。

    但是我们开发很少自己手动去做获取数据库连接、提交事务、回滚事务、释放连接等这些操作。

    为什么我们不需要做这些操作???

    那是有像Spring这样的框架,它底层用了动态代理技术,我们只需要写业务sql就可以了,其它像获取数据库连接、释放连接、提交事务、回滚事务等操作交给了代理类去完成,因为这些操作每个方法都是一样的,如果我们每个方法都去写那不是要疯掉。

    代理类在执行被代理类方法的前后加上自己的操作。


    传统数据库操作方式。

    // 加载驱动类
    Class.forName("com.mysql.jdbc.Driver");
    Connection conn;
    PreparedStatement ps1;
    PreparedStatement ps2;
    try {
        // 获取连接
        conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx", "root", "password");
        ps1 = conn.prepareStatement("update user set age = 1");
        ps2 = conn.prepareStatement("update user set money = 2");
        // 执行sql
        ps1.executeUpdate();
        ps2.executeUpdate();
        // 提交事务
        conn.commit();
     } catch (SQLException e) {
        // 回滚事务
        conn.rollback();
     } finally {
        // 关闭连接
        ps1.close();
        ps2.close();
        conn.close();
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    看代码 动态代理到底怎么实现的

    代理接口 EatSleep

    // 接口 吃
    public interface Eat {
        // 吃饭
        void eat();
    }
    
    // 接口 谁
    public interface Sleep {
        // 睡觉
        void sleep(int hour);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    被代理类 People

    它实现了两个接口 EatSleep

    public class People implements Eat, Sleep {
        @Override
        public void eat() {
            System.out.println("吃饭");
        }
    
        @Override
        public void sleep(int hour) {
            System.out.println("睡觉,时长:" + hour);
            if (hour <= 0) {
                // 睡眠时长小于0抛出异常
                throw new RuntimeException();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    统一处理类 ProxyInvocationHandler

    处理类需要实现InvocationHandler接口,获得invoke方法。

    invoke方法有三个参数:代理类、被代理类的方法、被代理类的方法参数。

    // 统一处理类
    public class ProxyInvocationHandler implements InvocationHandler {
        private Object obj;
    
        public ProxyInvocationHandler(Object obj) {
            this.obj = obj;
        }
    
        @Override
        public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
            Object result;
            try {
                System.out.println("方法调用之前");
                result = m.invoke(obj, args);
            } catch (InvocationTargetException e) {
                System.out.println("统一处理异常");
                return null;
            } finally {
                System.out.println("方法调用之后");
            }
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    生成代理类

    通过Proxy类的静态方法newProxyInstance生成代理类。

    需要传递三个参数:类加载器、被代理接口数组、统一处理类。

    public class Demo {
        public static void main(String[] args) {
            People obj = new People();
            Object proxyObj = Proxy.newProxyInstance(
                    obj.getClass().getClassLoader(),
                    obj.getClass().getInterfaces(),
                    new ProxyInvocationHandler(obj));
    
            Eat eatObj = (Eat) proxyObj;
            eatObj.eat();
            System.out.println();
    
            Sleep sleepObj = (Sleep) proxyObj;
            sleepObj.sleep(8);
            System.out.println();
    
            sleepObj.sleep(-1);
            System.out.println();
        }
    
    // 运行结果    
    // 方法调用之前
    // 吃饭
    // 方法调用之后
    
    // 方法调用之前
    // 睡觉,时长:8
    // 方法调用之后
    
    // 方法调用之前
    // 睡觉,时长:-1
    // 统一处理异常
    // 方法调用之后
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    类加载器可以通过class对象的getClassLoader()方法获得。

    还记得获取Class对象的几种方法吗???

    这里的obj.getClass().getInterfaces()得到结果就是接口的class数组。就等同于

    new Class[]{Eat.class, Sleep.class}
    
    • 1

    生成的代理类proxyObj可以向上转型为Eat接口和Sleep接口。不管调用Eateat()方法还是Sleepsleep()都会统一派发到InvocationHandler类的invoke方法,invoke方法中在调用被代理类的原方法之前和之后都可以进行一些操作,并且可以对被代理类的原方法的异常进行一些处理。


    结语

    动态代理非常重要,虽然平时可能很少用到,但是很多流行的框架都有用到它。动态代理还有一种实现方式CGLIB,它是一个强大的,高性能,高质量的Code生成类库,是一个开源项目,这个后续会跟大家分享。

    关注微信公众号:小虎哥的技术博客,每天一篇文章,让你我都成为更好的自己

  • 相关阅读:
    MetaObjectHandler的使用
    【前端】Vue+Element UI案例:通用后台管理系统-用户管理:Table表格增删查改、Pagination分页、搜索框
    公开课学习——基于索引B+树精准建立高性能索引
    归并排序和直接插入排序结合的排序算法
    shell编程01_Shell基础
    Jamba: A Hybrid Transformer-Mamba Language Model
    中国1km分辨率月最低温和最高温度数据集(1901-2020)
    【Q-Learning】TD算法的一种
    Maven 知识点总结
    施耐德AV71变频器和S7300PLC的DP通讯
  • 原文地址:https://blog.csdn.net/qq_28883885/article/details/126084485