这篇文章是《??AOP 那点事儿??》的续篇。在上一篇文章中,我们从写死代码到使用代理;从编程式SpringAOP到声明式SpringAOP。一切都朝着简单实用的方向发展。沿着SpringAOP的方向,RodJohnson(老罗)花了不少心思,都是为了让我们在使用Spring框架的时候不觉得麻烦,但事实并非如此。那么,老罗对SpringAOP做了哪些改进呢?现在继续!9、SpringAOP:切面之前提到的AOP框架,其实可以理解为一个拦截器框架,但是这个拦截器看起来很随意。例如,如果它拦截了一个类,那么它就会拦截该类中的所有方法。同样,我们在使用动态代理的时候,其实也会遇到这个问题。在代码中需要对拦截的方法名进行判断,以便过滤出我们需要拦截的方法。想想看,这种做法确实不优雅。在大量的真实项目中,我们似乎只需要拦截特定的方法,并不需要拦截所有的方法。所以,罗同志使用了AOP的一个很重要的工具Advisor(aspect)来解决这个问题。也是AOP的核心!是我们关注的焦点!也就是说,我们可以通过aspects组合增强类,拦截匹配条件,然后将这个aspect配置到ProxyFactory中生成代理。这里所说的“拦截匹配条件”在AOP中叫做Pointcut。其实只是一个基于表达式的拦截条件。综上所述,Advisor(切面)封装了Advice(增强)和Pointcut(切入点)。当你理解这句话时,继续读下去。我有意向GreetingImpl类添加了两个方法,均以“good”开头。接下来要做的是拦截这两个新方法,但不拦截sayHello()方法。@ComponentpublicclassGreetingImplimplementsGreeting{@OverridepublicvoidsayHello(Stringname){System.out.println("Hello!"+name);}publicvoidgoodMorning(Stringname){System.out.println("GoodMorning!"+name);}publicvoidgoodNight(Stringname){System.out.println("GoodNight!"+name);}}在SpringAOP中,老罗给我们提供了很多切面类,这些切面类我个人觉得最好用的是基于正则表达式的切面类。看一看你就明白了:注意上面代理对象的配置中的interceptorNames不再是增强,而是切面,因为增强已经封装到切面中了此外,切面还定义了切点(正则表达式),目的是只拦截满足切点匹配条件的方法。需要强调的是,这里的切入点表达式是基于正则表达式的。例子中“aop.demo.GreetingImpl.good.*”表达式后面的“.*”表示匹配所有字符,翻译过来就是“匹配aop.demo.GreetingImpl类中以good开头的方法”。SpringAOP中除了RegexpMethodPointcutAdvisor之外,还提供了几个切面类,如:DefaultPointcutAdvisor:默认切面(可以扩展自定义切面)NameMatchMethodPointcutAdvisor:根据方法名匹配切面StaticMethodMatcherPointcutAdvisor:匹配静态方法的切面一般来说,它让用户配置一个或几个agent似乎是可以接受的,但是随着项目的扩大,需要配置的agent会越来越多,而且配置需要更多的重复劳动。不说麻烦,还是很容易出错。Spring框架可以自动为我们生成代理吗?10、SpringAOP:自动代理(扫描Bean名称)SpringAOP提供了一个可以根据Bean名称自动生成代理的工具,它就是BeanNameAutoProxyCreator。它是这样配置的:...以上BeanNameAutoProxyCreator的使用只是为后缀“Impl”的Bean生成代理。需要注意的是,我们这里不能定义代理接口,也就是interfaces属性,因为我们根本不知道这些bean实现了多少个接口。这时候不能代理接口,只能代理类。所以这里提供了一个新的配置项,就是optimize。如果为true,则可以优化代理生成策略(默认为false)。也就是说,如果类有接口,就代理接口(使用JDK动态代理);如果没有接口,则代理该类(使用CGLib动态代理)。而不是像之前使用的proxyTargetClass属性那样,不管代理接口的使用方式如何,都强制代理类。可见SpringAOP确实为我们提供了很多不错的服务!既然CGLib可以代理任何类,为什么要用JDK的动态代理呢?你肯定会问。根据多年的实际项目经验:CGLib创建代理的速度比较慢,但是创建代理后运行速度非常快,而JDK动态代理正好相反。如果运行时继续使用CGLib创建代理,系统的性能会大大降低,所以推荐在系统初始化时使用CGLib创建代理,放到Spring的ApplicationContext中,以备后用。上面的例子只能匹配目标类,不能进一步匹配其中指定的方法。匹配方法需要考虑使用aspect和pointcut。SpringAOP还提供了一个基于切面的自动代理生成器:DefaultAdvisorAutoProxyCreator。11、SpringAOP:自动代理(扫描切面配置)为了匹配目标类中的指定方法,我们还需要在Spring中配置切面和切入点:...这里不需要配置代理,因为代理会由DefaultAdvisorAutoProxyCreator自动生成。也就是说,这个类可以扫描所有方面类,并自动为它们生成代理。看来不管怎么简化,老罗还是解决不了slice的配置,是个繁重的体力劳动。在Spring的配置文件中,依然会有大量的切面配置。但是,很多时候,SpringAOP提供的切面类确实不够用。比如我们要拦截指定注解的方法,就必须扩展DefaultPointcutAdvisor类,自定义一个切面类,然后在Spring的配置文件中进行配置。方面配置。不做不知道,做了就知道挺麻烦的。老罗的解法,仿佛陷入了方面类的深渊。这就是所谓的“面向切面编程”。最重要的是方面,最头疼的也是方面。需要简化切面配置,让Spring有所突破!神一样的老罗终于意识到了这一点,接受了网友的建议,整合了AspectJ,同时也保留了上述方面和代理配置方式(为了兼容老项目,也为了维护自己的面子)。将Spring与AspectJ集成不同于直接使用AspectJ。我们不需要定义AspectJ类(它是一种扩展Java语法的新语言,需要特定的编译器),只需使用AspectJ切入点表达式即可(它是一种比正则表达式更友好的表示)。12、Spring+AspectJ(基于注解:通过AspectJ执行表达式的拦截方式)下面是实现上述环绕增强的一个最简单的例子。首先定义一个切面类:@Aspect@ComponentpublicclassGreetingAspect{@Around("execution(*aop.demo.GreetingImpl.*(..))")publicObjectaround(ProceedingJoinPointpjp)throwsThrowable{before();对象结果=pjp.proceed();后();返回结果;}privatevoidbefore(){System.out.println("之前");}privatevoidafter(){System.out.println("After");}}注意:类上标注的@Aspect注解表示该类是一个Aspect(实际上是Advisor)。这个类不需要实现任何接口,只需要定义一个方法(方法名是什么无关紧要),只需要在方法上标注@Around注解,在注解中使用AspectJ切入点表达式即可。方法的参数包括一个ProceedingJoinPoint对象,在AOP中称为Joinpoint(连接点),通过这个对象可以获取到方法的任何信息,比如:方法名,参数等。我们重点分析这个切入点pointexpression:execution(*aop.demo.GreetingImpl.*(..))execution():表示拦截方式,需要匹配的规则可以定义在括号中。第一个“*”:表示该方法的返回值是任意的。第二个“*”:表示匹配该类中的所有方法。(..):表示方法的参数是任意的。它比正则表达式更具可读性吗?如果要匹配指定的方法,只需将第二个“*”改为指定的方法名即可。如何配置?看看它有多简单:两行配置就够了,有不需要配置大量的代理,更不用说大量的切面了,真是太棒了!需要注意的是proxy-target-class="true"属性,它的默认值为false,默认只能代理Interface(使用JDK动态代理),为true时,可以代理目标类(使用CGLib动态代理)。Spring和AspectJ结合的威力远不止于此。让我们谈谈时尚。拦截指定的注解方法怎么样?13.Spring+AspectJ(基于注解:通过AspectJ@annotation表达式拦截方法)为了拦截指定的注解方法,我们首先需要定义一个注解:@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceTag{}上面定义了一个@Tag注解,可以标记在方法上,运行时生效。稍微改变一下之前Aspect类的切入点表达式:@Aspect@ComponentpublicclassGreetingAspect{@Around("@annotation(aop.demo.Tag)")publicObjectaround(ProceedingJoinPointpjp)throwsThrowable{...}...}这次使用@annotation()表达式,在括号内定义要截取的注解名即可。直接在你要拦截的方法上定义@Tag注解,就这么简单:;}}上面的例子只有一个方法,如果有多个方法而我们只想拦截其中的一部分,这个方案会更有价值。除了@Around注解,其实还有几个相关的注解,稍微总结一下:@Before:前置增强@After:后置增强@Around:环绕增强@AfterThrowing:投掷增强@DeclareParents:引入增强另外to里面有个@AfterReturning(返回后增强),也可以理解为Finally增强,相当于finally语句,在方法结束后执行,也就是说比@After晚。最后的@DeclareParents竟然是增强功能的介绍!为什么不叫@Introduction?我不知道为什么,但它的作用是引入增强功能。14、Spring+AspectJ(引入增强)为了实现基于AspectJ的引入增强,我们还需要定义一个Aspect类:@Aspect@ComponentpublicclassGreetingAspect{@DeclareParents(value="aop.demo.GreetingImpl",defaultImpl=ApologyImpl.class)privateApologyapology;}你只需要在Aspect类中定义一个需要增强的接口,也就是需要在运行时动态实现的接口。在这个接口上标注了@DeclareParents注解,它有两个属性:value:目标类defaultImpl:引入接口的默认实现类我们只需要为引入接口提供一个默认实现类就可以完成引入增强:publicclassApologyImpl实现Apology{@OverridepublicvoidsaySorry(Stringname){System.out.println("Sorry!"+name);}}以上实现在运行时会自动增强到GreetingImpl类中,即不需要修改GreetingImpl类代码,让它实现Apology接口,我们单独为这个接口提供一个实现类(ApologyImpl),以做GreetingImpl想做的事。用客户端试试:Greetinggreeting=(Greeting)语境。getBean("greetingImpl");greeting.sayHello("杰克");Apology道歉=(道歉)问候;//强制转换为Apology接口apology.saySorry("Jack");}}从SpringApplicationContext中获取greetingImpl对象(其实就是一个代理对象),可以转化为自己静态实现的接口Greeting,也可以转化为自己动态实现的接口Apology,切换起来非常方便。使用AspectJ的引入增强比原来的SpringAOP引入增强更方便,而且还可以面向接口编程(以前只能面向实现类),这也是一个非常巨大的突破。所有这一切真的非常强大和灵活!但是还是有用户因为还在使用JDK1.4(根本没有注解)而无法尝试这些功能,怎么办?没想到SpringAOP提供的那些遗留系统也考虑到了。15、Spring+AspectJ(基于配置)除了使用@Aspect注解来定义切面类,SpringAOP还提供了一种基于配置的方式来定义切面类:使用元素进行AOP配置,在其子元素中配置方面,包括增强类型、目标方法、切点等。无论你不能使用注解还是不愿意使用注解,SpringAOP可以为你提供全方位的服务,好了,我所知道的比较实用的AOP技术都在这里了,当然还有一些更高级的特性,个人精力有限,这里就不深入了。按照惯例,给个牛逼的高清无码思维导图总结一下上面的知识点:再一张表总结下各种增强类型对应的解决方案:最后给出一个UML类diagram来描述SpringAOP的整体架构: