> 文章列表 > 动态代理是什么?应用场景?怎么实现?

动态代理是什么?应用场景?怎么实现?

动态代理是什么?应用场景?怎么实现?

动态代理:在运行时,创建目标类,可以调用和扩展目标类的方法

Java 中实现动态的方式:

  • JDK 中的动态代理 
  • Java类库 CGLib

应用场景:

  • 统计每个 api 的请求耗时
  • 统一的日志输出
  • 校验被调用的 api 是否已经登录和权限鉴定
  • Spring的 AOP 功能模块就是采用动态代理的机制来实现切面编程

怎么实现

  • JDK 动态代理
  • CGLib 动态代理
  • 使用 Spring aop 模块完成动态代理功能

动态代理的应用:Spring的AOP,加事务,加权限,加日志,自定义拦截方法。

下面深入了解一下:

 

JDK 动态代理

首先我们先来看一下动态代理的执行过程

 

 在 JDK 动态代理中,实现了 InvocationHandler 的类可以看作是 代理类(因为类也是一种对象,所以我们上面为了描述关系,把代理类形容成了代理对象)。JDK 动态代理就是围绕实现了 InvocationHandler 的代理类进行的,比如下面就是一个 InvocationHandler 的实现类,同时它也是一个代理类。

public class UserHandler implements InvocationHandler {private UserDao userDao;public UserHandler(UserDao userDao){this.userDao = userDao;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {saveUserStart();Object obj = method.invoke(userDao, args);saveUserDone();return obj;}public void saveUserStart(){System.out.println("---- 开始代理 ----");}public void saveUserDone(){System.out.println("---- 代理完成 ----");}
}

 

代理类一个最最最重要的方法就是 invoke 方法,它有三个参数

Object proxy: 动态代理对象,关于这个方法我们后面会说。

Method method: 表示最终要执行的方法,method.invoke 用于执行被代理的方法,也就是真正的目标方法

Object[] args: 这个参数就是向目标方法传递的参数。

我们构造好了代理类,现在就要使用它来实现我们对目标对象的调用,那么如何操作呢?请看下面代码。

public static void dynamicProxy(){UserDao userDao = new UserDaoImpl();InvocationHandler handler = new UserHandler(userDao);ClassLoader loader = userDao.getClass().getClassLoader();Class<?>[] interfaces = userDao.getClass().getInterfaces();UserDao proxy = (UserDao)Proxy.newProxyInstance(loader, interfaces, handler);proxy.saveUser();
}

如果要用 JDK 动态代理的话,就需要知道目标对象的类加载器、目标对象的接口,当然还要知道目标对象是谁。构造完成后,我们就可以调用 Proxy.newProxyInstance方法,然后把类加载器、目标对象的接口、目标对象绑定上去就完事儿了。

这里需要注意一下 Proxy 类,它就是动态代理实现所用到的代理类。

Proxy 位于java.lang.reflect 包下,这同时也旁敲侧击的表明动态代理的本质就是反射。

下面我们就围绕 JDK 动态代理,来深入理解一下它的原理,以及搞懂为什么动态代理的本质就是反射。

 

动态代理的实现原理

在了解动态代理的实现原理之前,我们先来了解一下 InvocationHandler 接口

InvocationHandler 接口

 

注释: 每个代理实例都有一个关联的调用处理程序。

*当在代理实例上调用方法时

*调用被编码并调度到{@code invoke}

*其调用处理程序的方法

 

动态代理的优势在于能够很方便的对代理类中方法进行集中处理,而不用修改每个被代理的方法。因为所有被代理的方法(真正执行的方法)都是通过在 InvocationHandler 中的 invoke 方法调用的。所以我们只需要对 invoke 方法进行集中处理。

invoke 方法只有三个参数

public Object invoke(Object proxy, Method method, Object[] args)
  • proxy:代理对象

  • method: 代理对象调用的方法

