精华内容
下载资源
问答
  • 熟悉动态代理的小伙伴都知道,java的动态代理有两个常用的方式,一个是jdk动态代理,一个是cglib,他们两个在使用上的最大区别就是前者是返回一个接口对象(其实质是Proxy类的子类),后者是返回了被代理类的类对象...
  • NULL 博文链接:https://jummy.iteye.com/blog/255628
  • jdk动态代理源码分析(通过该示例学会阅读源码的方法)2.jdk动态代理生成的代理类的源码3.总结三、手写实现jdk动态代理 一、代理模式 熟悉代理模式的可以直接点击目录第二章,jdk动态代理实现原理,本文的精髓所在,...
  • JDK动态代理简单示例

    2014-11-21 17:33:08
    通过一个简单例子来理解JDK动态代理的思想,资源为拷贝的视频讲解的内容。
  • 主要介绍了Java JDK动态代理实现原理实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • AOP之JDK动态代理和CGLib动态代理 ,具体效果和过程看博文 http://blog.csdn.net/evankaka/article/details/45195383
  • NULL 博文链接:https://hyp1987.iteye.com/blog/1833776
  • 实现java动态代理的两个实例,jdk动态代理和cglib
  • JDK动态代理,必须有接口,目标类必须实现接口,没有接口时候,需要使用cglib动态代理 动态:在程序的执行时,调用jdk提供的方法才能创建代理类的对象。 2.知道动态代理能做什么? 1.控制访问:在代理中,控制是否...

    动态代理:基于反射机制
    掌握的程度
    1.什么是动态代理?

    使用jdk的反射机制,创建对象的能力,创建的是代理类的对象,而不用你创建类文件,不用写java文件
    JDK动态代理,必须有接口,目标类必须实现接口,没有接口时候,需要使用cglib动态代理
    动态:在程序的执行时,调用jdk提供的方法才能创建代理类的对象。

    2.知道动态代理能做什么?

    1.控制访问:在代理中,控制是否可以调用目标对象的方法。
    2.功能增强,可以完成目标对象的调用时,附加一些额外的功能,这些额外的功能叫做功能增强 代理实现的方式

    1.静态代理:代理类是手工实现的java文件,同时代理的目标对象是规定的。

    1.优点:容易理解,使用方便
    2.在目标类比较多的时候,会产生大量的代理类;当接口改变时,影响的目标会复杂

    2.动态代理:

    使用反射机制,在程序执行中,创建代理类对象,特点,不用创建类文件,代理的目标类是活动的,可设置的
    1.不用创建代理类
    2.可以给不同的目标随时创建代理类

    动态代理:

    在静态代理中目标类很多时间,可以使用动态代理,避免静态代理的缺点。
    动态代理中目标类即使很多,1,代理类数量可以很少,2.当你修改了接口中的方法,不会影响代理类

    动态代理:在程序执行过程中,使用jdk反射机制,创建代理类对象,并动态的指定要代理目标类。
    	换句话说:动态代理是一中创建java对象的能力,让你不用创建taobao类就能创建代理类。
    	
    	在java中,要想创建对象:
    		1.创建对象,java文件编译class
    		2.使用构造方法,创建类的对象。
    

    3.实现动态代理的步骤:

    1.创建接口,定义目标类要完成的功能
    2.创建目标类实现接口
    3.创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能
    1.调用目标方法
    2.增强功能
    4.使用proxy类的静态方法,创建代理对象,并把返回值转为接口类型

    展开全文
  • jdk动态代理

    2019-08-03 01:08:21
    NULL 博文链接:https://bicashy.iteye.com/blog/1988686
  • Java两种动态代理JDK动态代理和CGLIB动态代理

    万次阅读 多人点赞 2018-08-07 15:33:35
    JDK动态代理 cglib动态代理 测试 代理模式 代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是...

    目录

    代理模式

    JDK动态代理

    cglib动态代理

    测试


    代理模式

    代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是“真正”实现类,A则比较“虚”,他借用了B的方法去实现接口的方法。A虽然是“伪军”,但它可以增强B,在调用B的方法前后都做些其他的事情。Spring AOP就是使用了动态代理完成了代码的动态“织入”。

    使用代理好处还不止这些,一个工程如果依赖另一个工程给的接口,但是另一个工程的接口不稳定,经常变更协议,就可以使用一个代理,接口变更时,只需要修改代理,不需要一一修改业务代码。从这个意义上说,所有调外界的接口,我们都可以这么做,不让外界的代码对我们的代码有侵入,这叫防御式编程。代理其他的应用可能还有很多。

    上述例子中,类A写死持有B,就是B的静态代理。如果A代理的对象是不确定的,就是动态代理。动态代理目前有两种常见的实现,jdk动态代理和cglib动态代理。

    JDK动态代理

    jdk动态代理是jre提供给我们的类库,可以直接使用,不依赖第三方。先看下jdk动态代理的使用代码,再理解原理。

    首先有个“明星”接口类,有唱、跳两个功能:

    package proxy;
    
    public interface Star
    {
        String sing(String name);
        
        String dance(String name);
    }
    

    再有个明星实现类“刘德华”:

    package proxy;
    
    public class LiuDeHua implements Star
    {   
        @Override
        public String sing(String name)
        {
             System.out.println("给我一杯忘情水");
    
            return "唱完" ;
        }
        
        @Override
        public String dance(String name)
        {
            System.out.println("开心的马骝");
    
            return "跳完" ;
        }
    }
    

    明星演出前需要有人收钱,由于要准备演出,自己不做这个工作,一般交给一个经纪人。便于理解,它的名字以Proxy结尾,但他不是代理类,原因是它没有实现我们的明星接口,无法对外服务,它仅仅是一个wrapper。

    package proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class StarProxy implements InvocationHandler
    {
        // 目标类,也就是被代理对象
        private Object target;
        
        public void setTarget(Object target)
        {
            this.target = target;
        }
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            // 这里可以做增强
            System.out.println("收钱");
            
            Object result = method.invoke(target, args);
            
            return result;
        }
        
        // 生成代理类
        public Object CreatProxyedObj()
        {
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }  
       
    }
    

    上述例子中,方法CreatProxyedObj返回的对象才是我们的代理类,它需要三个参数,前两个参数的意思是在同一个classloader下通过接口创建出一个对象,该对象需要一个属性,也就是第三个参数,它是一个InvocationHandler。需要注意的是这个CreatProxyedObj方法不一定非得在我们的StarProxy类中,往往放在一个工厂类中。上述代理的代码使用过程一般如下:

    1、new一个目标对象

    2、new一个InvocationHandler,将目标对象set进去

    3、通过CreatProxyedObj创建代理对象,强转为目标对象的接口类型即可使用,实际上生成的代理对象实现了目标接口。

            Star ldh = new LiuDeHua();
    
            StarProxy proxy = new StarProxy();
    
            proxy.setTarget(ldh); 
      
            Object obj = proxy.CreatProxyedObj();
            
            Star star = (Star)obj;

    Proxy(jdk类库提供)根据B的接口生成一个实现类,我们成为C,它就是动态代理类(该类型是 $Proxy+数字 的“新的类型”)。生成过程是:由于拿到了接口,便可以获知接口的所有信息(主要是方法的定义),也就能声明一个新的类型去实现该接口的所有方法,这些方法显然都是“虚”的,它调用另一个对象的方法。当然这个被调用的对象不能是对象B,如果是对象B,我们就没法增强了,等于饶了一圈又回来了。

    所以它调用的是B的包装类,这个包装类需要我们来实现,但是jdk给出了约束,它必须实现InvocationHandler,上述例子中就是StarProxy, 这个接口里面有个方法,它是所有Target的所有方法的调用入口(invoke),调用之前我们可以加自己的代码增强。

    看下我们的实现,我们在InvocationHandler里调用了对象B(target)的方法,调用之前增强了B的方法。

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            // 这里增强
            System.out.println("收钱");
            
            Object result = method.invoke(target, args);
            
            return result;
        }

    所以可以这么认为C代理了InvocationHandler,InvocationHandler代理了我们的类B,两级代理。

    整个JDK动态代理的秘密也就这些,简单一句话,动态代理就是要生成一个包装类对象,由于代理的对象是动态的,所以叫动态代理。由于我们需要增强,这个增强是需要留给开发人员开发代码的,因此代理类不能直接包含被代理对象,而是一个InvocationHandler,该InvocationHandler包含被代理对象,并负责分发请求给被代理对象,分发前后均可以做增强。从原理可以看出,JDK动态代理是“对象”的代理。

    下面看下动态代理类到底如何调用的InvocationHandler的,为什么InvocationHandler的一个invoke方法能为分发target的所有方法。C中的部分代码示例如下,通过反编译生成后的代码查看,摘自链接地址。Proxy创造的C是自己(Proxy)的子类,且实现了B的接口,一般都是这么修饰的:

    public final class XXX extends Proxy implements XXX
    

    一个方法代码如下:

    
      public final String SayHello(String paramString)
      {
        try
        {
          return (String)this.h.invoke(this, m4, new Object[] { paramString });
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
    

    可以看到,C中的方法全部通过调用h实现,其中h就是InvocationHandler,是我们在生成C时传递的第三个参数。这里还有个关键就是SayHello方法(业务方法)跟调用invoke方法时传递的参数m4一定要是一一对应的,但是这些对我们来说都是透明的,由Proxy在newProxyInstance时保证的。留心看到C在invoke时把自己this传递了过去,InvocationHandler的invoke的第一个方法也就是我们的动态代理实例类,业务上有需要就可以使用它。(所以千万不要在invoke方法里把请求分发给第一个参数,否则很明显就死循环了)

    C类中有B中所有方法的成员变量

      private static Method m1;
      private static Method m3;
      private static Method m4;
      private static Method m2;
      private static Method m0;
    

    这些变量在static静态代码块初始化,这些变量是在调用invocationhander时必要的入参,也让我们依稀看到Proxy在生成C时留下的痕迹。

    static
      {
        try
        {
          m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
          m3 = Class.forName("jiankunking.Subject").getMethod("SayGoodBye", new Class[0]);
          m4 = Class.forName("jiankunking.Subject").getMethod("SayHello", new Class[] { Class.forName("java.lang.String") });
          m2 = Class.forName("java.lang.Object").getMethod("toString", 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());
        }
      }
    

    从以上分析来看,要想彻底理解一个东西,再多的理论不如看源码,底层的原理非常重要。

    jdk动态代理类图如下

    cglib动态代理

    我们了解到,“代理”的目的是构造一个和被代理的对象有同样行为的对象,一个对象的行为是在类中定义的,对象只是类的实例。所以构造代理,不一定非得通过持有、包装对象这一种方式。

    通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是cglib的思想。根据里氏代换原则(LSP),父类需要出现的地方,子类可以出现,所以cglib实现的代理也是可以被正常使用的。

    先看下代码

    package proxy;
    
    import java.lang.reflect.Method;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    public class CglibProxy implements MethodInterceptor
    {
        // 根据一个类型产生代理类,此方法不要求一定放在MethodInterceptor中
        public Object CreatProxyedObj(Class<?> clazz)
        {
            Enhancer enhancer = new Enhancer();
            
            enhancer.setSuperclass(clazz);
            
            enhancer.setCallback(this);
            
            return enhancer.create();
        }
        
        @Override
        public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
        {
            // 这里增强
            System.out.println("收钱");
            
            return arg3.invokeSuper(arg0, arg2);
        } 
    }
    

    从代码可以看出,它和jdk动态代理有所不同,对外表现上看CreatProxyedObj,它只需要一个类型clazz就可以产生一个代理对象, 所以说是“类的代理”,且创造的对象通过打印类型发现也是一个新的类型。不同于jdk动态代理,jdk动态代理要求对象必须实现接口(三个参数的第二个参数),cglib对此没有要求。

    cglib的原理是这样,它生成一个继承B的类型C(代理类),这个代理类持有一个MethodInterceptor,我们setCallback时传入的。 C重写所有B中的方法(方法名一致),然后在C中,构建名叫“CGLIB”+“$父类方法名$”的方法(下面叫cglib方法,所有非private的方法都会被构建),方法体里只有一句话super.方法名(),可以简单的认为保持了对父类方法的一个引用,方便调用。

    这样的话,C中就有了重写方法、cglib方法、父类方法(不可见),还有一个统一的拦截方法(增强方法intercept)。其中重写方法和cglib方法肯定是有映射关系的。

    C的重写方法是外界调用的入口(LSP原则),它调用MethodInterceptor的intercept方法,调用时会传递四个参数,第一个参数传递的是this,代表代理类本身,第二个参数标示拦截的方法,第三个参数是入参,第四个参数是cglib方法,intercept方法完成增强后,我们调用cglib方法间接调用父类方法完成整个方法链的调用。

    这里有个疑问就是intercept的四个参数,为什么我们使用的是arg3而不是arg1?

        @Override
        public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
        {
            System.out.println("收钱");
            
            return arg3.invokeSuper(arg0, arg2);
        }

     因为如果我们通过反射 arg1.invoke(arg0, ...)这种方式是无法调用到父类的方法的,子类有方法重写,隐藏了父类的方法,父类的方法已经不可见,如果硬调arg1.invoke(arg0, ...)很明显会死循环。

    所以调用的是cglib开头的方法,但是,我们使用arg3也不是简单的invoke,而是用的invokeSuper方法,这是因为cglib采用了fastclass机制,不仅巧妙的避开了调不到父类方法的问题,还加速了方法的调用。

    fastclass基本原理是,给每个方法编号,通过编号找到方法执行避免了通过反射调用。

    对比JDK动态代理,cglib依然需要一个第三者分发请求,只不过jdk动态代理分发给了目标对象,cglib最终分发给了自己,通过给method编号完成调用。cglib是继承的极致发挥,本身还是很简单的,只是fastclass需要另行理解。

    参考

    https://blog.csdn.net/jiankunking/article/details/52143504

    http://www.php.cn/java-article-407212.html

    https://www.cnblogs.com/chinajava/p/5880887.html

    https://rejoy.iteye.com/blog/1627405

    测试

       public static void main(String[] args)
        {
            int times = 1000000;
            
            Star ldh = new LiuDeHua();
            StarProxy proxy = new StarProxy();
            proxy.setTarget(ldh);
            
            long time1 = System.currentTimeMillis();
            Star star = (Star)proxy.CreatProxyedObj();
            long time2 = System.currentTimeMillis();
            System.out.println("jdk创建时间:" + (time2 - time1));
            
            CglibProxy proxy2 = new CglibProxy();
            long time5 = System.currentTimeMillis();
            Star star2 = (Star)proxy2.CreatProxyedObj(LiuDeHua.class);
            long time6 = System.currentTimeMillis();
            System.out.println("cglib创建时间:" + (time6 - time5));
            
            long time3 = System.currentTimeMillis();
            for (int i = 1; i <= times; i++)
            {
                star.sing("ss");
                
                star.dance("ss");
            }
            long time4 = System.currentTimeMillis();
            System.out.println("jdk执行时间" + (time4 - time3));
            
            long time7 = System.currentTimeMillis();
            for (int i = 1; i <= times; i++)
            {
                star2.sing("ss");
                
                star2.dance("ss");
            }
            
            long time8 = System.currentTimeMillis();
            
            System.out.println("cglib执行时间" + (time8 - time7));   
        }

    经测试,jdk创建对象的速度远大于cglib,这是由于cglib创建对象时需要操作字节码。cglib执行速度略大于jdk,所以比较适合单例模式。另外由于CGLIB的大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久堆中。如果动态代理操作过多,容易造成永久堆满,触发OutOfMemory异常。spring默认使用jdk动态代理,如果类没有接口,则使用cglib。

    展开全文
  • 实例理解JDK动态代理和Cglib动态代理及其区别 深入理解设计模式之代理模式 代理代理化妆品生产理解JDK动态代理 代理代理汽车制造理解Cglib动态代理

    动态代理

    代理模式参考博客:

    https://blog.csdn.net/qq_42937522/article/details/105067563

    代理模式原理

    使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

    作用

    为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另外一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

    应用场景

    1)远程代理(Remote Proxy)

    为一个位于不同的地址空间的对象提供一个本地的代理对象。这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中。也即为不同地址空间提供局部的代表。

    2)虚拟代理(Virtual Proxy)

    根据需要创建开销很大的对象。如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。

    3)保护代理(Protection Proxy)

    控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。

    4)智能指引(Smart Reference)

    取代了简单的指针,它在访问对象时执行一些附加操作。

    5)Copy-on-Write代理

    它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。

    动态代理

    动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

    动态代理相比于静态代理的优点:

    抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。

    动态代理的一般用法

    在这里插入图片描述

    JDK动态代理

    使用Proxy和InvocationHandler创建动态代理

    Proxy提供用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类。如果我们在程序中为一个或多个接口动态地生成实现类,就可以使用Proxy来创建动态代理类:如果需要为一个或多个接口动态地创建实例,也可以使用Proxy来创建动态代理实例。
    Proxy提供了如下两个方法来创建动态代理类和动态代理实例:

    • static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces):创建一- 个动态
      代理类所对应的Class对象,该代理类将实现interfaces 所指定的多个接口。第一个ClassLoader指定生成动态代理类的类加载器。
    • static Object newProxyInstance(ClassLoader loader,Class<?>I] interfaces, InvocationHandler h):直接创建一个动态代理对象, 该代理对象的实现类实现了interfaces 指定的一系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象invoke方法。

    实际上,即使采用第一-种方式获取了 一个动态代理类之后,当程序需要通过该代理类来创建对象时一样需要传入一个InvocationHandler对象。也就是说,系统生成的每个代理对象都有一个与之关联的InvocationHandler对象。

    示例

    以代理商代理生产商的产品为例(JDK代理实现)

    代码实现

    需要实现的接口

    /**
     * 抽象接口——生产者
     */
    public interface Producer {
        String produce(String product);
    }
    
    /**
     * 功能描述:化妆品生产者(被代理对象)
     **/
    public class CosmeticProducer implements Producer {
    
        /**
         * 生产化妆品
         */
        @Override
        public String produce ( String product ) {
            System.out.println("正在生产化妆品" + product);
            return product;
        }
    
    }
    
    /**
     * 功能描述:提供产品的工具类
     **/
    public class ProductUtil {
    
        public static void prepareMaterial(){
            System.out.println("代理商为生产商准备材料");
        }
    
        public static void sellProduct(){
            System.out.println("代理商为生产商卖产品");
        }
        
    }
    
    /**
     * 功能描述:代理对象调用被代理对象方法,所必须实现的接口
     **/
    public class MyInvocationHandler implements InvocationHandler {
    
        /** 需要被代理的对象 */
        private Object target;
    
        public void setTarget ( Object target ) {
            this.target = target;
        }
    
        @Override
        public Object invoke ( Object proxy, Method method, Object[] args ) throws Throwable {
            //调用被代理对象方法前,执行的方法
            ProductUtil.prepareMaterial();
    
            //调用被代理对象的方法
            Object obj = method.invoke(target, args);
    
            //调用被代理对象方法后,执行的方法
            ProductUtil.sellProduct();
    
            //返回被代理对象方法的返回值
            return obj;
        }
    }
    
    /**
     * 功能描述:产品代理商(代理对象)
     *
     * 采用自定义的外部类MyInvocationHandler来处理
     *
     * @author RenShiWei
     * Date: 2020/6/19 16:46
     **/
    public class ProductProxy {
    
        //***方式一:使用自定义的外部类MyInvocationHandler实现InvocationHandler接口处理***
    
        public ProductProxy(){}
    
        //通过代理对象,调用被代理对象的方法
        public static Object getProductProxy ( Object target ) {
            MyInvocationHandler handler = new MyInvocationHandler();
            handler.setTarget(target);
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
        }
    
        //***方式二:使用匿名实现类来处理(可以减少类的数量)
    
        private Object target;
    
        public ProductProxy ( Object target ) {
            this.target = target;
        }
    
        //通过代理对象,调用被代理对象的方法
        public Object getProductProxy2 () {
            MyInvocationHandler handler = new MyInvocationHandler();
            handler.setTarget(target);
            //这里的InvocationHandler的实现类,也可以采用接口的匿名实现类来处理,减少类的数量
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke ( Object proxy, Method method, Object[] args ) throws Throwable {
                    //调用被代理对象方法前,执行的方法
                    ProductUtil.prepareMaterial();
    
                    //调用被代理对象的方法
                    Object obj = method.invoke(target, args);
    
                    //调用被代理对象方法后,执行的方法
                    ProductUtil.sellProduct();
    
                    //返回被代理对象方法的返回值
                    return obj;
                }
            });
        }
    }
    
    

    客户端调用

    /**
     * 功能描述:客户端调用
     **/
    public class client {
    
        public static void main ( String[] args ) {
            System.out.println("**方式一:使用MyInvocationHandler**");
            Producer cosmeticProducer = new CosmeticProducer();
            Producer productProxy = (Producer) ProductProxy.getProductProxy(cosmeticProducer);
            String s = productProxy.produce("欧莱雅男士洗面奶");
            System.out.println("被代理的产品:" + s);
    
            System.out.println("**方式二:使用匿名InvocationHandler接口实现类的方式**");
            ProductProxy proxy = new ProductProxy(new CosmeticProducer());
            Producer productProxy2 = (Producer) proxy.getProductProxy2();
            String s1 = productProxy2.produce("兰蔻洁面乳");
            System.out.println("被代理的产品:" + s1);
        }
    
    }
    

    结果:

    **方式一:使用MyInvocationHandler**
    代理商为生产商准备材料
    正在生产化妆品欧莱雅男士洗面奶
    代理商为生产商卖产品
    被代理的产品:欧莱雅男士洗面奶
    **方式二:使用匿名InvocationHandler接口实现类的方式**
    代理商为生产商准备材料
    正在生产化妆品兰蔻洁面乳
    代理商为生产商卖产品
    被代理的产品:兰蔻洁面乳
    

    JDK动态代理原理

    为什么要叫JDK动态代理?

    是因为代理对象是由JDK动态生成的,而不像静态代理方式写死代理对象和被代理类,不灵活。

    JDK动态代理基于拦截器和反射来实现

    使用条件

    1)必须实现InvocationHandler接口;

    2)使用Proxy.newProxyInstance产生代理对象;

    3)被代理的对象必须要实现接口;

    源码分析

    参考博客:https://blog.csdn.net/yhl_jxy/article/details/80586785

    Cglib动态代理

    1. 引入相关依赖
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
    
    1. 代理类实现MethodInterceptor接口,实现intercept方法

    2. 创建代理对象

    //返回一个代理对象:  是 target 对象的代理对象
    public Object getProxyInstance() {
        //1. 创建一个工具类
        Enhancer enhancer = new Enhancer();
        //2. 设置父类
        enhancer.setSuperclass(target.getClass());
        //3. 设置回调函数
        enhancer.setCallback(this);
        //4. 创建子类对象,即代理对象
        return enhancer.create();
    }
    

    4.客户端调用

    注意事项

    • 在内存中动态构建子类,注意代理的类不能为 final,否则报错java.lang.IllegalArgumentException:
    • 目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

    示例

    汽车制造厂制造汽车,代理商代理准备材料和售卖汽车

    /**
     * 功能描述:被代理者 汽车制造工厂
     **/
    public class CarFactory {
    
        public void productCar(){
            System.out.println("制造汽车");
        }
    
    }
    
    /**
     * 功能描述:代理类
     **/
    public class ProxyFactory implements MethodInterceptor {
    
        //维护一个目标对象
        private Object target;
    
        //构造器,传入一个被代理的对象
        public ProxyFactory(Object target) {
            this.target = target;
        }
    
        //返回一个代理对象:  是 target 对象的代理对象
        public Object getProxyInstance() {
            //1. 创建一个工具类
            Enhancer enhancer = new Enhancer();
            //2. 设置父类
            enhancer.setSuperclass(target.getClass());
            //3. 设置回调函数
            enhancer.setCallback(this);
            //4. 创建子类对象,即代理对象
            return enhancer.create();
        }
    
        //重写  intercept 方法,会调用目标对象的方法
        @Override
        public Object intercept ( Object o, Method method, Object[] objects, MethodProxy methodProxy ) throws Throwable {
            System.out.println("---代理商准备材料");
            Object returnVal = method.invoke(target,objects);
            System.out.println("---代理商售卖汽车");
            return returnVal;
        }
    
    }
    
    /**
     * 功能描述:客户端调用
     *
     **/
    public class Client {
    
        public static void main ( String[] args ) {
            //获取代理对象,并强转成被代理对象的数据类型
            CarFactory proxyInstance = (CarFactory) new ProxyFactory(new CarFactory()).getProxyInstance();
            //执行代理对象的方法,触发intecept 方法,从而实现 对目标对象的调用
            proxyInstance.productCar();
        }
    
    }
    

    结果

    ---代理商准备材料
    制造汽车
    ---代理商售卖汽车
    

    Cglib动态代理实现原理

    可以在运行期扩展Java类与实现Java接口。

    现CGLIB动态代理必须实现MethodInterceptor(方法拦截器)接口

    参考博客:Cglib动态代理实现原理

    MethodInterceptor接口源码

    package net.sf.cglib.proxy;
    
    import java.lang.reflect.Method;
    
    public interface MethodInterceptor extends Callback {
        Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
    }
    

    MethodInterceptor接口只有一个intercept()方法,这个方法有4个参数:

    1)Object表示增强的对象,即实现这个接口类的一个对象;

    2)Method表示要被拦截的方法;

    3)Object[]表示要被拦截方法的参数;

    4)MethodProxy表示要触发父类的方法对象;

    JDK动态代理VSCglib动态代理

    1.从实现方式上说

    JDK动态代理利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

    CGLIB动态代理利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

    2.何时使用JDK或者CGLIB?

    1)如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。

    2)如果目标对象实现了接口,可以强制使用CGLIB实现AOP。

    3)如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。

    JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承。

    但是针对接口编程的环境下推荐使用JDK的代理;

    3. JDK动态代理和CGLIB字节码生成的区别?

    1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类。

    2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的。

    4.CGlib比JDK快?

    1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

    2)在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,

    总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。

    5.Spring如何选择用JDK还是CGLIB?

    1)当Bean实现接口时,Spring就会用JDK的动态代理。

    2)当Bean没有实现接口时,Spring使用CGlib是实现。

    3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)。

    动态代理与AOP

    图解

    在这里插入图片描述

    展开全文
  • NULL 博文链接:https://johnyi.iteye.com/blog/385443
  • JDK动态代理的意义和用法

    千次阅读 2020-07-23 18:01:25
    一、JDK动态代理的意义 1.什么是代理代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到...

    一、JDK动态代理的意义

    1.什么是代理?

    代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

    代理模式的思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象。这些额外的操作通常需要与实际对象进行通信。

    2.java代理模式

    静态代理

    • 静态代理类:由程序员创建或者由第三方工具生成,再进行编译;在程序运行之前,代理类的.class文件已经存在了。
    • 静态代理类通常只代理一个类。
    • 静态代理事先知道要代理的是什么。

    动态代理

    • 动态代理类:在程序运行时,通过反射机制动态生成。
    • 动态代理类通常代理接口下的所有类。
    • 动态代理事先不知道要代理的是什么,只有在运行的时候才能确定。
    • 动态代理的调用处理程序必须实现InvocationHandler接口,并使用Proxy类中的newProxyInstance方法动态的创建代理类。
    • JDK动态代理只能代理接口(java的单继承特性),cglib只能代理类(本质生成了一个子类,所以要被代理的不能是final,本文不讲)。

    3.代理的意义

    JDK动态代理:代理对象=增强内容+原对象。(其实就是spring中的aop)

    意义:在编程中我们希望自己的代码是灵活的而不是死板的,能动态配置的东西就不要写死。这样来提高程序的可扩展性。如果不使用代理模式,将来我们想在当前的业务逻辑下添加一些其他的处理,比如日志、校验等等,就不得不侵入原有的业务代码,尤其是在重重继承关系复杂的类中,需要增加一些内容,并不清楚会不会影响到其他功能,所以使用代理来实现需要增加的内容。

    二、JDK动态代理实现

    写一个接口和实现类:

    public interface Movie {
        void  play();
    }
    public class NoVipMovie implements Movie {
        public void play(){
            System.out.println("播放非vip电影");
        }
    }

     实现InvocationHandler接口:

    public class MyInvocationHandler implements InvocationHandler {
        private Object object;
        public MyInvocationHandler(Object object){
            this.object=object;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            ad();
            Object invoke = method.invoke(object, args);
            return invoke;
        }
        public void ad(){
            System.out.println("播放广告");
        }
    
    }

     测试类调用Proxy类中的newProxyInstance:

    public class Test {
        public static void main(String[] args) {
             Movie movie = new NoVipMovie();
             MyInvocationHandler myInvocationHandler = new MyInvocationHandler(movie);
             //注掉的代码为将生成的代理对象存在磁盘上
             // System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
             Movie proxyObject  =(Movie) Proxy.newProxyInstance(NoVipMovie.class.getClassLoader(),NoVipMovie.class.getInterfaces(),myInvocationHandler);
            proxyObject.play();
        }
    }

    测试结果:

    播放广告
    播放非vip电影

    三、部分源码分析 

    动态代理实现流程

    1、为接口创建代理类的字节码文件

    2、使用ClassLoader将字节码文件加载到JVM

    3、创建代理类实例对象,执行对象的目标方法

    源码跟踪

    跟踪顺序:

    Proxy.newProxyInstance()-> getProxyClass0()->proxyClassCache->ProxyClassFactory->apply()->
    ProxyGenerator.generateProxyClass()

    进入Proxy.newProxyInstance()方法

     @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);
            }
    
             //生成或返回代理类(缓存有就返回,没有则生成新的)
            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;
                        }
                    });
                }
                   //创建动态代理类对象实例, 有参构造方法参数为InvocationHandler
                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()方法

     private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
            if (interfaces.length > 65535) {
                throw new IllegalArgumentException("interface limit exceeded");
            }
    
           //通过ProxyClassFactory创建代理类,看一下proxyClassCache是什么
            return proxyClassCache.get(loader, interfaces);
        }

    查看 proxyClassCache,发现缓存使用的是WeakCache实现的

     private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
            proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

     进入new ProxyClassFactory()对象

     private static final class ProxyClassFactory
            implements BiFunction<ClassLoader, Class<?>[], Class<?>>
        {
            // 代理类前缀
            private static final String proxyClassNamePrefix = "$Proxy";
    
            // 代理类计数器
            private static final AtomicLong nextUniqueNumber = new AtomicLong();
    
            @Override
            public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    
                Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
                for (Class<?> intf : interfaces) {
            
                    Class<?> interfaceClass = null;
                    try {
                        interfaceClass = Class.forName(intf.getName(), false, loader);
                    } catch (ClassNotFoundException e) {
                    }
                    if (interfaceClass != intf) {
                        throw new IllegalArgumentException(
                            intf + " is not visible from class loader");
                    }
       
                    if (!interfaceClass.isInterface()) {
                        throw new IllegalArgumentException(
                            interfaceClass.getName() + " is not an interface");
                    }
                    if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                        throw new IllegalArgumentException(
                            "repeated interface: " + interfaceClass.getName());
                    }
                }
    
                String proxyPkg = null;     
                int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
                for (Class<?> intf : interfaces) {
                    int flags = intf.getModifiers();
                    if (!Modifier.isPublic(flags)) {
                        accessFlags = Modifier.FINAL;
                        String name = intf.getName();
                        int n = name.lastIndexOf('.');
                        String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                        if (proxyPkg == null) {
                            proxyPkg = pkg;
                        } else if (!pkg.equals(proxyPkg)) {
                            throw new IllegalArgumentException(
                                "non-public interfaces from different packages");
                        }
                    }
                }
    
                if (proxyPkg == null) {
                    // public代理接口,使用com.sun.proxy包名
                    proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
                }
    
                long num = nextUniqueNumber.getAndIncrement();
                    //代理类名称
                String proxyName = proxyPkg + proxyClassNamePrefix + num;
                    //代理类class文件
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces, accessFlags);
                try {
                     //使用类加载器将代理类的字节码文件加载到JVM中
                    return defineClass0(loader, proxyName,
                                        proxyClassFile, 0, proxyClassFile.length);
                } catch (ClassFormatError e) {
                   
                    throw new IllegalArgumentException(e.toString());
                }
            }
        }

    进入ProxyGenerator.generateProxyClass()方法 

     public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
            ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
             //生成代理类class文件
            final byte[] var4 = var3.generateClassFile();
            //是否保存到磁盘
            if (saveGeneratedFiles) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        try {
                            int var1 = var0.lastIndexOf(46);
                            Path var2;
                            if (var1 > 0) {
                                Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                                Files.createDirectories(var3);
                                var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                            } else {
                                var2 = Paths.get(var0 + ".class");
                            }
    
                            Files.write(var2, var4, new OpenOption[0]);
                            return null;
                        } catch (IOException var4x) {
                            throw new InternalError("I/O exception saving generated file: " + var4x);
                        }
                    }
                });
            }
    
            return var4;
        }

    到这里有一个是否保存到磁盘的参数,我们通过测试类进行设置,回到自己写的测试类,增加这句设置

     System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

    public class Test {
        public static void main(String[] args) {
             Movie movie = new NoVipMovie();
             MyInvocationHandler myInvocationHandler = new MyInvocationHandler(movie);
             System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
             Movie proxyObject  =(Movie) Proxy.newProxyInstance(NoVipMovie.class.getClassLoader(),NoVipMovie.class.getInterfaces(),myInvocationHandler);
             proxyObject.play();
        }
    }
    

    再运行一下测试类,发现多了一个class文件。

     

    查看 $Proxy0

    public final class $Proxy0 extends Proxy implements Movie {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
    
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final void play() throws  {
            try {
                super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return (Integer)super.h.invoke(this, m0, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m3 = Class.forName("com.y20.chuan.test.jdkProxy.Movie").getMethod("play");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }

    通过观察class文件内容可知;

    1、代理类继承了Proxy类并且实现了要代理的接口,由于java不支持多继承,所以这就是JDK动态代理为什么不能代理类

    2、重写了equals、hashCode、toString方法

    3、有一个静态代码块,通过反射或者代理类的所有方法

    4、通过invoke执行代理类中的目标方法

    四、JDK动态代理的一些问题

    1.JDK动态代理为什么只能代理接口?

    因为java不支持多继承,所以只能通过实现接口来实现。

    2.为什么要重写equals、hashCode、toString方法?

    因为如果不重写这三个方法,而被代理类重写了这三个方法,那么代理类则会去调用object里面的这三个方法,代理类重写的方法就无用了。

    3.为什么动态代理类要继承 Proxy?

    因为Proxy对所有的代理类进行了一层抽象,它们有一个共同点,都持有一个InvocationHandle对象。当然,不要Proxy也可以,但是需要每个代理类自己要有一个InvocationHandle对象,从性能方面看,通过继承,可以减少生成代理类时的损耗。

     

    展开全文
  • 实现了一份jdk动态代理的demo,做了详细注释,供以后复习使用,另外,这里为什么需要大于100个字节才能传资源?我就不想那么多不行吗???
  • Spring框架的AOP中重要的一个知识点,动态代理,springAOP框架会根绝实际情况选择使用jdk动态代理还是cglib的动态代理
  • Jdk动态代理 底层源码

    2021-01-20 03:10:25
    java动态代理主要有2种,Jdk动态代理、Cglib动态代理,本文主要讲解Jdk动态代理的使用、运行机制、以及源码分析。当spring没有手动开启Cglib动态代理,即:或@EnableAspectJAutoProxy(proxyTargetClass = true),...
  • 简介 JDK动态代理就是在程序运行时,运用反射机制...动态代理又分为JDK动态代理和CGLIB动态代理,一个通过反射生成代理类,一个通过asm开源包,修改字节码生成子类。区别在于JDK只能代理接口,所以有需要代理的类...
  • JDK动态代理

    千次阅读 2018-10-08 19:05:43
    Spring AOP使用了两种代理机制: 一种是基于JDK动态代理; 一种是基于 CGLib 的动态代理. JDK本身只提供接口代理, 而不支持类的代理. JDK动态代理主要涉及 java.lang.reflect 包中的两个类: Proxy 和 ...
  • NULL 博文链接:https://lizhao6210-126-com.iteye.com/blog/1711218
  • 静态代理JDK动态代理,Cglib动态代理详解

    千次阅读 热门讨论 2021-02-12 13:21:39
    文章目录一、代理模式二、静态代理三、动态代理 一、代理模式 代理模式(Proxy Pattern)是程序设计中的一种设计模式,他的特征是代理类和委托类实现有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、...
  • Java JDK 动态代理(AOP)使用及实现原理分析

    万次阅读 多人点赞 2019-05-08 21:28:06
    三、JDK动态代理怎么使用? 四、动态代理怎么实现的? 五、结论 一、什么是代理代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,...
  • 基于java的jdk动态代理, 比较了静态代理动态代理的区别,以及动态代理的底层实现,反编译class文件 jdk动态代理和cglib的区别
  • java jdk 动态代理演示demo2 包含将生成的字节码保存到本地
  • 为什么JDK动态代理要基于接口实现而不能基于继承实现? 1. 问题 在阅读本文之前,可以先思考一下下面几个问题。 为什么说JDK动态代理要基于接口实现,而不能基于继承来实现? 在JDK动态代理中,在目标对象...
  • 一、代理模式介绍 什么是代理模式呢?我很忙,忙的没空理你,那你要...二、JDK静态代理 场景:比如西门庆找潘金莲,那潘金莲不好意思答复呀,咋办,找那个王婆做代理呀,然后就绿了大郎。。。 第一步:定义一种类...
  • jdk动态代理和cglib动态代理详解

    千次阅读 多人点赞 2018-08-22 15:14:11
    本文内容概括: 静态代理概述 基于继承方式实现静态代理 基于聚合方式实现静态代理 ...如上图,代理模式可分为动态代理和静态代理,我们比较常用的有动态代理中的jdk动态代理和Cglib代理,像spr...
  • 深入理解代理模式:静态代理JDK动态代理

    万次阅读 多人点赞 2018-03-01 00:22:11
    Java动态代理机制的出现使得Java开发人员只需要简单地指定一组接口及委托类对象便能动态地获得代理类,并且其所生成的代理类在将所有的方法调用分派到委托对象上反射执行的同时,还可以对方法进行增强,这也正是...
  • java jdk 动态代理 演示demo

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 159,758
精华内容 63,903
关键字:

jdk动态代理