动态代理学习

动态代理

代理模式(Proxy)

定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用

在代理模式中,是需要代理对象和目标对象实现同一个接口(如果是不同的接口,那就是适配器模式了)
代理模式最最最主要的原因就是,在不改变目标对象方法的情况下对方法进行增强,比如,我们希望对方法的调用增加日志记录,或者对方法的调用进行拦截,等等…

举例:

有接口有say方法:

public interface subjectInterface {
    public void saySomething(String num);
}

有实例类实现该方法:

public class kakaSay implements subjectInterface {
    @Override
    public void saySomething(String num) {
        System.out.println("real subject"+num);
    }
}

现在需要在say方法被调用的时候,记录方法被调用的时间,最直接的就是修改该实例的say方法,但是这样做的弊端就是如果有很多实现了subjectInterface接口的类,那就需要修改多处代码,而且这样的修改可能会导致其他的代码出问题(可能并不是所有的say都需要记录调用时间)。怎么办呢,这时候代理就要登场了!

静态代理

这样我们需要新建一个Proxy类同样实现subjectInterface接口,将要代理的对象传递进来,这样就可以在不修改kakasay的say方法的情况下实现了我们的需求。这其实就是静态代理

  • 静态代理类:由程序员创建或者由第三方工具生成,再进行编译;在程序运行之前,代理类的.class文件已经存在了。
  • 静态代理类通常只代理一个类。(接口与代理类是1对1的,有多个接口需要代理,就需要新建多个代理类,繁琐,类爆炸。)
  • 静态代理事先知道要代理的是什么。

代码实现:

public class kakaProxy implements subjectInterface {
     private subjectInterface target;

    public kakaJTProxy(subjectInterface target) {
        this.target = target;
    }

    public subjectInterface getTarget() {
        return target;
    }

    @Override
    public void saySomething(String num) {
        if(target!=null){
            System.out.println("kakaSay.saySomething invoke at"+System.currentTimeMillis());
            target.saySomething(num);
        }
    }
}

静态代理的问题:接口与代理类是1对1的,有多个接口需要代理,就需要新建多个代理类,繁琐,类爆炸。

动态代理

与静态代理的区别:

(1)Proxy类的代码被固定下来,不会因为业务的逐渐庞大而庞大;

(2)可以实现AOP编程,这是静态代理无法实现的;

(3)解耦,如果用在web业务下,可以实现数据层和业务层的分离。

(4)动态代理的优势就是实现无侵入式的代码扩展。

  • 动态代理类:在程序运行时,通过反射机制动态生成。
  • 动态代理类通常代理接口下的所有类。
  • 动态代理事先不知道要代理的是什么,只有在运行的时候才能确定。(反射的缘故??)
  • 动态代理的调用处理程序必须事先InvocationHandler接口,及使用Proxy类中的newProxyInstance方法动态的创建代理类。
  • Java动态代理只能代理接口,要代理类需要使用第三方的CLIGB等类库

在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。

Proxy类:

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods. 

Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法(这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义:):

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载

interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了

h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

InvocationHandler接口:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance. 
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法(唯一方法)来进行调用

InvocationHandler这个接口的唯一一个方法 invoke 方法:

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

proxy:  指代我们所代理的那个真实对象
method:  指代的是我们所要调用真实对象的某个方法的Method对象
args:  指代的是调用真实对象某个方法时接受的参数

动态代理栗子:

public class kakaDYProxy implements InvocationHandler {
    private subjectInterface target;
    public subjectInterface getTarget() {
        return target;
    }

