我的AOP那点事儿--2
在《我的AOP那点事儿-1》中,从写死代码,到使用代理;从编程式AOP到声明式AOP。一切都朝着简单实用主义的方向在发展。沿着 Spring AOP 的方向,Rod Johnson(老罗)花了不少心思,都是为了让我们使用 Spring 框架时不会感受到麻烦,但事实却并非如此。那么,后来老罗究竟对 Spring AOP 做了哪些改进呢?
9.Spring AOP:切面
之前谈到的 AOP 框架其实可以将它理解为一个拦截器框架,但这个拦截器似乎非常武断。比如说,如果它拦截了一个类,那么它就拦截了这个类中所有的方法。类似地,当我们在使用动态代理的时候,其实也遇到了这个问题。需要在代码中对所拦截的方法名加以判断,才能过滤出我们需要拦截的方法,想想这种做法确实不太优雅。在大量的真实项目中,似乎我们只需要拦截特定的方法就行了,没必要拦截所有的方法。于是,老罗同志借助了 AOP 的一个很重要的工具,Advisor(切面),来解决这个问题。它也是 AOP 中的核心!是我们关注的重点!
也就是说,我们可以通过切面,将增强类与拦截匹配条件组合在一起,然后将这个切面配置到 ProxyFactory 中,从而生成代理。
这里提到这个“拦截匹配条件”在 AOP 中就叫做 Pointcut(切点),其实说白了就是一个基于表达式的拦截条件罢了。
归纳一下,Advisor(切面)封装了 Advice(增强)与 Pointcut(切点 )。当您理解了这句话后,就往下看吧。
我在 GreetingImpl 类中故意增加了两个方法,都以“good”开头。下面要做的就是拦截这两个新增的方法,而对 sayHello() 方法不作拦截。
- @Component
- public class GreetingImpl implements Greeting {
- @Override
- public void sayHello(String name) {
- System.out.println("Hello! " + name);
- }
- public void goodMorning(String name) {
- System.out.println("Good Morning! " + name);
- }
- public void goodNight(String name) {
- System.out.println("Good Night! " + name);
- }
- }
在 Spring AOP 中,老罗已经给我们提供了许多切面类了,这些切面类我个人感觉最好用的就是基于正则表达式的切面类。
- <?xml version="1.0" encoding="UTF-8"?>
- <beans ...">
- <context:component-scan base-package="aop.demo"/>
- <!-- 配置一个切面 -->
- <bean id="greetingAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
- <property name="advice" ref="greetingAroundAdvice"/> <!-- 增强 -->
- <property name="pattern" value="aop.demo.GreetingImpl.good.*"/> <!-- 切点(正则表达式) -->
- </bean>
- <!-- 配置一个代理 -->
- <bean id="greetingProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
- <property name="target" ref="greetingImpl"/> <!-- 目标类 -->
- <property name="interceptorNames" value="greetingAdvisor"/> <!-- 切面 -->
- <property name="proxyTargetClass" value="true"/> <!-- 代理目标类 -->
- </bean>
- </beans>
注意以上代理对象的配置中的 interceptorNames,它不再是一个增强,而是一个切面,因为已经将增强封装到该切面中了。此外,切面还定义了一个切点(正则表达式),其目的是为了只将满足切点匹配条件的方法进行拦截。
需要强调的是,这里的切点表达式是基于正则表达式的。示例中的“aop.demo.GreetingImpl.good.*”表达式后面的“.*”表示匹配所有字符
除了 RegexpMethodPointcutAdvisor 以外,在 Spring AOP 中还提供了几个切面类,比如:
- DefaultPointcutAdvisor:默认切面(可扩展它来自定义切面)
- NameMatchMethodPointcutAdvisor:根据方法名称进行匹配的切面
- StaticMethodMatcherPointcutAdvisor:用于匹配静态方法的切面
总的来说,让用户去配置一个或少数几个代理,似乎还可以接受,但随着项目的扩大,代理配置就会越来越多,配置的重复劳动就多了,麻烦不说,还很容易出错。能否让 Spring 框架为我们自动生成代理呢?
10. Spring AOP:自动代理(扫描 Bean 名称)
为了匹配目标类中的指定方法,我们仍然需要在 Spring 中配置切面与切点:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans ...>
- ...
- <bean id="greetingAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
- <property name="pattern" value="aop.demo.GreetingImpl.good.*"/>
- <property name="advice" ref="greetingAroundAdvice"/>
- </bean>
- <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
- <property name="optimize" value="true"/>
- </bean>
- </beans>
这里无需再配置代理了,因为代理将会由 DefaultAdvisorAutoProxyCreator 自动生成。也就是说,这个类可以扫描所有的切面类,并为其自动生成代理。
看来不管怎样简化,老罗始终解决不了切面的配置,这件繁重的手工劳动。在 Spring 配置文件中,仍然会存在大量的切面配置。然而在有很多情况下 Spring AOP 所提供的切面类真的不太够用了,比如:想拦截指定注解的方法,我们就必须扩展 DefaultPointcutAdvisor 类,自定义一个切面类,然后在 Spring 配置文件中进行切面配置。不做不知道,做了您就知道相当麻烦了。
老罗的解决方案似乎已经掉进了切面类的深渊,这还真是所谓的“面向切面编程”了,最重要的是切面,最麻烦的也是切面。
必须要把切面配置给简化掉,Spring 才能有所突破!
神一样的老罗总算认识到了这一点,接受了网友们的建议,集成了 AspectJ,同时也保留了以上提到的切面与代理配置方式(为了兼容老的项目,更为了维护自己的面子)。将 Spring 与 AspectJ 集成与直接使用 AspectJ 是不同的,我们不需要定义 AspectJ 类(它是扩展了 Java 语法的一种新的语言,还需要特定的编译器),只需要使用 AspectJ 切点表达式即可(它是比正则表达式更加友好的表现形式)。
12. Spring + AspectJ(基于注解:通过 AspectJ execution 表达式拦截方法)
下面以一个最简单的例子,实现之前提到的环绕增强。先定义一个 Aspect 切面类:
- @Aspect
- @Component
- public class GreetingAspect {
- @Around("execution(* aop.demo.GreetingImpl.*(..))")
- public Object around(ProceedingJoinPoint pjp) throws Throwable {
- before();
- Object result = pjp.proceed();
- after();
- return result;
- }
- private void before() {
- System.out.println("Before");
- }
- private void after() {
- System.out.println("After");
- }
- }
注意:类上面标注的 @Aspect 注解,这表明该类是一个 Aspect(其实就是 Advisor)。该类无需实现任何的接口,只需定义一个方法(方法叫什么名字都无所谓),只需在方法上标注 @Around 注解,在注解中使用了 AspectJ 切点表达式。方法的参数中包括一个 ProceedingJoinPoint 对象,它在 AOP 中称为 Joinpoint(连接点),可以通过该对象获取方法的任何信息,例如:方法名、参数等。
下面重点来分析一下这个切点表达式:
execution(* aop.demo.GreetingImpl.*(..))
- execution():表示拦截方法,括号中可定义需要匹配的规则。
- 第一个“*”:表示方法的返回值是任意的。
- 第二个“*”:表示匹配该类中所有的方法。
- (..):表示方法的参数是任意的。
是不是比正则表达式的可读性更强呢?如果想匹配指定的方法,只需将第二个“*”改为指定的方法名称即可。
如何配置呢?看看是有多简单吧:
- <context:component-scan base-package="aop.demo"/>
- <aop:aspectj-autoproxy proxy-target-class="true"/>
两行配置就行了,不需要配置大量的代理,更不需要配置大量的切面,真是太棒了!需要注意的是 proxy-target-class=”true” 属性,它的默认值是 false,默认只能代理接口(使用 JDK 动态代理),当为 true 时,才能代理目标类(使用 CGLib 动态代理)。
Spring 与 AspectJ 结合的威力远远不止这些,我们来点时尚的吧,拦截指定注解的方法怎么样?
13. Spring + AspectJ(基于注解:通过 AspectJ @annotation 表达式拦截方法)
为了拦截指定的注解的方法,我们首先需要来自定义一个注解:
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Tag {
- }
以上定义了一个 @Tag 注解,此注解可标注在方法上,在运行时生效。
只需将前面的 Aspect 类的切点表达式稍作改动:
- @Aspect
- @Component
- public class GreetingAspect {
- @Around("@annotation(aop.demo.Tag)")
- public Object around(ProceedingJoinPoint pjp) throws Throwable {
- ...
- }
- ...
- }
这次使用了 @annotation() 表达式,只需在括号内定义需要拦截的注解名称即可。
直接将 @Tag 注解定义在您想要拦截的方法上,就这么简单:
- @Component
- public class GreetingImpl implements Greeting {
- @Tag
- @Override
- public void sayHello(String name) {
- System.out.println("Hello! " + name);
- }
- }
以上示例中只有一个方法,如果有多个方法,我们只想拦截其中某些时,这种解决方案会更加有价值。
除了 @Around 注解外,其实还有几个相关的注解,稍微归纳一下吧:
- @Before:前置增强
- @After:后置增强
- @Around:环绕增强
- @AfterThrowing:抛出增强
- @DeclareParents:引入增强
此外还有一个 @AfterReturning(返回后增强),也可理解为 Finally 增强,相当于 finally 语句,它是在方法结束后执行的,也就说说,它比 @After 还要晚一些。
最后一个 @DeclareParents 竟然就是引入增强!为什么不叫做 @Introduction 呢?我也不知道为什么,但它干的活就是引入增强。
14. Spring + AspectJ(引入增强)
为了实现基于 AspectJ 的引入增强,我们同样需要定义一个 Aspect 类:
- @Aspect
- @Component
- public class GreetingAspect {
- @DeclareParents(value = "aop.demo.GreetingImpl", defaultImpl = ApologyImpl.class)
- private Apology apology;
- }
只需要在 Aspect 类中定义一个需要引入增强的接口,它也就是运行时需要动态实现的接口。在这个接口上标注了 @DeclareParents 注解,该注解有两个属性:
- value:目标类
- defaultImpl:引入接口的默认实现类
我们只需要对引入的接口提供一个默认实现类即可完成引入增强:
- public class ApologyImpl implements Apology {
- @Override
- public void saySorry(String name) {
- System.out.println("Sorry! " + name);
- }
- }
以上这个实现会在运行时自动增强到 GreetingImpl 类中,也就是说,无需修改 GreetingImpl 类的代码,让它去实现 Apology 接口,我们单独为该接口提供一个实现类(ApologyImpl),来做 GreetingImpl 想做的事情。
还是用一个客户端来尝试一下吧:
- public class Client {
- public static void main(String[] args) {
- ApplicationContext context = new ClassPathXmlApplicationContext("aop/demo/spring.xml");
- Greeting greeting = (Greeting) context.getBean("greetingImpl");
- greeting.sayHello("Jack");
- Apology apology = (Apology) greeting; // 强制转型为 Apology 接口
- apology.saySorry("Jack");
- }
- }
从 Spring ApplicationContext 中获取 greetingImpl 对象(其实是个代理对象),可转型为自己静态实现的接口 Greeting,也可转型为自己动态实现的接口 Apology,切换起来非常方便。
使用 AspectJ 的引入增强比原来的 Spring AOP 的引入增强更加方便了,而且还可面向接口编程(以前只能面向实现类),这也算一个非常巨大的突破。
这一切真的已经非常强大也非常灵活了!但仍然还是有用户不能尝试这些特性,因为他们还在使用 JDK 1.4(根本就没有注解这个东西),怎么办呢?没想到 Spring AOP 为那些遗留系统也考虑到了。
15. Spring + AspectJ(基于配置)
除了使用 @Aspect 注解来定义切面类以外,Spring AOP 也提供了基于配置的方式来定义切面类
- <?xml version="1.0" encoding="UTF-8"?>
- <beans ...">
- <bean id="greetingImpl" class="aop.demo.GreetingImpl"/>
- <bean id="greetingAspect" class="aop.demo.GreetingAspect"/>
- <aop:config>
- <aop:aspect ref="greetingAspect">
- <aop:around method="around" pointcut="execution(* aop.demo.GreetingImpl.*(..))"/>
- </aop:aspect>
- </aop:config>
- </beans>
使用 <aop:config> 元素来进行 AOP 配置,在其子元素中配置切面,包括增强类型、目标方法、切点等信息。
无论您是不能使用注解,还是不愿意使用注解,Spring AOP 都能为您提供全方位的服务。
好了,我所知道的比较实用的 AOP 技术都在这里了,当然还有一些更为高级的特性,由于个人精力有限,这里就不再深入了。
再来一张表格,总结一下各类增强类型所对应的解决方案:
我的AOP那点事儿--2的更多相关文章
- 我的AOP那点事儿--1
题记:一段时间以来一直想整理下关于AOP的知识,之前一直停留在会怎么使用AOP,关于AOP的深入点儿的知识就不知所以然了,正好项目上刚好用到需要用AOP实现的功能,所以找个时间统一整理下就很有必要了. ...
- 学习AOP之认识一下Spring AOP
心碎之事 要说知道AOP这个词倒是很久很久以前了,但是直到今天我也不敢说非常的理解它,其中的各种概念即抽象又太拗口. 在几次面试中都被问及AOP,但是真的没有答上来,或者都在面上,这给面试官的感觉就是 ...
- Spring之AOP由浅入深
1.AOP的作用 在OOP中,正是这种分散在各处且与对象核心功能无关的代码(横切代码)的存在,使得模块复用难度增加.AOP则将封装好的对象剖开,找出其中对多个对象产生影响的公共行为,并将其封装为一个可 ...
- 从零开始写JavaWeb框架(第四章节的AOP)
使用"链式代理"实现 AOP 本文是<轻量级 Java Web 框架架构设计>的系列博文. 大家是否还记得<Proxy 那点事儿>中提到的 CGLib ...
- Spring AOP 知识整理
通过一个多月的 Spring AOP 的学习,掌握了 Spring AOP 的基本概念.AOP 是面向切面的编程(Aspect-Oriented Programming),是基于 OOP(面向对象的编 ...
- Spring的AOP2
本文是<AOP 那点事儿>的续集. 在上篇中,我们从写死代码,到使用代理:从编程式 Spring AOP 到声明式 Spring AOP.一切都朝着简单实用主义的方向在发展.沿着 Spri ...
- Java代理学习笔记
代理模式 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在关联关 ...
- AOP 与 注解的那些事儿~
持续原创输出,点击上方蓝字关注我 目录 前言 什么是AOP? AOP的相关概念(面试常客) Spring Boot 如何整合AOP自定义一个注解? 使用拦截器如何自定义注解? 内部调用导致AOP注解失 ...
- 什么是aop?
这个命题其实是讲了的,但是之前没有做,发现一些面试会问到,结合自己之前的学习经历.简单把这个问题描述一下. aop是跟oop相对应的一个概念.分别是aspect oriented programmin ...
随机推荐
- nginx自动生成缩略图
网站上常常一张图片,多个地方需要使用不同的尺寸,一般的方案是上传的时候,根据不同的尺寸用程序生成多张图片. 这么做有两个缺点: 1.如果需要新的尺寸图片的话,只能遍历下数据库,重新生成一次图片. 2. ...
- Ubuntu下的MongoDB GUI 可视化管理工具
目录 1 Robo 3T 2 NoSQLBooster for MongoDB(收费) 3 JetBrains Plugin Repository :: Mongo Plugin Ubuntu下的Mo ...
- 20145314郑凯杰《网络对抗技术》实验5 MSF基础应用
20145314郑凯杰<网络对抗技术>实验5 MSF基础应用 1.0 MS08_067安全漏洞 1.1 实验目标 了解掌握metasploit平台的一些基本操作,能学会利用已知信息完成简单 ...
- 20145311 《Java程序设计》第十周学习总结
20145311 <Java程序设计>第十周学习总结 教材学习内容总结 网络编程 ·网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据·程序员所作的事情就是把数据发送到指定的位置 ...
- 20145325张梓靖 《Java程序设计》第4周学习总结
20145325张梓靖 <Java程序设计>第4周学习总结 教材学习内容总结 何谓继承 继承共同行为 继承基本上就是避免多个类间重复定义共同行为:可把相同的程序代码提升为父类:用关键字 e ...
- Android Studio安装与使用
2013年谷歌推出android studio后,单独支持android开发,这是基于Java语言集成开发环境IntelliJ搭建的IDE.特别在android studio1.0稳定版出来后,谷歌将 ...
- jz2440-uboot-201204版本移植【学习笔记】【原创】
平台:jz2440 作者:庄泽彬(欢迎转载,请注明作者) 说明:韦东山二期视频学习笔记 交叉编译工具:arm-linux-gcc (GCC)4.3.2 PC环境:ubuntu18.04 一.uboot ...
- linux下如何关闭某个tmux窗口
答:分成两个步骤,如下: 1.列出当前的tmux窗口 jello@jello:~$ tmux ls 1: 1 windows (created Tue Jan 17 09:28:05 2019) [2 ...
- HDU 6425 Rikka with Badminton(组合问题签到)题解
题意:问你有多少种选法使得不能满足大于等于2个拍子且大于等于1个球. 思路:数学组合问题,分类讨论一下,刚开始的时候分的很乱,写的乱七八糟的...还有注意MOD,基本上有大数相乘的地方都要先MOD一下 ...
- [优化]深度学习中的 Normalization 模型
来源:https://www.chainnews.com/articles/504060702149.htm 机器之心专栏 作者:张俊林 Batch Normalization (简称 BN)自从提出 ...