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

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

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;
    }
 
    @Override
    public 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 ProxyClassFactory
        implements 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() {
            @Override
            public 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多的的。目的是为了降低耦合度,提高代码的复用性。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值