动态代理
基本概述
- 动态代理是一种在运行时动态生成代理对象的技术,它允许在不修改原始对象代码的情况下,通过代理对象间接访问原始对象,并在访问前后执行额外的操作。
- 动态代理通常用于实现横切关注点(cross-cutting concerns),如日志记录、性能监控、事务管理等。
- 在Java中,动态代理主要通过两个核心类来实现:Proxy和InvocationHandler。Proxy类用于动态创建代理类,而InvocationHandler接口负责处理代理对象的方法调用。通过实现InvocationHandler接口,开发人员可以在代理对象的方法调用前后添加自定义的逻辑,实现对原始对象的控制和增强。
案例演示
现有Vehicle接口,其中有run()方法,Car类和Ship类分别实现该接口。在执行Car类和Ship类中的run()方法之前和之后,分别输出”交通工具开始运行..“和”交通工具停止运行..”。
Vehicle接口
1 2 3 4
| public interface Vehicle {
public void run(); }
|
Car类
1 2 3 4 5 6
| public class Car implements Vehicle{ @Override public void run() { System.out.println("汽车在马路上行驶..."); } }
|
Ship类
1 2 3 4 5 6
| public class Ship implements Vehicle{ @Override public void run() { System.out.println("轮船在海上行驶..."); } }
|
VehicleProxyProvider类
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
| import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;
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(); Class<?>[] interfaces = target_vehicle.getClass().getInterfaces(); InvocationHandler invocationHandler= new InvocationHandler() { @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; } }
|
Test类
1 2 3 4 5 6 7 8
| public class Test { public static void main(String[] args) { Vehicle vehicle = new Car(); VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider(vehicle); Vehicle proxy = vehicleProxyProvider.getProxy(); proxy.run(); } }
|
动态代理深入——引出横切关注点
现有Calculate接口,其中有getSum()、getSub()方法,Phone类分别实现该接口。在执行Phone类中的getSum()、getSub()方法之前和之后输出日志信息。
Calculate接口
1 2 3 4 5 6
| public interface Calculate {
public float getSum(float num1,float num2);
public float getSub(float num1,float num2); }
|
Phone类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Phone implements Calculate{ @Override public float getSum(float num1, float num2) { float result = num1 + num2; System.out.println("方法内部打印:result=" + result); return result; }
@Override public float getSub(float num1, float num2) { float result = num1-num2; System.out.println("方法内部打印:result=" + result); return result; } }
|
CalculateProxyProvider类
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 35 36 37 38 39 40
| import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays;
public class CalculateProxyProvider { private Calculate target_calculate;
public CalculateProxyProvider(Calculate target_calculate) { this.target_calculate = target_calculate; }
public Calculate getProxy() { ClassLoader classLoader = target_calculate.getClass().getClassLoader(); Class<?>[] interfaces = target_calculate.getClass().getInterfaces(); InvocationHandler invocationHandler = new InvocationHandler() { Object result = null; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{ try { System.out.println("方法执行开始-日志-方法名-" + method.getName() + "-参数" + Arrays.asList(args)); result = method.invoke(target_calculate, args); System.out.println("方法执行结束-日志-方法名-" + method.getName() + "-结果" + result); } catch (Exception e) { e.printStackTrace(); System.out.println("方法执行异常-日志-方法名-" + method.getName() + "-异常类型" + e.getClass().getName()); } finally { System.out.println("方法最终结束-日志-方法名-" + method.getName()); } return result; } }; Calculate proxy =(Calculate) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); return proxy; } }
|
CalculateTest类
1 2 3 4 5 6 7 8 9 10
| public class CalculateTest { public static void main(String[] args) { Calculate calculate = new Phone(); CalculateProxyProvider calculateProxyProvider = new CalculateProxyProvider(calculate); Calculate proxy = calculateProxyProvider.getProxy(); proxy.getSum(10,2); System.out.println("==================================="); proxy.getSub(10,2); } }
|
输出结果:
方法执行开始-日志-方法名-getSum-参数[10.0, 2.0]
方法内部打印:result=12.0
方法执行结束-日志-方法名-getSum-结果12.0
方法最终结束-日志-方法名-getSum
====================================================
方法执行开始-日志-方法名-getSub-参数[10.0, 2.0]
方法内部打印:result=8.0
方法执行结束-日志-方法名-getSub-结果8.0
方法最终结束-日志-方法名-getSub
引出新问题
问题一:硬编码
由以上案例引出了AOP中的横切关注点概念,但是又产生了新问题,在上述案例中,横切关注点都是由 System.out.println输出,为硬编码,而我们应该将这些横切关注点改为用方法实现。
对CalculateProxyProvider类进行改进
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 35 36 37 38 39 40 41 42 43 44
| import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays;
public class CalculateProxyProvider { private Calculate target_calculate;
public CalculateProxyProvider(Calculate target_calculate) { this.target_calculate = target_calculate; }
public void before(Object proxy, Method method, Object[] args){ System.out.println("方法执行开始-日志-方法名-" + method.getName() + "-参数" + Arrays.asList(args)); } public void after(Object proxy, Method method, Object[] args){ System.out.println("方法执行结束-日志-方法名-" + method.getName()); } public Calculate getProxy() { ClassLoader classLoader = target_calculate.getClass().getClassLoader(); Class<?>[] interfaces = target_calculate.getClass().getInterfaces(); InvocationHandler invocationHandler = new InvocationHandler() { Object result = null; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{ try { before(proxy, method, args); result = method.invoke(target_calculate, args); after(proxy, method, args); } catch (Exception e) { e.printStackTrace(); System.out.println("方法执行异常-日志-方法名-" + method.getName() + "-异常类型" + e.getClass().getName()); } finally { System.out.println("方法最终结束-日志-方法名-" + method.getName()); } return result; } }; Calculate proxy =(Calculate) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); return proxy; } }
|
【说明】将 System.out.println("方法执行开始-日志-方法名-" + method.getName() + "-参数" + Arrays.asList(args));和System.out.println("方法执行结束-日志-方法名-" + method.getName());从getProxy()中提出去,将其构造成方法,然后在invoke()中调用。
问题二:耦合度高
日志相关方法和业务逻辑都放在CalculateProxyProvider类导致代码耦合度较高,应该将其拆分开来。以下为自己设计简易AOP:
MyAOP类
1 2 3 4 5 6 7 8
| public class MyAOP { public static void before(Object proxy, Method method, Object[] args) { System.out.println("自己的切入类的 before 日志--方法名:" + method.getName() + "--方法开始--参数:" + Arrays.asList(args)); } public static void after(Method method, Object result) { System.out.println("自己的切入类的 after 日志--方法名:" + method.getName() + "--方法正常结束--结果:result=" + result); } }
|
CalculateProxyProvider类
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 35 36 37 38
| import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays;
public class CalculateProxyProvider { private Calculate target_calculate;
public CalculateProxyProvider(Calculate target_calculate) { this.target_calculate = target_calculate; }
public Calculate getProxy() { ClassLoader classLoader = target_calculate.getClass().getClassLoader(); Class<?>[] interfaces = target_calculate.getClass().getInterfaces(); InvocationHandler invocationHandler = new InvocationHandler() { Object result = null; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{ try { MyAOP.before(proxy, method, args); result = method.invoke(target_calculate, args); MyAOP.after(proxy, method, args); } catch (Exception e) { e.printStackTrace(); System.out.println("方法执行异常-日志-方法名-" + method.getName() + "-异常类型" + e.getClass().getName()); } finally { System.out.println("方法最终结束-日志-方法名-" + method.getName()); } return result; } }; Calculate proxy =(Calculate) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); return proxy; } }
|
问题三:不灵活 复用性差 硬编码
再次对以上代码进行分析,可知代码依旧不够灵活,复用性差,硬编码(没有注解和反射支撑)。
至此,基于对以上代码的分析,便可以引出Spring的第二核心概念 AOP(面向切面编程),具体内容请听下回分解。