编程常见错误50例02springcore篇8讲06丨aop下_第1页
编程常见错误50例02springcore篇8讲06丨aop下_第2页
编程常见错误50例02springcore篇8讲06丨aop下_第3页
编程常见错误50例02springcore篇8讲06丨aop下_第4页
编程常见错误50例02springcore篇8讲06丨aop下_第5页
已阅读5页,还剩10页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

tricServie,还有一个充电方法():代代publicclassElectricServicepublicvoidcharge()throwsExceptionSystem.out.println("Electriccharging 6为了在执行charge()之前,鉴定下调用者的权限,我们增加了针对于Electric的切面类AopConfig,其中包含一个@Before增强。这里的增强没有做任何事情,仅仅是打印了一行日志,然后模拟执行权限校验功能(占用1秒钟)。8System.out.println("validatinguser911}执行后,我们得到以下log,接着一切按照预期继续执行代代validatinguserElectriccharging一段时间后,由于业务发展,ElectricServicecharge辑变得更加复杂了,我们需要仅仅针对ElectricService的charge()做性能统计。为了不影响原有的业务逻辑,我们在AopConfig中添加了另一个增强,代码更改后如下:1123456789//省略publicclassAopConfig@Before("execution(*com.spring.puzzle.class6.example1.ElectricService.chapublicvoidcheckAuthority(JoinPointpjp)throwsThrowable{System.out.println("validatinguserauthority");}代@Around("execution(*com.spring.puzzle.class6.example1.ElectricService.chapublicvoidrecordPerformance(ProceedingJoinPointpjp)throwsThrowable{longstart=System.currentTimeMillis();longend=System.currentTimeMillis();System.out.println("chargemethodtimecost:"+(end-start));}}执行后得到日志如下validatinguserauthorityElectriccharging...chargemethodtimecost1022通过性能统计打印出的日志,我们可以得知charge()行时间超过了1钟。然而,该方因此我们很容易看出问题所在:当前ElectricService中charge()的执行时间,包含了权限验证的时间,即包含了@Around强的checkAuthority行的所有时间。这并不符合我们的初衷,我们需要统计的仅仅是ElectricService.charge(性能统计,它并不当然,这些都是从日志直接观察出的现象。实际上,这个问题出现的根本原因和AOP行顺序有关。针对这个案例而言,当同一个切面(Aspect)中同时包含多个不同类型的增ArodBorAfterAtRurningAtrg等),它们的执行是有顺序的。那么顺序如何?我们不妨来解析下。案例解其实一切都可以从源码中得到!在第04课我们曾经提到过,Spring初始化单例类的一般过程,基本都是getBean()->doGetBean()->getSingleton如果发现Bean不存在,则调用createBean()->doCreateBean()进行实例化。而如果我们的代码里使用了gAOdoCreateB)至于对象如何创建,大体流程我们在上一讲已经概述过了。如果你力比较好的话,应该记得在对象的创建过程中,我们贴出过这样一段代码(参考AutoCreator#create代代123456789protectedObject(Class<?>beanClass,@NullableString@NullableObject[]specificInterceptors,TargetSourcetargetSource)//省略非关键代Advisor[]advisors=buildAdvisors(beanName,specificInterceptors);//省略非关键代}其中advisors就是增强方法对象,它的顺序决定了多个增强时,到底先执行谁。而这个集合对象本身是由specificInterceptors构建出来的,而specificInterceptors又是由AdvisorAutoCreator#getAdvicesAndAdvisorsForBean方法构建代代123456789protectedObject[]Class<?>beanClass,StringbeanName,@NullableTargetSourcetargetSourceList<Advisor>advisors=findEligibleAdvisors(beanClass,beanName);if(advisors.isEmpty())return;}return}简单说,其实就是根据当前的beanClass、beanName等信息,结合所有候选advisors,最终找出匹配(Eligible)的Advisor,为什么如此?毕竟AOP点可能配置多个,而我们执行的方法不见得会被所有的配置。寻找匹配Advisor的逻辑 AdvisorAutoCreator#findEligibleAdvisors:代代protectedList<Advisor>findEligibleAdvisors(Class<?>beanClass,String//寻找候选的List<Advisor>candidateAdvisors=//根据候选的Advisor和当前bean算出匹配的List<Advisor>eligibleAdvisors=if(!eligibleAdvisors.isEmpty())//排eligibleAdvisors= return12通过研读代码,最终Advisors的顺序是由两点决定candidateAdvisors的顺序sortAdvisors进行的排序这里我们可以重点看下对本案例起关键作用的candidateAdvisors排序。实际上,它的顺序是在@Aspect标记的AopConfigBean构建时就决定了。具体而言,就是在初始化过程中会排序自己配置的Advisors,并把排序结果存入了缓存(BeanFactoryAspectJAdvisorsBuilder#advisorsCache)后续Bean创建时,直接拿出这个排序好的候选Advisors。候选Advisors排序发生在Bean构建这个结论时,我们也可以通过AopConfigBean构建中的堆栈信息验证:可以看到,排序Bean构建中进行的,而最后排序执行的关键代码位于下面的方法中(参考ReflectiveAspectJAdvisorFactory#getAdvisorMethods):123456789privateList<Method>getAdvisorMethods(Class<?>aspectClass){finalList<Method>methods=newArrayList<>();ReflectionUtils.doWithMethods(aspectClass,method->{//Excludeif(AnnotationUtils.getAnnotation(method,Pointcut.class)==null){}},//return}上述代码的重点是第九行 PARATOR)方法我们来查 PARATOR的代码,会发现它是定义代123456789代123456789staticparator=Around.class,Before.class,After.class,AfterReturning.class,(Converter<Method,Annotation>)method->{AspectJAnnotation<?>annotationreturn(annotation!=null?annotation.getAnnotation()://合并上面两者比较PARATORparator=}PARATOR本质上是续比较器, paratorparator这两个比较器通 通过这个案例,我们重点了 parator这个比较器,此对象通过实例parator类而来,而 parator类是Spring中较为经典的 parator中最终要调用的比较器,在构 第二个参数是一个lambda调函数,用来将传递的参数转化为基准比较器需要的参数类型,在构造函数中赋值于this.converter。查 parator比较器方法compare如下代代12345publicintcompare(So1,So2)Tc1=this.converter.convert(o1);Tc2=pare(c1,}可知,这里是先调用从构造函数中获取到的lambda调函数this.converter,将需要比代代(Converter<Method,Annotation>)method->AspectJAnnotation<?>annotationreturn(annotation!=null?annotation.getAnnotation():5转化功能的代码逻辑较为简单,就是返回传入方法(method)上标记的增强注(Pointcut,Around,Before,After,AfterReturning以及1123456789代privatestaticfinalClass<?>[]ASPECTJ_ANNOTATION_CLASSES=newClass<?>[]Pointcut.class,Around.class,Before.class,After.class,protectedstaticAspectJAnnotation<?>findAspectJAnnotationOnMethod(Methodmetfor(Class<?>clazz:ASPECTJ_ANNOTATION_CLASSES){AspectJAnnotation<?>foundAnnotation=findAnnotation(method,(Class<Annif(foundAnnotation!=null){return}}return}经过转化后,我们获取到的待比较的数据其实就是注解了。而它们的排序parator的第一个参数,即最终会调用的基准比较器,以下是它的关键现代码代代 Around.class,Before.class,After.class,AfterReturning.class, parator类,我们先重点注意代代1234parator(Class<?>...instanceOrder)Assert.notNull(instanceOrder,"'instanceOrder'arraymustnotbethis.instanceOrder=}构造方法也是较为简单的,只是将传递进来的instanceOrder赋予了类成员变量,继续查 parator比较器方法compare如下,也就是最终要调用的比较方代代publicintcompare(To1,To2)inti1=inti2=return(i1<i2?-1:(i1==i2?0:5一个典Comparator,代码逻辑i1、i2升序排列,即getOrder()回的值越查看getOrder()的逻辑如1123456789privateintgetOrder(@NullableT{if(object!=null)for(inti=0;i<this.instanceOrder.length;i++)//instance在instanceOrder中的“排号if{return}}}return}代返回当前传递的增强注解在this.instanceOrder的序列值,序列值越小,则越靠前。而Around.class,Before.class,After.class,AfterReturning.class,AfterThrowing.class。到此为止,答案也呼之欲出 的排序,即为不同类型增强的优先级排序越靠前,优先级越高顺序依次为Around.class,Before.class,After.class,AfterReturning.class,AfterThrowing.class问题修从上述案例解析中,我们Around型的增强被调用的优先级高于Before型的增将的动到中性能统计只需要权限统计增强保持不变,依然charge()。ElectricService类代码更改如下:代代123456789publicclassElectricServicepublicvoid{}publicvoid{System.out.println("Electriccharging...");}切面代码更改如代代123456789//省略publicclassAopConfig@Before("execution(*com.spring.puzzle.class6.example1.ElectricService.chapublicvoidcheckAuthority(JoinPointpjp)throwsThrowable{System.out.println("validatinguserauthority");}@Around("execution(*com.spring.puzzle.class6.example1.ElectricService.doCpublicvoidrecordPerformance(ProceedingJoinPointpjp)throwsThrowable{longstart=System.currentTimeMillis();longend=System.out.println("chargemethodtimecost:"+(end-}}2这里业务逻辑类ElectricService没有任何变化,仅包含一个代代1importpublicclassElectricServicepublicvoidcharge()System.out.println("Electriccharging 7切面类AspectService包含两个方法,都是Before类型增强第一个方法BoreMthod()run()vttority(作用是在调用此方法之前做权限验证,如果不符合权限限制要求,则直接抛出异常。这里为了方便演示,此方法将直接抛出异常:代代123456789//省略publicclassAopConfigpublicvoidlogBeforeMethod(JoinPointpjp)throwsThrowable{System.out.println("stepinto->"+pjp.getSignature());}publicvoidvalidateAuthority(JoinPointpjp)throwsThrowable{thrownewRuntimeException("authoritycheckfailed");}}我们对代码的执行预期为:当鉴权失败时,由于ElectricService.charge()没有被调用,那么run()的调用日志也不应该被输出,即logBeforeMethod()不应该被调用,但事实总是stepinto->voidExceptioninthread"main"java.lang.RuntimeException:authoritycheck虽然鉴权失败,抛出了异ElectricService.charge()有被调用,但是logBeforeMethod()的调用日志却被输出了,这将导致后期针对于ElectricService.charge()的调用数据统计严重失真。案例解我们继续从源代码中寻找!你应该还记得上述代码中,定PARATOR的静态代码块吧代123456789staticparator=Around.class,Before.class,After.class,(Converter<Method,Annotation>)method->{AspectJAnnotation<?>annotation=return(annotation!=null?annotation.getAnnotation():PARATOR本质是续比较器,而上个案例中我们仅仅只看了第一个代123456789staticparator=Around.class,Before.class,After.class,(Converter<Method,Annotation>)method->{AspectJAnnotation<?>annotation=return(annotation!=null?annotation.getAnnotation()://第二个比较器,用来按照方法名parator14PARATOR我们可以看到,在第12行代码中,第2个比较器 parator依然使用 代代123456}//省略非关键代parator(Converter<S,T>converter)parable(),publicstatic<T>Comparator<T>comparable()77上述代码中 parator实例其实极其简单,代码如下1123456789public代parator<TextendsComparable<T>>implementspublicstaticparatorINSTANCE=publicintcompare(To1,T{}}答案和我们的猜测完全一致 parator最终调用了String类自身compareTo(),代码代代123456789publicintcompareTo(String{intlen1=intlen2=anotherString.value.length;intlim=Math.min(len1,len2);charv1[]=charv2[]=intk=0;while(k<{charc1=v1[k];charc2=v2[k

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论