  • args:调用方法中的参数。

 

动态代理的整个代理过程不像静态代理那样一目了然,清晰易懂,因为在动态代理的过程中,我们没有看到代理类的真正代理过程,也不明白其具体操作,所以要分析动态代理的实现原理,我们必须借助源码。

那么问题来了,首先第一步应该从哪分析?如果不知道如何分析的话,干脆就使用倒推法,从后往前找,我们直接先从 Proxy.newProxyInstance入手,看看是否能略知一二。

 

Proxy.newInstance 方法分析

Proxy 提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类

Proxy.newProxyInstance 源码(java.lang.reflect.Proxy)

 

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
{Objects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}Class<?> cl = getProxyClass0(loader, intfs);try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}return cons.newInstance(new Object[]{h});} catch (Exception e) {...
}

 

从上图中我们也可以看出,newProxyInstsance 方法最重要的几个环节就是获得代理类、获得构造器,然后构造新实例。

对反射有一些了解的同学,应该会知道获得构造器和构造新实例是怎么回事。

 

所以我们的重点就放在了获得代理类,这是最关键的一步,对应源码中的 Class<?> cl = getProxyClass0(loader, intfs); 我们进入这个方法一探究竟

private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {if (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded");}return proxyClassCache.get(loader, interfaces);
}

 这个方法比较简单,首先会直接判断接口长度是否大于 65535(刚开始看到这里我是有点不明白的,我心想这个判断是要判断什么?interfaces 这不是一个 class 类型吗,从 length 点进去也看不到这个属性,细看一下才明白,这居然是可变参数,Class ... 中的 ... 就是可变参数,所以这个判断我猜测应该是判断接口数量是否大于 65535。)

然后会直接从 proxyClassCache 中根据 loader 和 interfaces 获取代理对象实例。如果能够根据 loader 和 interfaces 找到代理对象,将会返回缓存中的对象副本;否则,它将通过 ProxyClassFactory 创建代理类。

proxyClassCache.get 就是一系列从缓存中的查询操作,注意这里的 proxyClassCache 其实是一个 WeakCache,WeakCahe 也是位于 java.lang.reflect 包下的一个缓存映射 map,它的主要特点是一个弱引用的 map,但是它内部有一个 SubKey ,这个子键却是强引用的。

这里我们不用去追究这个 proxyClassCache 是如何进行缓存的,只需要知道它的缓存时机就可以了:即在类加载的时候进行缓存。

如果无法找到代理对象,就会通过 ProxyClassFactory 创建代理,ProxyClassFactory 继承于 BiFunction

private static final class ProxyClassFactoryimplements BiFunction<ClassLoader, Class<?>[], Class<?>>{...}

ProxyClassFactory 里面有两个属性一个方法。

 其中apply方法是一个根据接口和类加载器进行代理实例创建的工厂方法,下面是这段代码的核心。

 我们看到代理类继承了 Proxy 类,所以也就决定了 Java 动态代理只能对接口进行代理

 

 总结: JDK提供了java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy类实现JDK动态代理,只能对接口进行代理。

cglib的动态代理

基于父类的动态代理技术

例子:

1. 创建一个目标类

public class Target{public void coreWork() {System.out.println("===核心业务方法运行===");}
}

 2. 创建一个增强方法类

public class Advice {public void before(){System.out.println("对核心业务方法执行前的增强......");}public void after(){System.out.println("后置增强......");}
}

3. 动态代理测试

public class ProxyMain {public static void main(String[] args) {Target target = new Target();Advice advice = new Advice();// 1. 创建增强器Enhancer enhancer = new Enhancer();// 2. 设置增强目标类enhancer.setSuperclass(target.getClass());// 3. 设置回调enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {advice.before();    //  前置增强Object invoke = method.invoke(target, args);advice.after();     //后置增强return invoke;}});// 4. 创建代理Target targetProxy = (Target) enhancer.create();targetProxy.coreWork();}
}

 

 总结: CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承。所以不能对final修饰的类进行代理。

总体总结:CGLib创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLib创建对象时所花费的时间却比JDK多的的。目的是为了降低耦合度,提高代码的复用性。