动态代理 看这一篇就够了

动态代理与静态代理

本文整理归纳了一些博客的关于动态代理的介绍。

Spring AOP中使用了两种动态代理,一种是JDK的动态代理,一种CGLIB的动态代理。JDK的动态代理必须指定接口,这些接口都是已经被代理对象实现了的;而CGLIB代理则不需要指定接口。

 

  • 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。

  • 动态:在程序运行时运用反射*机制动态创建而成。

 

为什么需要代理模式

假设需实现一个计算的类Math、完成加功能,如下所示:

public class Math {

     //加
     public int add(int n1,int n2){

         int result=n1+n2;
         System.out.println(n1+"+"+n2+"="+result);
         return result;

     }

现在需求发生了变化,要求项目中所有的类在执行方法时输出执行耗时最直接的办法是修改源代码,如下所示:
 

 public class Math {

      //加
      public int add(int n1,int n2){            //直接修改源代码

         //开始时间

         long start=System.currentTimeMillis();
         lazy();
         int result=n1+n2;
         System.out.println(n1+"+"+n2+"="+result);
         Long span= System.currentTimeMillis()-start;
         System.out.println("共用时:"+span);
         return result;
     }

public void lazy()

     {
         try {

             int n=(int)new Random().nextInt(500);
             Thread.sleep(n);

         } catch (InterruptedException e) {
             e.printStackTrace();
         }
    }

测试运行:

public class Test {
    public void test01()
    {
        Math math=new Math();
        int n1=100,n2=5;
        math.add(n1, n2);
    }

}

 

缺点:

1、工作量特别大,如果项目中有多个类,多个方法,则要修改多次。

2、违背了设计原则:开闭原则(OCP),对扩展开放,对修改关闭,而为了增加功能把每个方法都修改了,也不便于维护。

3、违背了设计原则:单一职责(SRP),每个方法除了要完成自己本身的功能,还要计算耗时、延时;每一个方法引起它变化的原因就有多种。

4、违背了设计原则:依赖倒转(DIP),抽象不应该依赖细节,两者都应该依赖抽象。而在Test类中,Test与Math都是细节。

使用静态代理可以解决部分问题。

 

静态代理

代理对象就是本身不是包含实际功能的对象,它就是一个传声筒,它调用被代理的对象,并且把返回结果再传给调用代理对象的对象被代理对象就是实际拥有功能调用的对象。

  • 调用代理对象的对象  (调用)------> 代理对象  (调用)------> 被代理对象(拥有实际功能)

  • 代理对象 (返回结果) ------> 调用代理对象的对象 

public interface IMath {        //接口 ,抽象主题
    //加
    int add(int n1, int n2);

    }

被代理的目标对象(主题类,算术类,实现抽象接口。)

public class Math implements IMath {

    public int add(int n1,int n2){     //重写接口方法

        int result=n1+n2;
        System.out.println(n1+"+"+n2+"="+result);
        return result;

    }

代理类

//静态代理类

public class MathProxy implements IMath {

     //被代理的对象

     IMath math=new Math();

     

     //加

     public int add(int n1, int n2) {        //这个类再重写接口方法

         //开始时间

         long start=System.currentTimeMillis();
         lazy();
         int result=math.add(n1, n2);        //调用被代理对象**  重写过的的接口方法
         Long span= System.currentTimeMillis()-start;
         System.out.println("共用时:"+span);
         return result;

     }

    //模拟延时
    public void lazy()

