前言
代理模式是一种设计模式,可能使得在不批改源指标的前提下,额定扩大源指标的性能。即通过拜访源指标的代理类,再由代理类去拜访源指标。这样一来,要扩大性能,就无需批改源指标的代码了。只须要在代理类上减少就能够了。
其实代理模式的核心思想就是这么简略,在java中,代理又分动态代理和动静代理2种,其中动静代理依据不同实现又辨别基于接口的的动静代理和基于子类的动静代理。
其中动态代理因为比较简单,面试中也没啥问的,在代理模式一块,问的最多就是动静代理,而且动静代理也是spring aop的核心思想,spring其余很多性能也是通过动静代理来实现的,比方拦截器,事务管制等。
熟练掌握动静代理技术,能让你业务代码更加精简而优雅。如果你须要写一些中间件的话,那动静代理技术更是必不可少的技能包。
那此篇文章就带大家一窥动静代理的所有细节吧。
动态代理
在说动静代理前,还是先说说动态代理。
所谓动态代理,就是通过申明一个明确的代理类来拜访源对象。
咱们有2个接口,Person和Animal。每个接口各有2个实现类,UML如下图:
每个实现类中代码都差不多统一,用Student来举例(其余类和这个简直截然不同)
<code class="java">public class Student implements Person{ private String name; public Student() { } public Student(String name) { this.name = name; } @Override public void wakeup() { System.out.println(StrUtil.format("学生[{}]晚上醒来啦",name)); } @Override public void sleep() { System.out.println(StrUtil.format("学生[{}]早晨睡觉啦",name)); } }
假如咱们当初要做一件事,就是在所有的实现类调用wakeup()
前减少一行输入早安~
,调用sleep()
前减少一行输入晚安~
。那咱们只须要编写2个代理类PersonProxy
和AnimalProxy
:
PersonProxy:
<code class="java">public class PersonProxy implements Person { private Person person; public PersonProxy(Person person) { this.person = person; } @Override public void wakeup() { System.out.println("早安~"); person.wakeup(); } @Override public void sleep() { System.out.println("晚安~"); person.sleep(); } }
AnimalProxy:
<code class="java">public class AnimalProxy implements Animal { private Animal animal; public AnimalProxy(Animal animal) { this.animal = animal; } @Override public void wakeup() { System.out.println("早安~"); animal.wakeup(); } @Override public void sleep() { System.out.println("晚安~"); animal.sleep(); } }
最终执行代码为:
<code class="java">public static void main(String[] args) { Person student = new Student("张三"); PersonProxy studentProxy = new PersonProxy(student); studentProxy.wakeup(); studentProxy.sleep(); Person doctor = new Doctor("王传授"); PersonProxy doctorProxy = new PersonProxy(doctor); doctorProxy.wakeup(); doctorProxy.sleep(); Animal dog = new Dog("旺旺"); AnimalProxy dogProxy = new AnimalProxy(dog); dogProxy.wakeup(); dogProxy.sleep(); Animal cat = new Cat("咪咪"); AnimalProxy catProxy = new AnimalProxy(cat); catProxy.wakeup(); catProxy.sleep(); }
输入:
早安~ 学生[张三]晚上醒来啦 晚安~ 学生[张三]早晨睡觉啦 早安~ 医生[王传授]晚上醒来啦 晚安~ 医生[王传授]早晨睡觉啦 早安~~ 小狗[旺旺]晚上醒来啦 晚安~~ 小狗[旺旺]早晨睡觉啦 早安~~ 小猫[咪咪]晚上醒来啦 晚安~~ 小猫[咪咪]早晨睡觉啦
论断:
动态代理的代码置信曾经不必多说了,代码非常简单易懂。这里用了2个代理类,别离代理了Person
和Animal
接口。
这种模式尽管好了解,然而毛病也很显著:
- 会存在大量的冗余的代理类,这里演示了2个接口,如果有10个接口,就必须定义10个代理类。
- 不易保护,一旦接口更改,代理类和指标类都须要更改。
JDK动静代理
动静代理,艰深点说就是:无需申明式的创立java代理类,而是在运行过程中生成”虚构”的代理类,被ClassLoader加载。从而防止了动态代理那样须要申明大量的代理类。
JDK从1.3版本就开始反对动静代理类的创立。次要外围类只有2个:java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
。
还是后面那个例子,用JDK动静代理类去实现的代码如下:
创立一个JdkProxy类,用于对立代理:
<code class="java">public class JdkProxy implements InvocationHandler { private Object bean; public JdkProxy(Object bean) { this.bean = bean; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (methodName.equals("wakeup")){ System.out.println("早安~~~"); }else if(methodName.equals("sleep")){ System.out.println("晚安~~~"); } return method.invoke(bean, args); } }
执行代码:
<code class="java">public static void main(String[] args) { JdkProxy proxy = new JdkProxy(new Student("张三")); Person student = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy); student.wakeup(); student.sleep(); proxy = new JdkProxy(new Doctor("王传授")); Person doctor = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy); doctor.wakeup(); doctor.sleep(); proxy = new JdkProxy(new Dog("旺旺")); Animal dog = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy); dog.wakeup(); dog.sleep(); proxy = new JdkProxy(new Cat("咪咪")); Animal cat = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy); cat.wakeup(); cat.sleep(); }
解说:
能够看到,绝对于动态代理类来说,无论有多少接口,这里只须要一个代理类。外围代码也很简略。惟一须要留神的点有以下2点:
-
JDK动静代理是须要申明接口的,创立一个动静代理类必须得给这个”虚构“的类一个接口。能够看到,这时候经动静代理类发明之后的每个bean曾经不是原来那个对象了。
-
为什么这里
JdkProxy
还须要结构传入原有的bean呢?因为解决完附加的性能外,须要执行原有bean的办法,以实现代理
的职责。这里
JdkProxy
最外围的办法就是<code class="java">public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
其中proxy为代理过之后的对象(并不是原对象),method为被代理的办法,args为办法的参数。
如果你不传原有的bean,间接用
method.invoke(proxy, args)
的话,那么就会陷入一个死循环。
能够代理什么
JDK的动静代理是也平时大家应用的最多的一种代理形式。也叫做接口代理。前几天有一个小伙伴在群里问我,动静代理是否一次能够代理一个类,多个类可不可以。
JDK动静代理说白了只是依据接口”凭空“来生成类,至于具体的执行,都被代理到了InvocationHandler
的实现类里。上述例子我是须要继续执行原有bean的逻辑,才将原有的bean结构进来。只有你须要,你能够结构进任何对象到这个代理实现类。也就是说,你能够传入多个对象,或者说你什么类都不代理。只是为某一个接口”凭空“的生成多个代理实例,这多个代理实例最终都会进入InvocationHandler
的实现类来执行某一个段独特的代码。
所以,在以往的我的项目中的一个理论场景就是,我有多个以yaml定义的规定文件,通过对yaml文件的扫描,来为每个yaml规定文件生成一个动静代理类。而实现这个,我只须要当时定义一个接口,和定义InvocationHandler
的实现类就能够了,同时把yaml解析过的对象传入。最终这些动静代理类都会进入invoke
办法来执行某个独特的逻辑。
Cglib动静代理
Spring在5.X之前默认的动静代理实现始终是jdk动静代理。然而从5.X开始,spring就开始默认应用Cglib来作为动静代理实现。并且springboot从2.X开始也转向了Cglib动静代理实现。
是什么导致了spring体系整体转投Cglib呢,jdk动静代理又有什么毛病呢?
那么咱们当初就要来说下Cglib的动静代理。
Cglib是一个开源我的项目,它的底层是字节码解决框架ASM,Cglib提供了比jdk更为弱小的动静代理。次要相比jdk动静代理的劣势有:
- jdk动静代理只能基于接口,代理生成的对象只能赋值给接口变量,而Cglib就不存在这个问题,Cglib是通过生成子类来实现的,代理对象既能够赋值给实现类,又能够赋值给接口。
- Cglib速度比jdk动静代理更快,性能更好。
那何谓通过子类来实现呢?
还是后面那个例子,咱们要实现雷同的成果。代码如下
创立CglibProxy类,用于对立代理:
<code class="java">public class CglibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); private Object bean; public CglibProxy(Object bean) { this.bean = bean; } public Object getProxy(){ //设置须要创立子类的类 enhancer.setSuperclass(bean.getClass()); enhancer.setCallback(this); //通过字节码技术动态创建子类实例 return enhancer.create(); } //实现MethodInterceptor接口办法 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { String methodName = method.getName(); if (methodName.equals("wakeup")){ System.out.println("早安~~~"); }else if(methodName.equals("sleep")){ System.out.println("晚安~~~"); } //调用原bean的办法 return method.invoke(bean,args); } }
执行代码:
<code class="java">public static void main(String[] args) { CglibProxy proxy = new CglibProxy(new Student("张三")); Student student = (Student) proxy.getProxy(); student.wakeup(); student.sleep(); proxy = new CglibProxy(new Doctor("王传授")); Doctor doctor = (Doctor) proxy.getProxy(); doctor.wakeup(); doctor.sleep(); proxy = new CglibProxy(new Dog("旺旺")); Dog dog = (Dog) proxy.getProxy(); dog.wakeup(); dog.sleep(); proxy = new CglibProxy(new Cat("咪咪")); Cat cat = (Cat) proxy.getProxy(); cat.wakeup(); cat.sleep(); }
解说:
在这里用Cglib作为代理,其思路和jdk动静代理差不多。也须要把原始bean结构传入。起因下面有说,这里不多赘述。
要害的代码在这里
<code class="java">//设置须要创立子类的类 enhancer.setSuperclass(bean.getClass()); enhancer.setCallback(this); //通过字节码技术动态创建子类实例 return enhancer.create();
能够看到,Cglib”凭空”的发明了一个原bean的子类,并把Callback指向了this,也就是以后对象,也就是这个proxy对象。从而会调用intercept
办法。而在intercept
办法里,进行了附加性能的执行,最初还是调用了原始bean的相应办法。
在debug这个生成的代理对象时,咱们也能看到,Cglib是凭空生成了原始bean的子类:
javassist动静代理
Javassist是一个开源的剖析、编辑和创立Java字节码的类库,能够间接编辑和生成Java生成的字节码。绝对于bcel, asm等这些工具,开发者不须要理解虚拟机指令,就能动静扭转类的构造,或者动静生成类。
在日常应用中,javassit通常被用来动静批改字节码。它也能用来实现动静代理的性能。
话不多说,还是一样的例子,我用javassist动静代理来实现一遍
创立JavassitProxy,用作对立代理:
<code class="java">public class JavassitProxy { private Object bean; public JavassitProxy(Object bean) { this.bean = bean; } public Object getProxy() throws IllegalAccessException, InstantiationException { ProxyFactory f = new ProxyFactory(); f.setSuperclass(bean.getClass()); f.setFilter(m -> ListUtil.toList("wakeup","sleep").contains(m.getName())); Class c = f.createClass(); MethodHandler mi = (self, method, proceed, args) -> { String methodName = method.getName(); if (methodName.equals("wakeup")){ System.out.println("早安~~~"); }else if(methodName.equals("sleep")){ System.out.println("晚安~~~"); } return method.invoke(bean, args); }; Object proxy = c.newInstance(); ((Proxy)proxy).setHandler(mi); return proxy; } }
执行代码:
<code class="java">public static void main(String[] args) throws Exception{ JavassitProxy proxy = new JavassitProxy(new Student("张三")); Student student = (Student) proxy.getProxy(); student.wakeup(); student.sleep(); proxy = new JavassitProxy(new Doctor("王传授")); Doctor doctor = (Doctor) proxy.getProxy(); doctor.wakeup(); doctor.sleep(); proxy = new JavassitProxy(new Dog("旺旺")); Dog dog = (Dog) proxy.getProxy(); dog.wakeup(); dog.sleep(); proxy = new JavassitProxy(new Cat("咪咪")); Cat cat = (Cat) proxy.getProxy(); cat.wakeup(); cat.sleep(); }
解说:
相熟的配方,相熟的滋味,大抵思路也是相似的。同样把原始bean结构传入。能够看到,javassist也是用”凭空“生成子类的形式类来解决,代码的最初也是调用了原始bean的指标办法实现代理。
javaassit比拟有特点的是,能够对所须要代理的办法用filter来设定,外面能够像Criteria结构器那样进行结构。其余的代码,如果你认真看了之前的代码演示,应该能很轻易看懂了。
ByteBuddy动静代理
ByteBuddy,字节码伙计,一听就很牛逼有不。
ByteBuddy也是一个赫赫有名的开源库,和Cglib一样,也是基于ASM实现。还有一个名气更大的库叫Mockito,置信不少人用过这玩意写过测试用例,其外围就是基于ByteBuddy来实现的,能够动静生成mock类,十分不便。另外ByteBuddy另外一个大的利用就是java agent,其次要作用就是在class被加载之前对其拦挡,插入本人的代码。
ByteBuddy十分弱小,是一个神器。能够利用在很多场景。然而这里,只介绍用ByteBuddy来做动静代理,对于其余应用形式,可能要专门写一篇来讲述,这里先给本人挖个坑。
来,还是相熟的例子,相熟的配方。用ByteBuddy咱们再来实现一遍后面的例子
创立ByteBuddyProxy,做对立代理:
<code class="java">public class ByteBuddyProxy { private Object bean; public ByteBuddyProxy(Object bean) { this.bean = bean; } public Object getProxy() throws Exception{ Object object = new ByteBuddy().subclass(bean.getClass()) .method(ElementMatchers.namedOneOf("wakeup","sleep")) .intercept(InvocationHandlerAdapter.of(new AopInvocationHandler(bean))) .make() .load(ByteBuddyProxy.class.getClassLoader()) .getLoaded() .newInstance(); return object; } public class AopInvocationHandler implements InvocationHandler { private Object bean; public AopInvocationHandler(Object bean) { this.bean = bean; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (methodName.equals("wakeup")){ System.out.println("早安~~~"); }else if(methodName.equals("sleep")){ System.out.println("晚安~~~"); } return method.invoke(bean, args); } } }
执行代码:
<code class="java">public static void main(String[] args) throws Exception{ ByteBuddyProxy proxy = new ByteBuddyProxy(new Student("张三")); Student student = (Student) proxy.getProxy(); student.wakeup(); student.sleep(); proxy = new ByteBuddyProxy(new Doctor("王传授")); Doctor doctor = (Doctor) proxy.getProxy(); doctor.wakeup(); doctor.sleep(); proxy = new ByteBuddyProxy(new Dog("旺旺")); Dog dog = (Dog) proxy.getProxy(); dog.wakeup(); dog.sleep(); proxy = new ByteBuddyProxy(new Cat("咪咪")); Cat cat = (Cat) proxy.getProxy(); cat.wakeup(); cat.sleep(); }
解说:
思路和之前还是一样,通过仔细观察代码,ByteBuddy也是采纳了发明子类的形式来实现动静代理。
各种动静代理的性能如何
后面介绍了4种动静代理对于同一例子的实现。对于代理的模式能够分为2种:
- JDK动静代理采纳接口代理的模式,代理对象只能赋值给接口,容许多个接口
- Cglib,Javassist,ByteBuddy这些都是采纳了子类代理的模式,代理对象既能够赋值给接口,又能够复制给具体实现类
Spring5.X,Springboot2.X只有都采纳了Cglib作为动静代理的实现,那是不是cglib性能是最好的呢?
我这里做了一个简略而粗犷的试验,间接把上述4段执行代码进行单线程同步循环多遍,用耗时来确定他们4个的性能。应该能看出些端倪。
JDK PROXY循环10000遍所耗时:0.714970125秒 Cglib循环10000遍所耗时:0.434937833秒 Javassist循环10000遍所耗时:1.294194708秒 ByteBuddy循环10000遍所耗时:9.731999042秒
执行的后果如上
从执行后果来看,确实是cglib成果最好。至于为什么ByteBuddy执行那么慢,不肯定是ByteBuddy性能差,也有可能是我测试代码写的有问题,没有找到正确的形式。所以这只能作为一个大抵的参考。
看来Spring抉择Cglib还是有情理的。
最初
动静代理技术对于一个常常写开源或是中间件的人来说,是一个神器。这种个性提供了一种新的解决形式。从而使得代码更加优雅而简略。动静代理对于了解spring的核心思想也有着莫大的帮忙,心愿对动静代理技术感兴趣的童鞋能试着去跑一遍示例中的代码,来增强了解。
最初附上本篇文章中所用到的全副代码,我曾经将其上传到Gitee:
如果你曾经看到这,感觉此篇文章能帮忙到你的话,请给文章点赞且分享,也心愿能关注我的公众号。我是一个开源作者,会在空余工夫分享技术和生存,和你一起成长。