    public kakaDYProxy(subjectInterface target) {
        this.target = target;
    }
    /*
    proxy: 指通过 Proxy.newProxyInstance() 生成的代理类对象
    method: 指的是我们所要调用真实对象的某个方法的Method对象
    args:  指的是调用真实对象某个方法时接受的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("动态代理开始前可加操作");
        System.out.println("method--:"+method);
        // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用,反射调用
        method.invoke(target,args);
        System.out.println("动态代理之后可加操作");
        return null;
    }
}

从InvocationHandler这个名称我们就可以知道,实现了这个接口的中介类用做**“调用处理器”**。**当我们调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。**这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。

public class TestMainDT {
    public static void main(String[] args) {
        // 我们要代理的真实对象
        subjectInterface realSubject = new kakaSay();//这里指定被代理类

        // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler handler = new kakaDYProxy(realSubject);

        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader(),我们这里使用代理类的类加载器,也就是被代理的那个真实对象
         * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
         * 第三个参数handler,我们这里将这个代理对象关联到了上方的InvocationHandler 这个对象上
         */
        subjectInterface subject = (subjectInterface) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(), handler);


        //验证subject是Proxy的实例,实现了subject接口
        System.out.println(subject instanceof Proxy);

        //这里可以看出subject的Class类是$Proxy0,这个$Proxy0类继承了Proxy,实现了subjectInterface接口
        System.out.println("subject的Class类是:"+subject.getClass().toString());

        System.out.print("subject中的本类属性有:");
        Field[] field=subject.getClass().getDeclaredFields();
        for(Field f:field){
            System.out.print(f.getName()+", ");
        }

        System.out.print("\n"+"subject中的本类方法有:");
        Method[] method=subject.getClass().getDeclaredMethods();
        for(Method m:method){
            System.out.print(m.getName()+", ");
        }

        System.out.println("\n"+"subject的父类是:"+subject.getClass().getSuperclass());
        System.out.print("\n"+"subject实现的接口是:");
        Class[] interfaces=subject.getClass().getInterfaces();
        for(Class i:interfaces){
            System.out.print(i.getName()+", ");
        }
        System.out.println(subject.getClass().getName());
        System.out.println("\n\n"+"运行结果为:");
        subject.saySomething("003");
    }
}

//运行结果:
true
subject的Class类是:class com.sun.proxy.$Proxy0

	/*
	可能以为返回的这个代理对象会是subjectInterface类型的对象,或者是InvocationHandler的对象,结果却	不是,首先我们解释一下**为什么我们这里可以将其转化为subjectInterface类型的对象?**原因就是在		newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就	会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是	subjectInterface类型,所以就可以将其转化为subjectInterface类型了。

	**同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,		它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对		象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号**。
	*/

subject中的本类属性有:m1, m2, m3, m0, 

subject中的本类方法有:equals, toString, hashCode, saySomething, 
subject的父类是:class java.lang.reflect.Proxy

subject实现的接口是:ProxyTest.PT01.subjectInterface, com.sun.proxy.$Proxy0


运行结果为:
动态代理开始前可加操作
method--:public abstract void ProxyTest.PT01.subjectInterface.saySomething(java.lang.String)

		/*正好就是我们的subjectInterface接口中的方法,这也就证明了当我通过代理对象来调用方法的时			候,起实际就是委托由其关联到的 handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过			代理的方式来调用的。
		*/

real subject003
动态代理之后可加操作


	/*
	这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 		中的invoke方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject类型的参数,表示我要代理	的就是这个真实对象,所以此时就会调用 handler 中的invoke方法去执行:
	*/

动态代理源码分析

我们利用Proxy.newProxyInstance创建动态代理对象,打开newProxyInstance()方法源码

