动态代理

2020-07-03  本文已影响0人  wly25

https://www.cnblogs.com/incognitor/p/9759987.html

#### 1. 动态代理的实现方式:

涉及3个类:

- 委托类对象/被代理对象 - 实际对象

- 实现InvocationHandler对象 - 包含对实际对象方法增强的实现

- 代理对象 Proxy.newProxyInstance(zhangsan.getClass().getClassLoader(),zhangsan.getClass().getInterfaces(), stuHandler);

#### 2. 动态代理的原理

1. newProxyInstance 封装创建代理对象的步骤 Class<?> cl = getProxyClass0(loader, intfs);

2. 在运行的过程中通过ProxyGenerator创建代理类的字节码文件并加载到JVM中,这样就获取到代理类的Class对象。 3. 通过class对象 通过反射获取构造函数对象(需要传入InvocationHandler), 通过构造函数创建代理类实例对象

3. 反编译代理类的字节码文件 代理类以$Proxy0来命名 且extends Proxy implements Person 继承Proxy类实现了和委托类相同的接口

extends Proxy 集成了父类的构造函数, 父类构造函数需要传入一个InvocationHandler对象, 这个就是我们在Proxy.newProxyInstance传入的nvocationHandler对象

实现了被代理对象相同的接口, 这样就和被代理对象有相同的方法, 可以通过代理类调用相同方法。

4. 代理的构造函数会传入一个InvocationHandler的实例  而InvocationHandler有持有一个委托类的实例

5. 代理类的内部会会通过反射维护接口所有方法实例 Method m1, m2

6. 所以代理类也可以调用和委托类相同的方法 因为实现了相同的接口  当通过代理类调用方法是 实际是上调用this.h.invoke(this, m3, null); 也就是InvocationHander的invoke方法 传入当前的invocationhandler实例和 对应的方法Methond, 以及参数

7. 这样 invoke方法执行增强代码部分 然后在通过其维护的委托类实例调用m3方法也就是 委托类的方法  这样就实现了动态代理

3. #### CGLib

Java原生的动态代理 只能代理实现接口的类  使用cglib来实现对继承类的动态代理  当然cdglib是不能代理final类的

```

package com.learn.proxy.inteface;

public interface IActor {

public void basicAct(float money);

public void dangeAct(float money);

}

package com.learn.proxy.inteface;

public class Actor implements IActor{

public void basicAct(float money) {

  System.out.println("拿到钱 开始基本表演 " + money);

}

public void dangeAct(float money) {

  System.out.println("拿到钱 开始危险表演 " + money);

}

}

package com.learn.proxy.inteface;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

/**

* 动态代理:

作用:不改变源码的基础上 对已有的方法增强 (它是AOP思想的实现技术)

分类:

  基于接口的动态代理

  要求: 被代理对象最少实现一个接口

  提供者: JDK 官方

  设计的类:Proxy

  创建代理类的方法: newProxyInstance(ClassLoader, class[], InvocationHandler)

    参数的含义:

    ClassLoader:类加载器 和被代理对象使用相同的类加载器 一般都是固定写法 比如要代理xxx 写法就是xxx.getClass().getClassLoader()

    Class[]: 字节码数组 被代理类实现的接口 要求被代理对象和代理对象具有相同的行为 一般也是固定写法 xxx.getClass().getInterfaces()

            InvocationHandler: 它是一个接口 就是用于我们提供增强代码的  我们一般都是些一个该接口的实现类 实现类可以是匿名内部类

    它的含义就是 如何代理 此处代码只能谁用谁提供

    策略模式:

      使用要求: 数据已经有了  目的明确  达到目标的过程就是侧罗

      在dbutils中的ResultSetHandler就是策略模式的具体应用

* @author LJ

*

*/

public class Client {

public static void main(String[] args) {

  final Actor actor = new Actor();

// actor.basicAct(1000f);

// actor.dangeAct(5000f);

 

  IActor proxyActor = (IActor)Proxy.newProxyInstance(actor.getClass().getClassLoader(), actor.getClass().getInterfaces(), new InvocationHandler(){

  /**

    * 执行被代理对象任何方法 都会先执行下面的方法 该方法有类似拦截的功能

    * 方法参数:

    *  Object proxy: 代理对象的引用    不一定每次都用

    *  Method method: 当前执行的方法

    *  Object[]args: 当前执行方法所需要的参数

    * 

    *  返回值: 当前执行方法的返回值

    */

  @Override

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    Object rtValue = null;

    // 1. 取出方法中的参数

    Float money = (Float) args[0];

    // 2. 判断当前执行的是什么方法

    if("basicAct".equals(method.getName())) {

    //基本表演

    if(money > 10000) {

      rtValue = method.invoke(actor, money);

    }

    }

    if("dangeAct".equals(method.getName())) {

    //基本表演

    if(money > 50000) {

      rtValue = method.invoke(actor, money);

    }

    }   

    return rtValue;

  } 

  });

 

  proxyActor.basicAct(20000f);

  proxyActor.dangeAct(150000f);

}

}

```

