首页 > 其他分享 >Spring(1)-粗解动态代理

Spring(1)-粗解动态代理

时间:2024-04-27 20:13:56浏览次数:25  
标签:vehicle 粗解 target 对象 Spring Object 代理 Vehicle public

Spring 最核心的概念是IOC、AOP,AOP的核心功能底层实现机制就是动态代理。
本文使用一个案例逐步讲解动态代理的底层原理。
备注:本文内容核心是韩顺平老师课程内容,这是我做的笔记外加个人理解和补充。

案例需求说明

  1. 我们有一个 Vehicle 接口,其中有一个 run 方法;这个接口下有两个实现类 Car 和 Ship
  2. 当运行 Car 对象的 run 方法和 Ship 对象的 run 方法时,输出如下内容:
交通工具开始运行了...
小汽车在公路 running..
交通工具停止运行了...

交通工具开始运行了...
大轮船在公路 running..
交通工具停止运行了...
  1. 思考如何完成

案例解决方案-传统方式

传统按OOP的思想,这个需求解决方法很简单,就是分别创建好接口,再创建对应的实现类,测试即可,主要代码如下:

  1. Vehicle 接口
package com.example.proxy2;  
  
public interface Vehicle {  
    public void run();  
}
  1. Car 实现类
package com.example.proxy2;  
  
public class Car implements Vehicle{  
    public void run(){  
        System.out.println("交通工具开始运行了...");  
        System.out.println("小汽车在公路 running..");  
        System.out.println("交通工具停止运行了...");  
    }  
}
  1. Ship 实现类
package com.example.proxy2;  
  
public class Ship implements Vehicle{  
    public void run(){  
        System.out.println("交通工具开始运行了...");  
        System.out.println("轮船在海上航行..");  
        System.out.println("交通工具停止运行了...");  
    }  
}
  1. Test ,这里用到了动态绑定机制,可以阅读Java(1)-粗解动态绑定 - marigo - 博客园
package com.example.proxy2;  
  
public class Test {  
    public static void main(String[] args) {  
        Vehicle car = new Car();  
        car.run();  
        Vehicle ship = new Ship();  
        ship.run();  
    }  
}

传统方法好吗,会明显发现有代码冗余,在这个案例中虽然只是输出语句,看起来不算冗余,但是扩展一下如果是某个复杂的实现方法呢,比如某个校验方法,那就显得很冗余了,而且冗余也还好,更主要的是无法对这些方法进行统一管理,修改起来很麻烦,所以解决了需要但是没有很好地解决。

案例解决方案-动态代理

动态代理的思路是在调用方法时,利用反射机制,根据方法去决定调用哪个对象方法
直接上代码:

  1. 我们先写一个类 VehicleProxyProvider
    1. 该类可以返回一个代理对象
    2. 定义一个属性 target_vehicle,将来真正要执行的对象赋值给它,当然这个对象肯定要实现 Vehicle 接口
    3. 定义构造器
    4. 接下来,我们要写一个方法 getProxy ,用来返回一个代理对象
      1. 开始写,随便设置一个返回值null
public class VehicleProxyProvider {  
	// 定义一个属性  
	private Vehicle target_vehicle;
	// 构造器
	public VehicleProxyProvider(Vehicle target_vehicle) {  
	    this.target_vehicle = target_vehicle;  
	}	
	// 返回代理对象方法	
	public Vehicle getProxy(){  
	    return null;  
	}
}
  1. 因为 getProxy 方法很重要,我们单独拿出来细讲
    1. 在 java.lang.reflect 包中有一个类 Proxy,它有一个方法 newProxyInstance() 用来创建代理对象/实例,源码如注释所示
    2. 初始我们对 newProxyInstance() 所需要的三个参数都没有,所以都设置成null,proxy就是代理对象,我们返回它
    3. 初始是 Object proxy = Proxy.newProxyInstance(null, null, null);,需要强转一下类型,才能返回
public Vehicle getProxy(){  
    /**  
     * Proxy.newProxyInstance() 方法  
     * public static Object newProxyInstance(ClassLoader loader,  // 类加载器  
     *                                       Class<?>[] interfaces,  // 将来要代理的对象的接口信息  
     *                                       InvocationHandler h) // 调用处理程序/对象,有一个很重要的方法invoke
     */  
    Vehicle proxy = (Vehicle) Proxy.newProxyInstance(null, null, null);
    return proxy;
}
  1. 我们继续完善Proxy.newProxyInstance(null, null, null)所需要的三个参数
    1. ClassLoader loader 类加载器,相关知识可以查看Java(2)-粗解类加载器 - marigo - 博客园
    2. Class<?>[] interfaces 要执行的类的接口信息
    3. 创建 InvocationHandler 对象
      1. InvocationHandler 本身是个接口(可以查阅源码)
      2. 接口不能实例化,但是我们就是要得到这个对象,该怎么办?使用匿名对象可以解决。简单理解就是在实现接口方法的同时创建对象。
      3. 这个接口中有个非常重要的方法,public Object invoke(Object proxy, Method method, Object[] args),这个invoke方法就是我们将来执行 target_vehicle 对象的方法时会调用到
