Aspect-Oriented Programming, 面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
遵循开放-封闭原则,面向修改关闭,面向扩展开放。需要修改业务逻辑时,可以不直接修改代码,尤其是类似的修改点在多处出现时,比如日志打印。AOP的用处就更大了。
面向切面编程(AOP)通过提供另外一种思考程序结构的途经来弥补面向对象编程(OOP)的不足。在OOP中模块化的关键单元是类(classes),而在AOP中模块化的单元则是切面。切面能对关注点进行模块化,例如横切多个类型和对象的事务管理。
动态代理类的字节码是在程序运行时由Java反射机制动态生成。
应用实例
如果要在doSomething()方法执行前、后加入其他逻辑,又不想修改原来的代码,就可以采用代理的方式。
代理分为静态代理和动态代理。
静态代理
对于静态代理,需要为每一个接口都编写对应的代理,编写麻烦,而且会产生一系列的Proxy类、不方便维护。
JDK动态代理
JDK提供的动态代理只能代理接口,不能代理没有接口的类。
CGLib动态代理
CGLib能代理没有接口的类,弥补了JDK动态代理的不足。
1、概述:
Cglib是一个优秀的动态代理框架,它的底层使用ASM(JAVA字节码处理框架)在内存中动态的生成被代理类的子类。使用CGLIB即使被代理类没有实现任何接口也可以实现动态代理功能。但是不能对final修饰的类进行代理。
2、原理:
通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用。
<JDK动态代理与CGLib动态代理均是实现Spring AOP的基础>
3、使用:
使用Cglib前需要导入以下两个jar文件:
asm.jar – Cglib的底层实现。【cglib包的底层是使用字节码处理框架ASM来转换字节码并生成新的类,所以cglib包要依赖于asm包】
cglib.jar - Cglib的核心jar包。Spring AOP框架
Spring AOP使用纯Java实现。它不需要专门的编译过程。Spring AOP不需要控制类装载器层次,因此它适用于J2EE web容器或应用服务器。
Spring目前仅支持使用方法调用作为连接点(join point)(在Spring bean上通知方法的执行)。虽然可以在不影响到Spring AOP核心API的情况下加入对成员变量拦截器支持,但Spring并没有实现成员变量拦截器。如果你需要把对成员变量的访问和更新也作为通知的连接点,可以考虑其它的语言,如AspectJ。
Spring实现AOP的方法跟其他的框架不同。Spring并不是要提供最完整的AOP实现(尽管Spring AOP有这个能力),相反的,它其实侧重于提供一种AOP实现和Spring IoC容器之间的整合,用于帮助解决在企业级开发中的常见问题。
因此,Spring的AOP功能通常都和Spring IoC容器一起使用。切面使用普通的bean定义语法来配置(尽管Spring提供了强大的"自动代理(autoproxying)"功能):与其他AOP实现相比这是一个显著的区别。有些事使用Spring AOP是无法轻松或者高效完成的,比如说通知一个细粒度的对象(例如典型的域对象):这种时候,使用AspectJ是最好的选择。
Spring 2.0可以无缝的整合Spring AOP,IoC和AspectJ,使得所有的AOP应用完全融入基于Spring的应用体系。
Spring缺省使用J2SE 动态代理(dynamic proxies)来作为AOP的代理。 这样任何接口(或者接口集)都可以被代理。
Spring也可以使用CGLIB代理. 对于需要代理类而不是代理接口的时候CGLIB代理是很有必要的。如果一个业务对象并没有实现一个接口,默认就会使用CGLIB。作为面向接口编程的最佳实践,业务对象通常都会实现一个或多个接口。但也有可能会,在这种情况(希望不常有)下,你可能需要通知一个没有在接口中声明的方法,或者需要传入一个代理对象给方法作为具体类型
AspectJ框架
AspectJ 意思就是Java的AspectJava的AOP。它其实不是一个新的语言它就是一个代码编译器
需要注意的就是AspectJ是在编译期重构的代码所以获得的对象是声明类型如果要获得运行时类型需要使用一些关键字this、target。
Spring AOP和AspectJ的比较
做能起作用的最简单的事。Spring AOP比完全使用AspectJ更加简单, 因为它不需要引入AspectJ的编译器/织入器到你开发和构建过程中。 如果你仅仅需要在Spring bean上通知执行操作,那么Spring AOP是合适的选择。 如果你需要通知domain对象或其它没有在Spring容器中管理的任意对象,那么你需要使用AspectJ。 如果你想通知除了简单的方法执行之外的连接点(如:调用连接点、字段get或set的连接点等等), 也需要使用AspectJ。
当使用AspectJ时,你可以选择使用AspectJ语言(也称为“代码风格”)或@AspectJ注解风格。 很显然,如果你用的不是Java 5+那么结论是你只能使用代码风格。 如果切面在你的设计中扮演一个很大的角色,并且你能在Eclipse中使用, 那么首选AspectJ语言 :- 因为该语言专门被设计用来编写切面,所以会更清晰、更简单。如果你没有使用 Eclipse,或者在你的应用中只有很少的切面并没有作为一个主要的角色,你或许应该考虑使用@AspectJ风格 并在你的IDE中附加一个普通的Java编辑器,并且在你的构建脚本中增加切面织入(链接)的段落。
如果你选择使用Spring AOP,那么你可以选择@AspectJ或者XML风格。显然如果你不是运行 在Java 5上,XML风格是最佳选择。对于使用Java 5的项目,需要考虑多方面的折衷。
XML风格对现有的Spring用户来说更加习惯。它可以使用在任何Java级别中 (参考连接点表达式内部的命名连接点,虽然它也需要Java 5+) 并且通过纯粹的POJO来支持。当使用AOP作为工具来配置企业服务时XML会是一个很好的选择。 (一个好的例子是当你认为连接点表达式是你的配置中的一部分时,你可能想单独更改它) 对于XML风格,从你的配置中可以清晰的表明在系统中存在那些切面。
XML风格有两个缺点。第一是它不能完全将需求实现的地方封装到一个位置。 DRY原则中说系统中的每一项知识都必须具有单一、无歧义、权威的表示。 当使用XML风格时,如何实现一个需求的知识被分割到支撑类的声明中以及XML配置文件中。 当使用@AspectJ风格时就只有一个单独的模块 -切面- 信息被封装了起来。 第二是XML风格同@AspectJ风格所能表达的内容相比有更多的限制:仅仅支持"singleton"切面实例模型, 并且不能在XML中组合命名连接点的声明。
另一个需要考虑的因素是,你是希望在编译期间进行织入(weaving),还是编译后(post-compile)或是运行时(run-time)。Spring只支持运行时织入。如果你有多个团队分别开发多个使用Spring编写的模块(导致生成多个jar文件,例如每个模块一个jar文件),并且其中一个团队想要在整个项目中的所有Spring bean(例如,包括已经被其他团队打包了的jar文件)上应用日志通知(在这里日志只是用于加入横切关注点的举例),那么通过配置该团队自己的Spring配置文件就可以轻松做到这一点。之所以可以这样做,就是因为Spring使用的是运行时织入。
如果你使用AspectJ想要做到同样的事情,你也许就需要使用acj(AspectJ编译器)重新编译所有的代码并且进行重新打包。否则,你也可以选择使用AspectJ编译后(post-compile)或载入时(load-time)织入。
因为Spring基于代理模式(使用CGLIB),它有一个使用限制,即无法在使用final修饰的bean上应用横切关注点。因为代理需要对Java类进行继承,一旦使用了关键字final,这将是无法做到的。在这种情况下,你也许会考虑使用AspectJ,其支持编译期织入且不需要生成代理。
于此相似,在static和final方法上应用横切关注点也是无法做到的。因为Spring基于代理模式。如果你在这些方法上配置通知,将导致运行时异常,因为static和final方法是不能被覆盖的。在这种情况下,你也会考虑使用AspectJ,因为其支持编译期织入且不需要生成代理。
使用AspectJ的一个间接局限是,因为AspectJ通知可以应用于POJO之上,它有可能将通知应用于一个已配置的通知之上。对于一个你没有注意到这方面问题的大范围应用的通知,这有可能导致一个无限循环。
所以,如果你希望在Spring bean上采取比较简单的方式应用横切关注点时,并且这些bean没有被标以final修饰符,同时相似的方法也没有标以static或final修饰符时,就使用Spring AOP吧。相比之下,如果你需要在所提到的限制之上应用横切关注点,或者要在POJO上应用关注点,那么就使用AspectJ。你也可能选择同时使用两种方法,因为Spring支持这样。
参考资料