### 引入场景

动态代理在Java中是很重要的一部分,在很多框架中都会用到,如Spring中的AOP、Hadoop中的RPC等。

如果要对第三方提供的JAR包中的某个类中的某个方法的前后加上自己的逻辑,比如打log ,注意此时我们只有第三方提供的CLASS文件,因此根本不可能去修改别人的源代码,那该怎么办?

有两种方法可以实现,一种是利用继承,另一种是利用聚合。举例说明:

假设第三方中提供一个Run接口,里面只一个run方法,以及它的实现类Person

```

public interface Run { 

    public void run(); 

}

public class Person implements Run { 

    @Override 

    public void run() { 

        System.out.println("person running..."); 

    } 

}

```

第一种利用继承实现

```

public class SuperMan1 extends Person {   

    @Override 

    public void run() { 

        //方法开始前打LOG 

        LOG.info("super man1 before run..."); 

        super.run(); 

        //方法结束后打LOG 

        LOG.info("super man1 after run..."); 

    }   

}

```

第二种利用聚合实现

```

public class SuperMan2 implements Run { 

    private Run person;   

    public SuperMan2(Run person) { 

        this.person = person; 

    } 

 

    @Override 

    public void run() { 

        //方法开始前打LOG 

        LOG.info("super man2 before run..."); 

        person.run(); 

        //方法结束后打LOG 

        LOG.info("super man2 after run..."); 

    }   

}

```

这两种实现方式,哪一种更好呢?

显然是第二种利用聚合实现方法好,因为这种方式很灵活,同时又不会有多层的父子类关系。而继承最不好的地方就是不灵活,同时会很容易形成臃肿的父子类关系,不利于后期的维护

其实SuperMan1类和SuperMan2类都是Person类的代理类,对Person类中的方法进行加强,只不过这种代理是静态代理,很受限制。因为SuperMan1和SuperMan2只能代理Run类型的类,其它类型没法代理。如果有其他接口比如Fly 就需要额外在写代理类,有多少个接口就要单独实现多少个代理类。 

为了解决这个问题,Java中引入动态代理。

### Java动态代理原理

**动态代理的意思就是一个类的(比如Person)的代理类(比如SuperMan2)是动态生成的,也就是说这个代理类不是提前写好的,是在程序运行时动态的生成的**

动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。例如,这里的方法计时,所有的被代理对象执行的方法都会被计时,然而我只做了很少的代码量

一个典型的动态代理创建对象过程可分为以下四个步骤:

1、通过实现InvocationHandler接口创建调用处理器

```

IvocationHandler handler = new InvocationHandlerImpl(...);

```

2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类

```

Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});

```

反编译代理类字节码