/**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
     *
     * <p>{@code Proxy.newProxyInstance} throws
     * {@code IllegalArgumentException} for the same reasons that
     * {@code Proxy.getProxyClass} does.
     *
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     * @return  a proxy instance with the specified invocation handler of a
     *          proxy class that is defined by the specified class loader
     *          and that implements the specified interfaces
     * @throws  IllegalArgumentException if any of the restrictions on the
     *          parameters that may be passed to {@code getProxyClass}
     *          are violated
     * @throws  SecurityException if a security manager, <em>s</em>, is present
     *          and any of the following conditions is met:
     *          <ul>
     *          <li> the given {@code loader} is {@code null} and
     *               the caller's class loader is not {@code null} and the
     *               invocation of {@link SecurityManager#checkPermission
     *               s.checkPermission} with
     *               {@code RuntimePermission("getClassLoader")} permission
     *               denies access;</li>
     *          <li> for each proxy interface, {@code intf},
     *               the caller's class loader is not the same as or an
     *               ancestor of the class loader for {@code intf} and
     *               invocation of {@link SecurityManager#checkPackageAccess
     *               s.checkPackageAccess()} denies access to {@code intf};</li>
     *          <li> any of the given proxy interfaces is non-public and the
     *               caller class is not in the same {@linkplain Package runtime package}
     *               as the non-public interface and the invocation of
     *               {@link SecurityManager#checkPermission s.checkPermission} with
     *               {@code ReflectPermission("newProxyInPackage.{package name}")}
     *               permission denies access.</li>
     *          </ul>
     * @throws  NullPointerException if the {@code interfaces} array
     *          argument or any of its elements are {@code null}, or
     *          if the invocation handler, {@code h}, is
     *          {@code null}
     */
    @CallerSensitive
    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);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        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 (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

在生成代理类的过程中会进行一些列检查,比如访问权限等,查看getProxyClass0()方法;

对生产的代理类进行了缓存。。。

/**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
        // proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
    }

发现重点是ProxyClassFactory类,通过反射获得接口的修饰符、包名等,根据 规则命名代理类;

然后调用ProxyGenerator.generateProxyClass 生成代理类。

public static byte[] generateProxyClass(final String name,  
                                           Class[] interfaces)  
   {  
       ProxyGenerator gen = new ProxyGenerator(name, interfaces);  
    // 这里动态生成代理类的字节码,由于比较复杂就不进去看了  
       final byte[] classFile = gen.generateClassFile();  
  
    // 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上  
       if (saveGeneratedFiles) {  
           java.security.AccessController.doPrivileged(  
           new java.security.PrivilegedAction<Void>() {  
               public Void run() {  
                   try {  
                       FileOutputStream file =  
                           new FileOutputStream(dotToSlash(name) + ".class");  
                       file.write(classFile);  
                       file.close();  
                       return null;  
                   } catch (IOException e) {  
                       throw new InternalError(  
                           "I/O exception saving generated file: " + e);  
                   }  
               }  
           });  
       }  
  
    // 返回代理类的字节码  
       return classFile;  
   }

我们可以自己试试 ProxyGenerator.generateProxyClass 的功能。

public class ProxyGeneratorUtils {    /**
     * 把代理类的字节码写到硬盘上 
     * @param path 保存路径 
     */
    public static void writeProxyClassToHardDisk(String path) {// 获取代理类的字节码  
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", Student.class.getInterfaces());
 
        FileOutputStream out = null; 
        try {
            out = new FileOutputStream(path);
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

main 方法里面进行调用 :

public class Main {    public static void main(String[] args) {
        ProxyGeneratorUtils.writeProxyClassToHardDisk("$Proxy0.class");
    }
}

可以发现,在根目录下生成了一个 $Proxy0.class 文件,文件内容反编译后如下:

import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;import proxy.Person;public final class $Proxy0 extends Proxy implements Person
{  private static Method m1;  private static Method m2;  private static Method m3;  private static Method m0;  
  /**
  *注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白
  *为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个
  *被代理对象的实例,不禁会想难道是....? 没错,就是你想的那样。
  *
  *super(paramInvocationHandler),是调用父类Proxy的构造方法。
  *父类持有:protected InvocationHandler h;
  *Proxy构造方法:
  *    protected Proxy(InvocationHandler h) {
  *         Objects.requireNonNull(h);
  *         this.h = h;
  *     }
  *  */
  public $Proxy0(InvocationHandler paramInvocationHandler)    throws 
  {    super(paramInvocationHandler);
  }  
  //这个静态块本来是在最后的,我把它拿到前面来,方便描述
   static
  {    try
    {      //看看这儿静态块儿里面有什么,是不是找到了giveMoney方法。请记住giveMoney通过反射得到的名字m3,其他的先不管
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);      return;
    }    catch (NoSuchMethodException localNoSuchMethodException)
    {      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }    catch (ClassNotFoundException localClassNotFoundException)
    {      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  } 
  /**
  * 
  *这里调用代理对象的giveMoney方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
  *this.h.invoke(this, m3, null);这里简单,明了。
  *来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象,
  *再联系到InvacationHandler中的invoke方法。嗯,就是这样。  */
  public final void giveMoney()    throws 
  {    try
    {      this.h.invoke(this, m3, null);      return;
    }    catch (Error|RuntimeException localError)
    {      throw localError;
    }    catch (Throwable localThrowable)
    {      throw new UndeclaredThrowableException(localThrowable);
    }
  }  //注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。}

jdk 为我们的生成了一个叫 $Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,**我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。**通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。

我们可以对 InvocationHandler 看做一个中介类,中介类持有一个被代理对象,**在 invoke 方法中调用了被代理对象的相应方法,而生成的代理类中持有中介类,**因此,当我们在调用代理类的时候,就是再调用中介类的 invoke 方法,通过反射转为对被代理对象的调用。

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

生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了 Proxy 类,所以也就决定了 java 动态代理只能对接口进行代理,Java 的继承机制(类单继承,接口多继承)注定了这些动态代理类们无法实现对 class 的动态代理。

参考链接:
https://www.cnblogs.com/xiaoluo501395377/p/3383130.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值