Spring(1)-粗解动态代理

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;  
    }  
}
posted @ 2024-04-27 19:58  marigo  阅读(3)  评论(0编辑  收藏  举报