
为什么需要动态代理动态代理是一种在不修改原代码的情况下增强原代码功能的技术。这里有两个重点不修改原代码增强原代码功能举个例子说明这两点重要在哪里下面有两个方法如果我想让这两个方法都打印 a 和 b 的值要怎么实现呢public int add(int a, int b) { return a b; } public int sub(int a, int b) { return a - b; }这个问题非常简单只需要在 return 语句前面加上一个打印语句就可以了代码如下public int add(int a, int b) { System.out.println(a a b b); return a b; } public int sub(int a, int b) { System.out.println(a a b b); return a - b; }现在我要修改这个需求我不再打印 a 和 b 的值我要打印当前时间和当前线程。没错这个需求我们一样可以在两个方法中写相同的代码来实现。但这正是问题所在因为这两个方法中出现了重复的代码只要这段代码需要修改就必须把两个方法中的代码重写一遍。如果有 10 个、100 个方法都使用到了这段重复的代码修改起来非常麻烦。对于这种重复出现的代码我们推荐的做法是用方法将重复代码抽取出来以后如果这段代码需要调整我们只需要一次更新就可以四处运行了。public int add(int a, int b) { print(a, b); return a b; } public int sub(int a, int b) { print(a, b); return a - b; } public void print(int a, int b) { System.out.println(a a b b); }但是用方法抽取重复代码真的能完全做到 “一次更新到处运行” 吗其实还是不可以的如果方法的参数列表、可见性、返回值、抛出的异常发生变化调用这个方法的方法也要修改代码来适配一样需要调整原代码在这个例子中如果需要在 return 语句前面添加更多的功能就需要不断往 add 和 sub 方法中塞入更多的方法一样需要调整原代码同时因为需要手动调用方法可能会忘记调用某个方法所以这个时候动态代理的优势就体现出来了首先它能够实现最基础的功能增强原代码的功能并且可以做到无侵入、不修改原代码。什么是动态代理在前面已经介绍过了动态代理是一种在不修改原代码的情况下增强原代码功能的技术。这里起一个新的标题是为了和静态代理区分开来。动态代理和静态代理都有这几个概念被代理类被代理类对象代理类代理类对象其实呢这些概念只有两个关系被代理类和代理类的关系类和对象的关系。那类和对象的关系我们都很清楚了现在只需要搞懂被代理类和代理类之间的关系就可以了。举个例子现在我们项目里面有一个 UserServiceImpl 类实现了一个支付方法。interface UserService { boolean pay(int money); } class UserServiceImpl implements UserService{ Override public boolean pay(int money) { System.out.println(检查余额); System.out.println(调用支付API); return true; } }现在需要对这个支付方法进行增强增加一个风险控制功能基于静态代理来实现。interface UserService { boolean pay(int money); } class UserServiceImpl implements UserService { Override public boolean pay(int money) { System.out.println(检查余额); System.out.println(调用支付API); return true; } } class UserServiceImpl2 implements UserService { Override public boolean pay(int money) { System.out.println(检查账号风险情况); System.out.println(检查余额); System.out.println(调用支付API); return true; } }UserServiceImpl 是原始类没有进行方法的增强而 UserServiceImpl2 类是代理类增加了风险控制的功能。UserServiceImpl 是没有被增强的类UserServiceImpl2 是增强后的类所以 UserServiceImpl 是被增强类UserServiceImpl2 是增强类。换一个说法UserServiceImpl 是被代理类UserServiceImpl2 是代理类。但是事情还没有结束还记得类跟对象的关系吗有了代理类还需要代理类对象调用这个 pay 方法才能真正实现效果。被代理类旧的类没有被增强代理类一个新的类增强了功能被代理类对象被代理类的对象不能实现各种特殊的效果代理类对象代理类的对象可以实现特殊的效果既然搞懂了代理类和被代理类的关系就下来就是动态代理和静态代理的关系了。静态代理需要手动编写代理类动态代理不需要手动编写代理类程序运行时自动生成代理类我举的例子是静态代理所以需要手动编写 UserServiceImpl2 这个代理类而动态代理是不需要手动编写代理类的。但是动态代理还是要写代码的。据我所知实现动态代理的技术有两种JDK 和 CGLib下面是这两种方案的代码实现。JDKJDK 是 Java 原生支持的动态代理方案基于接口 反射实现。举个例子现在有一个 Dog 类里面有两个方法 eat 和 drink现在要基于 JDK 对 eat 方法进行增强要怎么实现呢public class Dog { public void eat(String food) { System.out.println(小狗在吃 food); } public void drink(String water) { System.out.println(小狗在喝 water); } }首先使用极限法假设 Dog 类里面有 1 亿个方法我怎么知道是哪个方法需要增强这里就要使用到接口了将需要增强的方法通过接口暴露出去让 Dog 类实现这个接口重写 eat 方法。public class Dog implements abc { Override public void eat(String food) { System.out.println(小狗在吃 food); } public void drink(String water) { System.out.println(小狗在喝 water); } } interface abc { public void eat(String food); }现在已知 abc 这个接口有一个 eat 方法而 Dog 实现了 abc 接口重新了 eat 方法eat 方法需要增强。public static void main(String[] args) { Dog dog new Dog(); abc proxy (abc) Proxy.newProxyInstance(Dog.class.getClassLoader(), new Class[]{abc.class}, new InvocationHandler() { Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(小狗的eat方法被增强了); return method.invoke(dog, args); } }); proxy.eat(骨头); }Proxy 是代理的意思Proxy.newProxyInstance 就是创建代理实例的意思返回值就是代理类对象而代理类和被代理类通过接口建立联系代理类需要实现接口重写抽象方法才能进行增强所以代理类是接口实现类代理类对象是接口实现类对象。Proxy.newProxyInstance 的返回值是 Object 类型需要向下转型为接口类型。Proxy.newProxyInstance 的第一个参数是类加载器因为代理类是动态创建的需要使用其他类的类加载器加载到方法区中。类加载器可以随便选吗不能提供类加载器的类必须和接口类由同一个类加载器加载。Proxy.newProxyInstance 的第二个参数是 Class 数组需要传入接口的 class 对象。因为一个代理类可以实现多个接口所以这里是一个数组。Proxy.newProxyInstance 的第三个参数是一个匿名内部类。Object proxy这里的 proxy 其实就是代理类对象Method method这里是反射中的方法对象也就是原始方法Object[] args方法需要的参数在匿名内部类的 invoke 方法中可以进行前置处理通过反射 API 调用原始方法后置处理。有细心的同学肯定发现了匿名内部类的 invoke 方法有三个参数但是我只用了两个proxy 这个参数是怎么使用的呢其实在匿名内部类的 invoke 方法中还有一个变量可以使用就是被代理类对象 dog。dog 是被代理类对象proxy 是代理类对象。dog 调用 eat 方法不会被增强而 proxy 调用 eat 方法会再次进入 invoke 方法中会导致栈溢出。CGLibCGLib 不是 Java 原生支持的动态代理方案需要引入外部依赖不过在 spring-boot-starter-web 中已经包含这个依赖了只要引入这个依赖就可以了。还是同样的例子有一个 Dog 类里面有两个方法 eat 和 drink要对 eat 方法进行增强。在 JDK 中通过接口获取要增强的方法通过反射调用原始方法。在 CGLib 中是通过继承关系获取要增强的方法至于调用原始方法就变成了调用父类的方法。public static void main(String[] args) { Enhancer enhancer new Enhancer(); enhancer.setSuperclass(Dog.class); enhancer.setCallback(new MethodInterceptor() { Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println(小狗的eat方法被增强了); return proxy.invokeSuper(obj, args); } }); Dog dog (Dog) enhancer.create(); dog.eat(骨头); dog.drink(凉白开); }enhancer.setSuperclass 就是指定要继承哪个类代理类是被代理类的子类。enhancer.setCallback 就是编写增强逻辑需要传入一个匿名内部类。Object objobj 就是代理类对象也就是子类对象Method method跟 JDK 一样是原始方法对象Object[] args方法参数MethodProxy proxy方法代理跟第二个参数 method 建立了联系通过 invokeSuper 调用父类的方法也就是调用 method需要传入子类对象 obj 和方法需要的参数 argsenhancer.create 就是创建代理类对象不过返回的是 Object 实例需要向下转型成 Dog 类对象。但是CGLib 也有缺点那就是所有继承下来的方法都会被增强。代理类继承了 eat 方法和 drink 方法所以这两个方法执行时后会被增强打印同一条语句。而 JDK 是通过接口实现的代理类实现了接口只会增强接口里面的方法。SpringBoot AOP 默认优先使用 CGLib个人认为是 JDK 需要借助接口作为跳板不够灵活。