动态代理
代理模式
代理模式是一种比较好理解的设计模式。
简单来说就是:我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的能力,实现功能的增强。
生活中的代购、租房中介、售票黄牛、婚介、经纪人、快递、事务代理、非侵入式日志监听等,都是代理 模式的实际体现。
使用代理模式主要有两个目的:
- 一是保护目标对象
- 二是增强目标对象功能(比如说在目标对象的某个方法执行前后增加一些自定义的操作)
代理模式一般包含三种角色:
- 抽象角色(Subject):抽象角色的主要职责是声明真实类与代理类的共同接口方法,该类可以是接口也可以是抽象类
- 真实角色(RealSubject):该角色也被称为被代理类,该类定义了代理所表示的真实对象,是负责执行系统真正的逻辑业务对象
- 代理角色(Proxy):也被称为代理类,其内部持有
Real Subject
的引用,因此具备完全的对 RealSubject 的代理权。客户端调用代理对象的方法,同时也调用被代理对象的方法,但是会在代理对象前后增加一些处理代码。
一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。代理模式属于结构型模式,分为静态代理和动态代理。
静态代理
静态代理是代理类在编译期间就创建好的,不是编译器生成的代理类,而是手动创建的类。在编译时就已经将接口,被代理类,代理类等确定下来。软件设计中所指的代理一般是指静态代理,也就是在代码中显式指定的代理。
下面我们通过一个简单的案例,来了解下静态代理。
Cat.java1 2 3 4 5 6 7 8 9
|
public interface Cat { public String eatFood(String foodName); public boolean running(); }
|
Lion.java1 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
|
public class Lion implements Cat { private String name; private int runningSpeed; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getRunningSpeed() { return runningSpeed; } public void setRunningSpeed(int runningSpeed) { this.runningSpeed = runningSpeed; } public Lion() { } @Override public String eatFood(String foodName) { String eat = this.name + " Lion eat food. foodName = " + foodName; System.out.println(eat); return eat; } @Override public boolean running() { System.out.println(this.name + " Lion is running . Speed :" + this.runningSpeed); return false; } }
|
代理类角色(FeederProxy)
FeederProxy.java1 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
|
public class FeederProxy implements Cat { private Cat cat; public FeederProxy(){} public FeederProxy(Cat cat) { if (cat instanceof Cat) { this.cat = cat; } } public void setCat(Cat cat) { if (cat instanceof Cat) { this.cat = cat; } } @Override public String eatFood(String foodName) { System.out.println("proxy Lion exec eatFood "); return cat.eatFood(foodName); } @Override public boolean running() { System.out.println("proxy Lion exec running."); return cat.running(); } }
|
静态代理类测试
staticProxyTest.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public class staticProxyTest { public static void main(String[] args) { Lion lion = new Lion(); lion.setName("狮子 小王"); lion.setRunningSpeed(100);
Cat proxy = new FeederProxy(lion); System.out.println(Thread.currentThread().getName()+" -- " + proxy.eatFood("水牛")); proxy.running(); } }
|
静态代理很好的诠释了代理设计模式,代理模式最主要的就是有一个公共接口(Cat),一个委托类(Lion),一个代理类(FeederProxy),代理类持有委托类的实例,代为执行具体类实例方法。
代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指客户端不直接调用实际对象的方法,客户端依赖公共接口并使用代理类。 那么我们在代理过程中就可以加上一些其他用途。
就这个例子来说在 eatFood 方法调用中,代理类在调用具体实现类之前添加System.out.println(“proxy Lion exec eatFood “);语句 就是添加间接性带来的收益。代理类存在的意义是为了增加一些公共的逻辑代码。
静态代理的缺陷
- 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
- 代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。
- 静态代理一个代理只能代理一种类型,而且是在编译器就已经确定被代理的对象。
动态代理
jdk动态代理模式是利用java中的反射技术,在运行时动态创建代理类。
JDK 动态代理
基于 JDK 的动态代理涉及到两个核心的类:Proxy类
和 InvocationHandler接口
JDK动态代理的写法比较固定,需要先定义一个接口和接口的实现类,然后再定义一个实现了InvocationHandler接口的实现类。最终调用Proxy类的newInstance()方法即可。示例代码如下:
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| public interface UserService { int insert(); String query(); }
public class UserServiceImpl implements UserService{ @Override public int insert() { System.out.println("insert"); return 0; }
@Override public String query() { System.out.println("query"); return null; } }
public class UserServiceInvocationHandler implements InvocationHandler { private Object target;
public UserServiceInvocationHandler(Object target){ this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("invocation handler"); return method.invoke(target,args); } }
public class MainApplication {
public static void main(String[] args) { ClassLoader classLoader = MainApplication.class.getClassLoader(); Class[] classes = new Class[]{UserService.class}; InvocationHandler invocationHandler = new UserServiceInvocationHandler(new UserServiceImpl()); UserService userService = (UserService) Proxy.newProxyInstance(classLoader, classes, invocationHandler); userService.insert(); } }
Output: invocation handler insert
|
jdk实现动态代理可以分为以下几个步骤:
- 先检查委托类是否实现了相应接口,保证被访问方法在接口中也要有定义
- 创建一个实现InvocationHandler接口的类
- 在类中定义一个被代理对象的成员属性,为了扩展方便可以直接使用Object类,也可以根据需求定义相应的接口
- 在invoke方法中实现对委托对象的调用,根据需求对方法进行增强
- 使用Proxy.newProxyInstance(…)方法创建代理对象,并提供要给获取代理对象的方法
下面我们通过一些手段,来看下jdk动态代理生成的代理类长什么样子。
手段一:
自定义的一个小工具类将动态生成的代理类保存到本地
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
|
public class ProxyUtils {
public static boolean saveProxyClass(String path, String proxyClassName, Class[] interfaces){ if (proxyClassName == null || path == null) { return false; } byte[] classFile = ProxyGenerator.generateProxyClass(proxyClassName, interfaces); try (FileOutputStream out = new FileOutputStream(path)) { out.write(classFile); out.flush(); } catch (IOException e) { e.printStackTrace(); return false; } return true; } }
|
手段二:
在代码中添加一行代码:System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true")
,就能实现将程序运行过程中产生的动态代理对象的class文件写入到磁盘。如下示例:
- 运行程序,最终发现在项目的根目录下出现了一个包:com.sun.proxy。包下有一个文件
$Proxy0.class
。在idea打开,发现就是所产生代理类的源代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MainApplication { public static void main(String[] args) { System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); ClassLoader classLoader = MainApplication.class.getClassLoader(); Class[] classes = new Class[]{UserService.class}; InvocationHandler invocationHandler = new UserServiceInvocationHandler(new UserServiceImpl()); UserService userService = (UserService) Proxy.newProxyInstance(classLoader, classes, invocationHandler); userService.insert(); } }
|
通过上面两种方法获取到代理对象的源代码如下:
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| package com.sun.proxy;
import com.tiantang.study.UserService; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements UserService { private static Method m1; private static Method m3; private static Method m4; private static Method m2; private static Method m0;
public $Proxy0(InvocationHandler var1) throws { super(var1); }
public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } }
public final int insert() throws { try { return (Integer)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
public final String query() throws { try { return (String)super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("com.tiantang.study.UserService").getMethod("insert"); m4 = Class.forName("com.tiantang.study.UserService").getMethod("query"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
|
通过源码我们发现,$Proxy0
类继承了Proxy类,同时实现了UserService接口。到这里,我们的问题一就能解释了,为什么JDK的动态代理只能基于接口实现,不能基于继承来实现?
因为Java中不支持多继承,而JDK的动态代理在创建代理对象时,默认让代理对象继承了Proxy类,所以JDK只能通过接口去实现动态代理。
$Proxy0
实现了UserService接口,所以重写了接口中的两个方法($Proxy0
同时还重写了Object类中的几个方法)。所以当我们调用query()方法时,先是调用到$Proxy0.query()
方法,在这个方法中,直接调用了super.h.invoke()方法,父类是Proxy,父类中的h
就是我们定义的InvocationHandler,所以这儿会调用到UserServiceInvocationHandler.invoke()方法。因此当我们通过代理对象去执行目标对象的方法时,会先经过InvocationHandler的invoke()方法,然后在通过反射method.invoke()去调用目标对象的方法,因此每次都会先打印invocation handler
这句话。
CGLIB 动态代理
cglib动态代理和jdk动态代理类似,也是采用操作字节码机制,在运行时生成代理类。cglib 动态代理采取的是创建目标类的子类的方式,因为是子类化,我们可以达到近似使用被调用者本身的效果。
字节码处理机制-指得是ASM来转换字节码并生成新的类
注:spring中有完整的cglib相关的依赖,所以以下代码基于spring官方下载的demo中直接进行编写的
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
|
public class OrderService {
public void saveOrder(String orderInfo) throws InterruptedException { Thread.sleep(System.currentTimeMillis() & 100); System.out.println("订单:" + orderInfo + " 保存成功"); } }
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target) { this.target = target; }
public Object getProxyInstance(){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); }
@Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
System.out.println("cglib代理:保存订单用时: " + (System.currentTimeMillis() - start) + "ms"); return result; } }
public class Client { public static void main(String[] args) throws InterruptedException { OrderService orderService = new OrderService(); OrderService orderServiceProxy = (OrderService) new ProxyFactory(orderService).getProxyInstance(); String saveFileName = "CglibOrderServiceDynamicProxy.class"; ProxyUtils.saveProxyClass(saveFileName, orderService.getClass().getSimpleName(), new Class[]{IOrderService.class}); orderServiceProxy.saveOrder(" cruder 新买的花裤衩 "); } }
|
cglib动态代理实现步骤和jdk及其相似,可以分为以下几个步骤:
- 创建一个实现MethodInterceptor接口的类
- 在类中定义一个被代理对象的成员属性,为了扩展方便可以直接使用Object类,也可以根据需求定义相应的接口
- 在invoke方法中实现对委托对象的调用,根据需求对方法进行增强
- 使用Enhancer创建生成代理对象,并提供要给获取代理对象的方法
cglib动态代理生成的代理类和jdk动态代理代码格式上几乎没有什么区别,唯一的区别在于cglib生成的代理类继承了仅仅Proxy类,而jdk动态代理生成的代理类继承了Proxy类的同时也实现了一个接口。代码如下:
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 45 46 47 48 49 50 51 52
| public final class OrderService extends Proxy { private static Method m1; private static Method m2; private static Method m0;
public OrderService(InvocationHandler var1) throws { super(var1); }
public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } }
public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
|
jdk proxy vs cglib
JDK Proxy 的优势:
- 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
- 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
- 代码实现简单。
cglib 优势:
- 有的时候调用目标可能不便实现额外接口,从某种角度看,限定调用者实现接口是有些侵入性的实践,类似 cglib 动态代理就没有这种限制。
- 只操作我们关心的类,而不必为其他相关类增加工作量。
代理模式: 为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。
jdk动态代理生成的代理类继承了Proxy类并实现了被代理的接口;而cglib生成的代理类则仅继承了Proxy类。
jdk动态代理最大缺点:只能代理接口,既委托类必须实现相应的接口
cglib缺点:由于是通过“子类化”的方式, 所以不能代理final的委托类或者普通委托类的final修饰的方法。
动态代理导致spring事务失效的场景: https://www.jianshu.com/p/3dd79531fe41