     {
         try {

            int n=(int)new Random().nextInt(500);
            Thread.sleep(n);

         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     }

测试运行

public class Test {

     IMath math=new MathProxy();

     public void test01()
     {
         int n1=100,n2=5;
         math.add(n1, n2);
     }
}

 

小结

通过静态代理,是否完全解决了上述的4个问题:

已解决:

  • 解决了“开闭原则(OCP)”的问题,因为并没有修改Math类,而扩展出了MathProxy类。

  • 解决了“依赖倒转(DIP)”的问题,通过引入接口。

  • 解决了“单一职责(SRP)”的问题,Math类不再需要去计算耗时与延时操作,但从某些方面讲MathProxy还是存在该问题。

未解决:

如果项目中有多个类,则需要编写多个代理类**,工作量大,不好修改,不好维护,不能应对变化。

如果要解决上面的问题,可以使用动态代理。

 

动态代理,使用JDK内置的Proxy实现

只需要一个代理类,而不是针对每个类编写代理类。

一、动态代理与静态代理的区别。

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

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

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

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

 

在上一个示例中修改代理类MathProxy如下:

静态代理这个模式本身有个大问题,如果类方法数量越来越多的时候,代理类的代码量是十分庞大的。所以引入动态代理来解决此类问题

 

Java中动态代理的实现,关键就是这两个东西:

  • Proxy

  • InvocationHandler

下面从InvocationHandler接口中的invoke方法入手,简单说明一下Java如何实现动态代理的。

接口:

//抽象角色(动态代理只能代理接口)  

public interface Subject {  

    public void request();  

}

接口实现:

//真实角色:实现了Subject的request()方法  
public class RealSubject implements Subject{  
      
    public void request(){  
        System.out.println("From real subject.");  
    }  
}

 

    //实现了InvocationHandler  
    public class DynamicSubject implements InvocationHandler  
    {  
        private Object obj;//这是动态代理的好处,被封装的对象是Object类型,接受任意类型的对象  
      
        public DynamicSubject()  
        {  
        }  
      
        public DynamicSubject(Object obj)  
        {  
            this.obj = obj;  
        }  
      
        //这个方法不是我们显示的去调用  
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable  
        {  
            System.out.println("before calling " + method);  
    
            method.invoke(obj, args);  

            System.out.println("after calling " + method);  
            return null;  
        }  
      
    }  

 

    //客户端:生成代理实例,并调用了request()方法  
    public class Client {  
      
        public static void main(String[] args) throws Throwable{  
            // TODO Auto-generated method stub  
      
            Subject rs=new RealSubject();//这里指定被代理类  
            InvocationHandler ds=new DynamicSubject(rs);  
            Class<?> cls=rs.getClass();  
              
            //以下是一次性生成代理  
              
            Subject subject=(Subject) Proxy.newProxyInstance*(  
                    cls.getClassLoader(),cls.getInterfaces(), ds);  
              


            //这里可以通过运行结果证明subject是Proxy的一个实例,这个实例实现了Subject接口  
            System.out.println(subject instanceof Proxy);  
              
            //这里可以看出subject的Class类是$Proxy0,这个$Proxy0类继承了Proxy,实现了Subject接口  
            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()+", ");  
            }  

            // subject中的方法有:request, hashCode, equals, toString,   
            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("\n\n"+"运行结果为:");  
            subject.request()**;  
        }  
    }  

运行结果:

    运行结果如下:此处省略了包名,***代替  
    true  
    subject的Class类是:class $Proxy0  
    subject中的属性有:m1, m3, m0, m2,   
    subject中的方法有:request, hashCode, equals, toString,   
    subject的父类是:class java.lang.reflect.Proxy  
    subject实现的接口是:cn.edu.ustc.dynamicproxy.Subject,   
      
    运行结果为:  
    before calling public abstract void ***.Subject.request()  
    From real subject.  
    after calling public abstract void ***.Subject.request()  

这个结果的信息非常重要,至少对我来说。因为我在动态代理犯晕的根源就在于将上面的subject.request()理解错了,至少是被表面所迷惑,没有发现这个subject和Proxy之间的联系,一度纠结于最后调用的这个request()是怎么和invoke()联系上的,而invoke又是怎么知道request存在的。其实上面的true和class $Proxy0就能解决很多的疑问,再加上下面将要说的$Proxy0的源码,完全可以解决动态代理的疑惑了。

从以上代码和结果可以看出,我们并没有显示的调用invoke()方法,但是这个方法确实执行了。下面就整个的过程进行分析一下:

从Client中的代码看,可以从newProxyInstance这个方法作为突破口,我们先来看一下Proxy类中newProxyInstance方法的源代码:

public static Object newProxyInstance(ClassLoader loader,  
            Class<?>[] interfaces,  
            InvocationHandler h)  
    throws IllegalArgumentException  
    {  
        if (h == null) {  
            throw new NullPointerException();  
        }  
      
        /*
         * Look up or generate the designated proxy class.
         */  
        Class cl = getProxyClass(loader, interfaces);      //创建代理类$Proxy0.$Proxy0类 ,实现了interfaces的接口,并继承了Proxy类.
      
        /*
         * Invoke its constructor with the designated invocation handler.
         */  
        try {  
               /*
                * Proxy源码开始有这样的定义:
                * private final static Class[] constructorParams = { InvocationHandler.class };
                * cons即是形参为InvocationHandler类型的构造方法
               */  
            Constructor cons = cl.getConstructor(constructorParams);  
            return (Object) cons.newInstance(new Object[] { h });  
        } catch (NoSuchMethodException e) {  
            throw new InternalError(e.toString());  
        } catch (IllegalAccessException e) {  
            throw new InternalError(e.toString());  
        } catch (InstantiationException e) {  
            throw new InternalError(e.toString());  
        } catch (InvocationTargetException e) {  
            throw new InternalError(e.toString());  
        }  
    }  

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)做了以下几件事:

(1)根据参数loader和interfaces调用方法     getProxyClass(loader, interfaces)   创建代理类$Proxy0.$Proxy0类 ,实现了interfaces的接口,并继承了Proxy类.

(2)实例化$Proxy0并在构造方法中把DynamicSubject传过去,接着$Proxy0调用父类Proxy的构造器,为h赋值,如下:

    class Proxy{  
        InvocationHandler h=null;  
        protected Proxy(InvocationHandler h) {  
            this.h = h;  
        }  
        ...  
    }  


来看一下这个继承了Proxy的$Proxy0的源代码:

    public final class $Proxy0 extends Proxy implements Subject {  
        private static Method m1;  
        private static Method m0;  
        private static Method m3;  
        private static Method m2;  
      
        static {  
            try {  
                m1 = Class.forName("java.lang.Object").getMethod("equals",  
                        new Class[] { Class.forName("java.lang.Object") });  
      
                m0 = Class.forName("java.lang.Object").getMethod("hashCode",  
                        new Class[0]);  
      
                m3 = Class.forName("***.RealSubject").getMethod("request",  
                        new Class[0]);  
      
                m2 = Class.forName("java.lang.Object").getMethod("toString",  
                        new Class[0]);  
      
            } catch (NoSuchMethodException nosuchmethodexception) {  
                throw new NoSuchMethodError(nosuchmethodexception.getMessage());  
            } catch (ClassNotFoundException classnotfoundexception) {  
                throw new NoClassDefFoundError(classnotfoundexception.getMessage());  
            }  
        } //static  
      
        public $Proxy0(InvocationHandler invocationhandler) {  
            super(invocationhandler);  
        }  
      
        @Override  
        public final boolean equals(Object obj) {  
            try {  
                return ((Boolean) super.h.invoke(this, m1, new Object[] { obj })) .booleanValue();  
            } catch (Throwable throwable) {  
                throw new UndeclaredThrowableException(throwable);  
            }  
        }  
      
        @Override  
        public final int hashCode() {  
            try {  
                return ((Integer) super.h.invoke(this, m0, null)).intValue();  
            } catch (Throwable throwable) {  
                throw new UndeclaredThrowableException(throwable);  
            }  
        }  
      
        public final void request() {  
            try {  
                super.h.invoke(this, m3, null);  
                return;  
            } catch (Error e) {  
            } catch (Throwable throwable) {  
                throw new UndeclaredThrowableException(throwable);  
            }  
        }  
      
        @Override  
        public final String toString() {  
            try {  
                return (String) super.h.invoke(this, m2, null);  
            } catch (Throwable throwable) {  
                throw new UndeclaredThrowableException(throwable);  
            }  
        }  
    }  
  • 接着把得到的$Proxy0实例强制转换成Subject,并将引用赋给subject。

  • 当执行subject.request()方法时,就调用了$Proxy0类中的request()方法**,进而调用父类Proxy中的h的invoke()方法.即InvocationHandler.invoke()。

PS:1、需要说明的一点是,Proxy类中getProxyClass方法返回的是Proxy的Class类。之所以说明,是因为我一开始犯了个低级错误,以为返回的是“被代理类的Class类”- -!推荐看一下getProxyClass的源码,很长=。=

        2、从$Proxy0的源码可以看出,动态代理类不仅代理了显示定义的接口中的方法,而且还代理了java的根类Object中的继承而来的equals()、hashcode()、toString()这三个方法,并且仅此三个方法。

Q:到现在为止,还有一个疑问,invoke方法中的第一个参数是Proxy的实例(准确说,最终用到的是$Proxy0的实例),但是有什么用呢?或者说,程序内是怎样显示出作用的?

A:就本人目前的水平看来,这个proxy参数并没有什么作用,在整个动态代理机制中,并没有用到InvocationHandler中invoke方法的proxy参数。而传入的这个参数实际是代理类的一个实例。我想可能是为了让程序员在invoke方法中使用反射来获取关于代理类的一些信息吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值