```Java

import com.xych.proxy.jdkproxy.IDao;

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 IDao {

    private static Method m1;

    private static Method m4;

    private static Method m2;

    private static Method m0;

    private static Method m3;

    public $Proxy0(InvocationHandler paramInvocationHandler) throws Throwable  {

        super(paramInvocationHandler);

    }

    public final boolean equals(Object paramObject)  {

        try {

            return ((Boolean) this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();

        } catch(Error | RuntimeException localError) {

            throw localError;

        } catch(Throwable localThrowable) {

            throw new UndeclaredThrowableException(localThrowable);

        }

    }

    public final void save() {

        try {

            this.h.invoke(this, m4, null);

            return;

        } catch(Error | RuntimeException localError) {

            throw localError;

        } catch(Throwable localThrowable) {

            throw new UndeclaredThrowableException(localThrowable);

        }

    }

    public final String toString()  {

        try {

            return (String) this.h.invoke(this, m2, null);

        } catch(Error | RuntimeException localError) {

            throw localError;

        } catch(Throwable localThrowable)  {

            throw new UndeclaredThrowableException(localThrowable);

        }

    }

    public final int hashCode()  {

        try {

            return ((Integer) this.h.invoke(this, m0, null)).intValue();

        } catch(Error | RuntimeException localError) {

            throw localError;

        } catch(Throwable localThrowable) {

            throw new UndeclaredThrowableException(localThrowable);

        }

    }

    public final void update() {

        try

        {

            this.h.invoke(this, m3, null);

            return;

        }catch(Error | RuntimeException localError) {

            throw localError;

        } catch(Throwable localThrowable) {

            throw new UndeclaredThrowableException(localThrowable);

        }

    }

    static  {

        try {

            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });

            m4 = Class.forName("com.xych.proxy.jdkproxy.IDao").getMethod("save", new Class[0]);

            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);

            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);

            m3 = Class.forName("com.xych.proxy.jdkproxy.IDao").getMethod("update", new Class[0]);

            return;

        } catch(NoSuchMethodException localNoSuchMethodException)  {

            throw new NoSuchMethodError(localNoSuchMethodException.getMessage());

        } catch(ClassNotFoundException localClassNotFoundException)  {

            throw new NoClassDefFoundError(localClassNotFoundException.getMessage());

        }

    }

}

```

$Proxy0继承Proxy继承,在Proxy里有一个成员变量h。并通过构造函数复制。 这个h就是在newProxyInstance传入的

```

protected InvocationHandler h;

```

jdk在程序运行过程中动态为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类字节码文件,然后编译到JVM中 这样就获取到$Proxy0的class对象

然后通过class对象 通过反射机制获取动态代理类的构造函数,其参数类型InvocationHandler接口类型

```

Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});

```

通过构造函数创建代理类实例,此时需传入已经实现InvocationHandler接口的对象作为参数被传入 这样就创建了代理类的实例对象

```

Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));

```

为了简化对象创建过程,Proxy类中的newProxyInstance方法封装了2~4,只需两步即可完成代理对象的创建。

```

Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(),

        new Class[]{Subject.class}, new InvocationHandlerImpl (real));

```

通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。

首先来看static代码块

```

static  {

        try {

            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });

            m4 = Class.forName("com.xych.proxy.jdkproxy.IDao").getMethod("save", new Class[0]);

            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);

            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);

            m3 = Class.forName("com.xych.proxy.jdkproxy.IDao").getMethod("update", new Class[0]);

            return;

        } catch(NoSuchMethodException localNoSuchMethodException)  {

            throw new NoSuchMethodError(localNoSuchMethodException.getMessage());

        } catch(ClassNotFoundException localClassNotFoundException)  {

            throw new NoClassDefFoundError(localClassNotFoundException.getMessage());

        }

    }

```

IDao接口定义了两个方法save、update,加上Object类的equals、toString、hashCode这三个方法,总共5个方法,即上面代码中的m1-m5成员变量,并在’静态代码块’里初始化了这5个成员变量。

- m0-m3 反射获取Object类中的三个方法: quals、toString、hashCode

- m4-mxx 反射获取实现接口中的方法

同时$Proxy0本身也实现了IDao接口并重写了equals、toString、hashCode方法

```

public final class $Proxy0 extends Proxy implements IDao`

```

而$Proxy0本身有h成员变量,代理类调用自己这些方法时 都是通过统一调用this.h.invoke方法类执行

我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中传入了对应的方法m0-mxx

```

this.h.invoke(this, m3, null);

```

而在通过反射执行方法时 

```

rtValue = method.invoke(actor, money);

```

实际执行了被代理对象actor的相应方法。

通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。

,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。

上一篇下一篇

猜你喜欢

热点阅读