// 类加载器  
ClassLoader classLoader = target_vehicle.getClass().getClassLoader();  
// 要执行的类的接口,我们要代理的是target_vehicle,所以要获取target_vehicle的接口Vehicle  
Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();  
// 调用处理程序  
InvocationHandler invocationHandler = new InvocationHandler() {  
    /**  
     * public Object invoke(Object proxy, Method method, Object[] args)     * proxy: 代理对象  
     * method: 代理对象被调用的方法,我们的例子中是 .run()方法  
     * args: 代理对象调用方法的参数,我们的例子中是 .run()方法中的参数,不过我们的例子中没有参数  
     * return: 返回代理对象调用方法的返回值,我们的例子中是,。run()的返回结果,不过没有返回值只有打印  
     */  
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        // 在调用目标对象的方法之前,我们可以添加一些自己的操作  
        System.out.println("交通工具开始运行了...");  
        // 调用目标对象的方法,反射  
        Object result = method.invoke(target_vehicle, args);  
        // 在调用目标对象的方法之后,我们可以添加一些自己的操作  
        System.out.println("交通工具停止运行了...");  
        return result;  
    }  
};
  1. 创建好三个需要的参数后,放入到 Proxy.newProxyInstance 中
Vehicle proxy = (Vehicle) Proxy.newProxyInstance(loader, interfaces, invocationHandler);
  1. 测试
    1. 创建一个对象,Car或者Ship,我们这里用Vehicle ship = new Ship();
    2. new VehicleProxyProvider(ship),将要代理的对象传入进去
    3. 接下来如果还是 ship.run(),那么调用的还是 ship 自身的run方法,没有走代理对象,可以测试一下,我们将Ship类中的第一个和第三个输出注释掉,结果输出轮船在海上航行..
    4. 所以,第三步是要获取一个代理对象 Vehicle proxy = vehicleProxyProvider.getProxy();
    5. 最后,运行代理对象的执行方法 proxy.run();
public class Test {  
    public static void main(String[] args) {  
        // 1. 创建一个轮船对象  
        Vehicle ship = new Ship();  
        // 2. 创建一个 VehicleProxyProvider 对象  
        VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider(ship);  
        // 3. 获取代理对象,该对象可以代理执行方法  
        Vehicle proxy = vehicleProxyProvider.getProxy();  
        // 4. 代理对象执行方法
        proxy.run();  
    }  
}

输出:

交通工具开始运行了...
轮船在海上航行..
交通工具停止运行了...
  1. 最后值得一提是:代理对象 proxy 在编译时是 Vehicle 类型,运行时是 Proxy 类型,这个结论比较容易理解,下面主要讲解代码具体的执行过程
    1. 既然我们输出了“交通工具开始运行了...”和“交通工具停止运行了...“,说明一定是执行了invoke(Object proxy, Method method, Object[] args) 方法
    2. 在这个方法中,先输出“交通工具开始运行了...”,再通过反射执行 method.invoke(target_vehicle, args)方法,回来继续输出“交通工具停止运行了...”

总结

动态代理,在哪里体现了动态呢?
执行的对象是动态的,我们创建谁就用谁的方法 Vehicle ship = new Ship();,这里可以创建Ship也可以创建Car;执行的方法是动态的。

完整代码

  1. Vehicle 接口
public interface Vehicle {  
    public void run();  
}
  1. Vehicle 实现类 Ship
public class Ship implements Vehicle{  
    public void run(){  
//        System.out.println("交通工具开始运行了...");  
        System.out.println("轮船在海上航行..");  
//        System.out.println("交通工具停止运行了...");  
    }  
}
  1. VehicleProxyProvider
public class VehicleProxyProvider {  
    // 定义一个属性  
    private Vehicle target_vehicle;  
  
    public VehicleProxyProvider(Vehicle target_vehicle) {  
        this.target_vehicle = target_vehicle;  
    }  
  
