让我们来揭开AOP和动态代理技术的神秘面纱
AOP是什么东西
首先来说AOP并不是Spring框架的核心技术之一,AOP全称AspectOrientProgramming,即面向切面的编程。其要解决的问题就是在不改变源代码的情况下,实现对逻辑功能的修改。常用的场景包括记录日志、异常处理、性能监控、安全控制(例如拦截器)等,总结起来就是,凡是想对当前功能做变更,但是又不想修改源代码的情况下,都可以考虑是否可以用AOP实现。
为什么要面向切面呢,我直接改源代码不是很好吗?当然没有问题,如果情况允许。但是考虑到下面这些情况,我本来写好了1000个方法,有一天,我想加入一些控制,我想在执行方法逻辑之前,检查一些系统参数,参数检查没问题再执行逻辑,否则不执行。这种情况怎么办呢,难道要修改这1000个方法吗,那简直就是灾难。还有,有些线上逻辑执行缓慢,但我又不想重新部署环境,因为那样会影响线上业务,这种情况下,也可以考虑AOP方式,Btrace就是这样一个线上性能排查的神器。SpringAOP的用法
面向切面编程,名字好像很炫酷,但是使用方式已经被Spring封装的非常简单,只需要简单的配置即可实现。使用方式不是本文介绍的重点,下面仅演示最简单最基础的使用,实现对调用的方法进行耗时计算,并打印出来。
环境说明:JDK1。8,Springmvc版本4。3。2。RELEASE
1、首先引用Springmvc相关的maven包,太多了,就不列了,只列出Springaop相关的dependencygroupIdorg。springframeworkgroupIdspringaopartifactIdversion4。3。2。RELEASEversiondependencydependencygroupIdorg。aspectjgroupIdaspectjrtartifactIdversion1。8。9versiondependencydependencygroupIdorg。aspectjgroupIdaspectjweaverartifactIdversion1。8。9versiondependency
2、在Springmvc配置文件中增加关于AOP的配置,内容如下:lt;?xmlversion1。0encodingUTF8?beansdefaultlazyinittruexmlnshttp:www。springframework。orgschemabeansxmlns:xsihttp:www。w3。org2001XMLSchemainstancexmlns:contexthttp:www。springframework。orgschemacontextxmlns:mvchttp:www。springframework。orgschemamvcxmlns:phttp:www。springframework。orgschemapxmlns:aophttp:www。springframework。orgschemaaopxsi:schemaLocationhttp:www。springframework。orgschemabeanshttp:www。springframework。orgschemabeansspringbeans4。3。xsdhttp:www。springframework。orgschemamvchttp:www。springframework。orgschemamvcspringmvc4。3。xsdhttp:www。springframework。orgschemacontexthttp:www。springframework。orgschemacontextspringcontext4。3。xsdhttp:www。springframework。orgschemaaophttp:www。springframework。orgschemaaopspringaop4。3。xsd!自动扫描与装配beancontext:componentscanbasepackagekite。lab。springcontext:componentscan!启动AspectJ支持bean
3、创建切面类,并在kite。lab。spring。service包下的方法设置切面,使用Around注解监控,实现执行时间的计算并输出,内容如下:importorg。aspectj。lang。JoinPoint;importorg。aspectj。lang。ProceedingJoinPoint;importorg。aspectj。lang。annotation。;importorg。springframework。stereotype。Component;importorg。springframework。util。StopWatch;ComponentAspectpublicclassPerformanceMonitor{配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点Pointcut(execution(kite。lab。spring。service。。(。。)))publicvoidaspect(){}Around(aspect())publicObjectmethodTime(ProceedingJoinPointpjp)throwsThrowable{StopWatchstopWatchnewStopWatch();stopWatch。start();开始ObjectretValpjp。proceed();stopWatch。stop();结束System。out。println(String。format(方法s耗时sms!,pjp。getSignature()。toShortString(),stopWatch。getTotalTimeMillis()));returnretVal;}}
4、被切面监控的类定义如下:packagekite。lab。spring。service;publicclassWorker{publicStringdowork(){System。out。println(生活向来不易,我正在工作!);return;}}
5、加载Springmvc配置文件,并调用Worker类的方法publicstaticvoidmain(String〔〕args){StringfilePathspringservlet。xml;ApplicationContextacnewFileSystemXmlApplicationContext(filePath);Workerworker(Worker)ac。getBean(worker);worker。dowork();}
6、显示结果如下:
用法介绍完了,让我们脱下AOP华丽的衣衫,不,是揭下她神秘的面罩,来看一看她的芳容,了解一下她的内心。SpringAOP原理
AOP的实现原理就是动态的生成代理类,代理类的执行过程为:执行我们增加的代码(例如方法日志记录)回调原方法增加的代码逻辑。看图比较好理解:
SpringAOP动态代理可能采用JDK动态代理或CGlib动态生成代理类两种方式中的一种,决定用哪一种方式的判断标准就是被切面的类是否有其实现的接口,如果有对应的接口,则采用JDK动态代理,否则采用CGlib字节码生成机制动态代理方式。
代理模式是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。代理类和委托类实现相同的接口,所以调用者调用代理类和调用委托类几乎感觉不到差别。
是不是看完了定义,感觉正好可以解决切面编程方式要解决的问题。下图是基本的静态代理模式图:
而动态代理的意思是运行时动态生成代理实现类,由于JVM的机制,需要直接操作字节码,生成新的字节码文件,也就是。class文件。JDK动态代理
JDK动态代理模式采用sun的ProxyGenerator的字节码框架。要说明的是,只有实现了接口的类才能使用JDK动态代理技术,实现起来也比较简单。
1、只要实现InvocationHandler接口,并覆写invoke方法即可。具体实现代码如下:packagekite。lab。spring。aop。jdkaop;importjava。lang。reflect。InvocationHandler;importjava。lang。reflect。Method;importjava。lang。reflect。Proxy;JdkProxyauthorfengzhengpublicclassJdkProxyimplementsInvocationHandler{privateObjecttarget;绑定委托对象并返回一个代理类paramtargetreturnpublicObjectbind(Objecttarget){this。targettarget;取得代理对象returnProxy。newProxyInstance(target。getClass()。getClassLoader(),target。getClass()。getInterfaces(),this);}调用方法OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object〔〕args)throwsThrowable{Objectresultnull;System。out。println(事物开始);执行方法resultmethod。invoke(target,args);System。out。println(事物结束);returnresult;}}
Proxy。newProxyInstance方法用于动态生成实际生成的代理类,三个参数依次为被代理类的类加载器、被代理类所实现的接口和当前代理拦截器。
覆写的invoke中可以加入我们增加的业务逻辑,然后回调原方法。
2、被代理的类仍然用的前面springaop介绍的那个worker类,只不过我们需要让这个类实现自接口,接口定义如下:packagekite。lab。spring。service;IWorkerpublicinterfaceIWorker{Stringdowork();}
3、实际调用如下:publicstaticvoidmain(String〔〕args){JdkProxyjdkProxynewJdkProxy();IWorkerworker(IWorker)jdkProxy。bind(newWorker());worker。dowork();}
原理说明:jdkProxy。bind会生成一个实际的代理类,这个生成过程是利用的字节码生成技术,生成的代理类实现了IWorker接口,我们调用这个代理类的dowork方法的时候,实际在代理类中是调用了JdkProxy(也就是我们实现的这个代理拦截器)的invoke方法,接着执行我们实现的invoke方法,也就执行了我们加入的逻辑,从而实现了切面编程的需求。
我们把动态生成的代理类字节码文件反编译一下,也就明白了。由于代码较长,只摘出相关部分。
首先看到类的接口和继承关系:publicfinalclassProxy0extendsProxyimplementsIWorker
代理类被命名为Proxy0,继承了Proxy,并且实现了IWorker,这是关键点。
找到dowork方法,代码如下:publicfinalStringdowork()throws{try{return(String)super。h。invoke(this,m3,(Object〔〕)null);}catch(RuntimeExceptionErrorvar2){throwvar2;}catch(Throwablevar3){thrownewUndeclaredThrowableException(var3);}}
super。h就是我们实现的JdkProxy这个类,可以看到调用了这个类的invoke方法,并且传入了参数m3,再来看m3是什么,m3Class。forName(kite。lab。spring。service。IWorker)。getMethod(dowork,newClass0);
看到了吧,m3就是dowork方法,是不是流程就明确了。
但是,并不是所有的被代理的类(要被切面的类)都实现了某个接口,没有实现接口的情况下,JDK动态代理就不行了,这时候就要用到CGlib字节码框架了。CGLIB动态代理
CGlib库使用了ASM这一个轻量但高性能的字节码操作框架来转化字节码,它可以在运行时基于一个类动态生成它的子类。厉害了吧,不管有没有接口,凡是类都可以被继承,拥有这样的特点,原则上来说,它可以对任何类进行代码拦截,从而达到切面编程的目的。
CGlib不需要我们非常了解字节码文件(。class文件)的格式,通过简单的API即可实现字节码操作。
基于这样的特点,CGlib被广泛用于如SpringAOP等基于代理模式的AOP框架中。
下面就基于CGlib实现一个简单的动态代理模式。
1、创建拦截类实现MethodInterceptor接口,并覆intercept方法,在此方法中加入我们增加的逻辑,代码如下:publicclassMyAopWithCGlibimplementsMethodInterceptor{OverridepublicObjectintercept(Objecto,Methodmethod,Object〔〕objects,MethodProxymethodProxy)throwsThrowable{System。out。println(嘿,你在干嘛?);methodProxy。invokeSuper(o,objects);System。out。println(是的,你说的没错。);returnnull;}
2、被代理的类依然是上面的Worker类,并且不需要接口。
3、客户端调用代理方法的代码如下:publicstaticvoidmain(String〔〕args){System。setProperty(DebuggingClassWriter。DEBUGLOCATIONPROPERTY,cglib);MyAopWithCGlibaopnewMyAopWithCGlib();EnhancerenhancernewEnhancer();enhancer。setSuperclass(Worker。class);enhancer。setCallback(aop);Workerworker(Worker)enhancer。create();worker。dowork();}
代码第一行是要将动态生成的字节码文件持久化到磁盘,方便反编译观察。
利用CGlib的Enhancer对象,设置它的继承父类,设置回调类,即上面实现的拦截类,然后用create方法创造一个Worker类,实际这个类是Worker类的子类,然后调用dowork方法。执行结果如下:
可以看到我们织入的代码起作用了。
4、上面功能比较简单,它会横向切入被代理类的所有方法中,下来我们稍微做的复杂一点。控制一下,让有些方法被织入代码,有些不被织入代码,模仿Springaop,我们新增一个注解,用于注解哪些方法要被横向切入。注解如下:packagekite。lab。spring。aop。AopWithCGlib;importjava。lang。annotation。;CGLIBauthorfengzhengRetention(RetentionPolicy。RUNTIME)Target(ElementType。METHOD)DocumentedpublicinterfaceCGLIB{Stringvalue()default;}
5、然后再Worker中增加一个方法,并应用上面的注解packagekite。lab。spring。service;importkite。lab。spring。aop。AopWithCGlib。CGLIB;WorkerauthorfengzhengpublicclassWorker{publicStringdowork(){System。out。println(生活向来不易,我正在工作!);return;}CGLIB(valuecglib)publicvoiddowork2(){System。out。println(生活如此艰难,我在奔命!);}}
我们在dowrok2上应用了上面的注解
6、在拦截方法中加入注解判断逻辑,如果加了上面的注解,就加入织入的代码逻辑,否则不加入,代码如下:OverridepublicObjectintercept(Objecto,Methodmethod,Object〔〕objects,MethodProxymethodProxy)throwsThrowable{Annotation〔〕annotationsmethod。getDeclaredAnnotations();booleanisCglibfalse;for(Annotationannotation:annotations){if(annotation。annotationType()。getName()。equals(kite。lab。spring。aop。AopWithCGlib。CGLIB)){isCglibtrue;}}if(isCglib){System。out。println(嘿,你在干嘛?);methodProxy。invokeSuper(o,objects);System。out。println(是的,你说的没错。);}returnnull;}
7、调用方法如下:publicstaticvoidmain(String〔〕args){System。setProperty(DebuggingClassWriter。DEBUGLOCATIONPROPERTY,cglib);MyAopWithCGlibaopnewMyAopWithCGlib();EnhancerenhancernewEnhancer();enhancer。setSuperclass(Worker。class);enhancer。setCallback(aop);Workerworker(Worker)enhancer。create();worker。dowork();worker。dowork2();}
执行结果应该为dowork不执行被织入的逻辑,dowork2执行被织入的代码逻辑,执行结果如下:
另外说一下,CGlib不支持final类,CGlib的执行速度比较快,但是创建速度比较慢,所以如果两种动态代理都适用的场景下,有大量动态代理类创建的场景下,用JDK动态代理模式,否则可以用CGlib。
标准的SpringMVC框架,一般都是一个服务接口类对应一个实现类,所以根据SpringAOP的判断逻辑,应该大部分情况下都是使用的JDK动态代理模式。当然也可以手动改成CGlib模式。最后
掌握AOP的实现原理,你就掌握了又一个核心科技。回过头一看,害,也挺简单。
作者:古时的风筝
链接:https:juejin。impost5ed723606fb9a047a07f2ec2
反垄断元年,互联网拆墙释放创新活力即将过去的2021年,反垄断互联互通成为互联网领域当仁不让的关键词。多年割据后,平台之间横亘的高墙正在逐渐打破,一个重新走向互联互通的互联网时代正在到来。微信淘宝链接分享……
未来的今日头条,是否跟百度一样几乎啥都可以查的到?自从有了今日头条后,很少上百度了,百度上能搜索到的内容,头条上一样可以找的到。现在的头条走大众化,亲民化的路线,比较贴近民生接地气,头条给我的感觉是朝气蓬勃充满活力,哪一样也不……
20条javaScript示例代码让你的代码更简洁1。通过条件判断给变量赋值布尔值的正确姿势badif(aa){btrue}else{bfalse}goodbaa2。在if中判断数组长度不为零的正确姿势badif(arr。le……
信息化时代下的教育将何去何从?我为什么强调这个时代,因为这个时代是信息化的时代,而信息化的时代是知识大爆炸的,是人工智能的,是多而不是少的问题。人工智能可以取代很多工作,例如:前台、服务员、保安等。我一直觉……
在部队大口喝酒的日子,有多少战友还记得那种情景,欢迎唠一唠?我当兵时部队还没有禁酒令,每到节假日会餐可以饮酒。记得九八年十月份,也是我们新婚后的第一个节日,我去团里看值班的爱人,正赶上他们连队集合去吃午饭,其他连队值班的主官见我来……
小米股价跌破发行价,雷军股票软件要装回来?截至1月28日盘中,小米低开低走,跌2。18,报16。12港元股,自股价最高点跌幅近55,股价已腰斩。小米集团于2018年7月9日登陆港交所,发行价17港元股,但上市即破……
1月27日晚两市互动精华富瑞特装:子公司富瑞能服潼深3井顺利投产迈瑞医疗:新冠抗体检测试剂已拿到CE及境外多国注册证耐普矿机:产品有应用到锂矿企业但占比较低谱尼测试:北京、上海等四家……
2000元左右的热门5G手机,参考这几款,颜值性价比都很高1、realmeQ2Pro:8128G,1399元realmeQ2ProrealmeQ2Pro是realmeX7的套娃机型,性价比要高不少;搭载的是天玑800U处理……
荣耀X20Max网上爆料!居然用了慢充?随着荣耀Magic系列的热销。曾经让大屏手机用户期待的发布会彩蛋X20Max机型的消息逐渐多了起来。这其中信息真假难辨。加上有些公众号一向不负责任胡乱爆料比如小米Max4荣耀N……
华为手机科技感十足的四个技巧,一分钟告诉你,不用就可惜了对于国内的华为相信大家都比较熟悉,而从这几年华为所发布的几款手机来看,不论是从外观、性能方面都受用户喜爱。同时也圈了不少的花粉。今天小编就来分享一下华为手机中这几个技巧,应该没……
国内首款智能加油机器人正式上班,3分钟内即可完成随着科技的发展,人工智能变得越来越成熟。如今,连加油都开始使用机器人了。国内首款智能加油机器人开始上班,我们一起来看看,它究竟有啥强大之处。1hr国内首款智能加油机器人正……
微信小程序都有哪些?手机必备微信小程序有哪些?微信小程序Top100榜单。微信最近推出的小程序肯定让很多手机内存报警的用户看到了希望,因为再也不必安装那么多软件占据着手机的空间,只需要在微信搜索对……