动态代理
1、先谈静态代理
对于静态代理,我们已经很熟悉了。我们拥有一个抽象类,真实类继承自抽象类并重写其业务方法,代理类持有真实类的对象实例,在重写业务方法中通过调用真实类的方法,并且添加自己的逻辑。这样代理类就实现了对真实类的行为代理。
静态代理的缺点在于,我们需要实现多个代理类,这无疑是很崩溃的。
2、JDK动态代理
优点先行:我们说静态代理的缺点在于需要为每一个真实类都生成一个对应的代理类,这样就很繁琐。动态代理呢,我们是动态生成目标类的动态代理类,根本不需要为每一个只需要定义一个动态代理类,就可以代理所有、无数的真实类;当然我们需要一个类来实现
InvocationHandler
接口即可。也就是说我们只需要一个类即可。
java中提供了一个接口InvocationHandler
和Proxy
类来实现动态代理。
InvocationHandler
接口是代理实例用来调用处理程序(代理行为)的接口,它只有一个方法。接收的参数为:动态代理对象,代理对象调用的接口方法的实例,执行参数。
就如下面的Market
类实现了InvocationHandler
接口,内部的Object target
用来持有动态代理的真实对象实例,bind
方法用来接收外部真实类对象传递给target
。然后重点就是重写invoke
方法了,这个方法和下面的Proxy
类一起讲解。
这个类是用来处理动态代理的逻辑的,它的作用是接收生成的动态代理对象和被代理对象实现的接口与运行时的参数,这样就能拦截对接口方法的调用,从而实现自己的拦截逻辑。
如下图,我们在测试类中,使用Proxy
类的newProxyInstance
静态方法来生成动态代理对象。该方法很重要,传入的参数:被代理类的类加载器,被代理类的所实现的接口(用数组保存的),实现了InvocationHandler
的接口的动态代理处理类。
这个类用来生成了动态代理的对象,很神奇的是,我们调用接口的方法时,就发现方法被拦截了,这时候生成的是动态代理对象,所以接口方法的逻辑已经是被拦截的逻辑了。(这里我们看成是一个新的动态代理的对象就好了)
为避免混乱,将包、类的结构给出:
动态代理类的命名规则:包名+$Proxy
+id,同一个接口的实现类的id是相同的。
JDK动态代理的总结(不足)
1.JDK动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法。因此,对一个类而言,如果想要对它使用JDK动态代理,那么这个类就必须实现接口interface
,并且我们拦截的是其接口声明的方法才行。简言之,JDK动态代理只能对实现了接口的类生成代理,而不能针对未实现接口的类。
2.我们发现,在JDK动态代理的实现过程中,我们无法对接口中的各个方法都实现一段独有的逻辑(如果被代理类实现的接口有多个方法,我们的动态代理的拦截逻辑却只有一段,所有的方法被拦截时都是用相同的逻辑来处理)。这是一个很大的问题。
3.JDK动态代理生成的动态代理类,是无法调用原业务类自己拥有的方法的(即接口中没有声明的方法)。要明白,动态代理类的存在意义是为了拦截方法并修改逻辑;而JDK动态代理的局限性之一就是只能拦截接口所声明的方法。
4.JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler
来处理。
3、CGLib动态代理
和JDK动态代理不同的是,CGLib动态代理解决了JDK动态代理的第一个不足。也就是说,如果一个类没有实现接口,那么我们还可以使用CGLib来生成其动态代理对象。
这里我们要讲到MethodInterceptor
接口了。注意,CGLib动态代理并不是JDK中的类,它是外部的lib包。
我们还需要结合动态代理对象的生成来讲解。
同样,我们只需要定义一个动态代理的处理类即可。实现了MethodInterceptor
接口的类,需要重写intercept
方法,用来拦截被代理类的方法调用。然后在该类中定义一个生产动态代理对象的方法,该方法接收被代理类的对象,然后拦截方法,设置调用拦截方法的逻辑,最后返回创建的动态代理对象。
CGLib动态代理的总结
1.CGlib动态代理的原理是通过继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理。 因为动态代理类是继承自业务类,所以该类和方法不能声明成final(无法继承或重写)。
2.同JDK动态代理一样,CGLib动态代理也无法做到对业务类的每个方法都实现不同的拦截逻辑。
JDK动态代理和CGLib动态代理的比较
1.JDK动态代理是面向接口,在创建代理实现类时比CGLib要快,创建动态代理类的速度快。
2.CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么会失败),在创建代理这一块没有JDK动态代理快,但是运行速度比JDK动态代理要快。
3.2者最终都是生成了一个新的动态代理类对象。
Spring中的动态代理
1、Spring在选择用JDK还是CGLiB的依据?
(1)当Bean实现接口时,Spring就会用JDK的动态代理。
(2)当Bean没有实现接口时,Spring使用CGlib实现。
(3)可以强制使用CGlib(在spring配置中加入
)
2、CGlib比JDK快?
(1)在对JDK动态代理与CGlib动态代理的代码实验中看,1W次执行下,JDK7及8的动态代理性能比CGlib要好20%左右。
(2)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。