    public Vehicle getProxy(){  
        // 类加载器  
        ClassLoader loader = target_vehicle.getClass().getClassLoader();  
        /**  
         * Proxy.newProxyInstance() 方法  
         * public static Object newProxyInstance(ClassLoader loader,  // 类加载器  
         *                                       Class<?>[] interfaces,  // 要执行的类的接口  
         *                                       InvocationHandler h) // 调用处理程序  
         */  
        // 类加载器  
        ClassLoader classLoader = target_vehicle.getClass().getClassLoader();  
        // 要执行的类的接口,我们要代理的是target_vehicle,所以要获取target_vehicle的接口Vehicle  
        Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();  
        // 调用处理程序  
        InvocationHandler invocationHandler = new InvocationHandler() {  
            /**  
             * public Object invoke(Object proxy, Method method, Object[] args)             * proxy: 代理对象  
             * method: 代理对象被调用的方法,我们的例子中是 .run()方法  
             * args: 代理对象调用方法的参数,我们的例子中是 .run()方法中的参数,不过我们的例子中没有参数  
             * return: 返回代理对象调用方法的返回值,我们的例子中是,。run()的返回结果,不过没有返回值只有打印  
             */  
            @Override  
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
                // 在调用目标对象的方法之前,我们可以添加一些自己的操作  
                System.out.println("交通工具开始运行了...");  
                // 调用目标对象的方法,反射  
                Object result = method.invoke(target_vehicle, args);  
                // 在调用目标对象的方法之后,我们可以添加一些自己的操作  
                System.out.println("交通工具停止运行了...");  
                return result;  
            }  
        };  
        Vehicle proxy = (Vehicle) Proxy.newProxyInstance(loader, interfaces, invocationHandler);  
        return proxy;  
    }  
}

标签:vehicle,粗解,target,对象,Spring,Object,代理,Vehicle,public
From: https://www.cnblogs.com/marigo/p/18162430

相关文章

  • Spring(2)-粗解横切关注点
    本文是SpringAOP的前置内容,过渡作用。备注:本文核心内容是韩顺平老师的课程,在此基础上整理的笔记和个人理解需求有一个SmartAnimal接口,可以完成简单的加减法,要求在执行getSum()和getSub()时,输出执行前,执行过程,执行后的日志输出,请思考如何实现.日志--方法名--getSum方法......
  • Spring(3)-AOP快速入手
    经过前面Spring(1)-粗解动态代理-marigo-博客园Spring(2)-粗解横切关注点-marigo-博客园两篇内容,我们可以引入AOP了。AOP的简单理解AOP的全称(aspectorientedprogramming),面向切面编程。我们在此之前接触的更多是OOP,也就是面向对象编程。OOP和AOP有什么异同,网上有......
  • Spring(4)-AOP使用细节
    有了Spring(3)-AOP快速入手-marigo-博客园的学习,大体知道AOP的使用,接下来我们对AOP的细节进行展开。AOP-切入表达式作用:通过表达式定位一个或者多个连接点连接点可以理解成我们要切入到哪个类的哪个具体方法语法:execution([权限修饰符][返回值类型][简单类名/全类名][......
  • (一)spring beans
    1.beanDefinition首先是承载class的载体,里面包含了许多如是否单例,属性值等内容。以下只是建议代码,重在理解概念packageorg.springframework.spring.beans.factory.config;/***@ClassName:BeanDefinition//类名*@Description://描述*@Author:10300//作者......
  • Java(1)-粗解动态绑定
    Java的动态绑定机制是OOP中一个非常核心的概念。要理解动态绑定需要从Java的对象和类说起。当我们在创建一个类的时候,实际上就是在定义一种新的数据类型。类中可以包含属性和方法,基于这个类创建一个对象的时候,这个对象就有拥有该类所有的属性和方法。在引出动态绑定的概念之前,可......
  • Java(2)-粗解类加载器
    Java的类加载器是Java运行时环境中的重要组件,核心功能是将类的字节码加载到Java虚拟机中。举个例子可以通过一个图书馆的比喻来形象地解释类加载器的作用、用法和使用场景。想象一下,有一个巨大的图书馆(JVM),其中有非常多的藏书(类)。当你(程序)需要阅读一本书(使用一个类)时,你首先需要......
  • SpringBoot集成minio前后端联调
    基本配置初始化项目新建一个SpringBoot项目,集成lombokmybatis-plusminiohutool-core(可有可无)。新建一个数据表attachement,用于存储文件上传后在minio中的位置。droptableifexistsattachment;createtableattachment(idintauto_increment......
  • spring-securty-oauth2使用例子
    oauth2概念https://www.cnblogs.com/LQBlog/p/16996125.html环境搭建1.引入依赖<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId></depen......
  • spring boot
    链接:https://pan.baidu.com/s/1quiC-bqO5s3KgoLT5MWX7Q?pwd=412p提取码:412p1.Springboot入门springboot-简化了开发-比如-我们之前导入依赖--到需要自己写配置类-返回Beanspringboot帮我们简化了这个工程SpringBoot提供了一种快速使用Spring的方式,基于约定优于配置的思想sp......
  • 有意思!一个关于 Spring 历史的在线小游戏
    发现SpringOne的官网上有个好玩的彩蛋,分享给大家!进到SpringOne的官网,可以看到右下角有个类似马里奥游戏中的金币图标。点击该金币之后,会打开一个新的页面,进入下面这样一个名为:TheHistoryOfSpring的在线小游戏你可以使用上下左右的方向键来控制Spring的Logo一步步经历......