Spring AOP是Spring框架体系结构中非常重要的功能模块之一,该模块提供了面向切面编程实现。面向切面编程在事务处理、日志记录、安全控制等操作中被广泛使用。
1.AOP的概念 AOP(Aspect-Oriented Programming),即面向切面编程。它与OOP(Object-Oriented Programming,面向对象编程) 相辅相成,提供了与 OOP 不同的抽象软件结构的视角。在 OOP 中,以类作为程序的基本单元,而AOP中的基本单元是Aspect(切面)。 AOP采取横向抽取机制,即将分散在各个方法中的重复代码提取出来,然后在程序编译或运行阶段,再将这些抽取出来的代码应用到需要执行的地方。这种横向抽取机制,采用传统的OOP是无法办到的,因为OOP实现的是父子关系的纵向重用。但是AOP不是OOP的替代品,而是OOP的补充,它们相辅相成。
AOP的术语 在Spring AOP框架中,涉及以下常用术语。 (1)切面 切面(Aspect)是指封装横切到系统功能(如事务处理)的类。 (2)连接点 连接点(Joinpoint)是指程序运行中的一些时间点,如方法的调用或异常的抛出。 (3)切入点 切入点(Pointcut)是指那些需要处理的连接点。在Spring AOP 中,所有的方法执行都是连接点,而切入点是一个描述信息,它修饰的是连接点,通过切入点确定哪些连接点需要被处理。切面、连接点和切入点的关系如图所示。
(4)通知(增强处理) 由切面添加到特定的连接点(满足切入点规则)的一段代码,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面开启后,切面的方法。因此,通知是切面的具体实现。 (5)引入 引入(Introduction)允许在现有的实现类中添加自定义的方法和属性。 (6)目标对象 目标对象(Target Object)是指所有被通知的对象。如果AOP 框架使用运行时代理的方式(动态的AOP)来实现切面,那么通知对象总是一个代理对象。 (7)代理 代理(Proxy)是通知应用到目标对象之后,被动态创建的对象。 (8)组入 组入(Weaving)是将切面代码插入到目标对象上,从而生成代理对象的过程。根据不同的实现技术,AOP织入有三种方式:编译器织入,需要有特殊的Java编译器;类装载期织入,需要有特殊的类装载器;动态代理织入,在运行期为目标类添加通知生成子类的方式。Spring AOP框架默认采用动态代理织入,而AspectJ(基于Java语言的AOP框架)采用编译器织入和类装载器织入。
基于注解开发AspectJ 先了解一下Spring的通知类型。根据Spring中通知在目标类方法的连接点位置,可以分为6种如下类型: 1.环绕通知 环绕通知是在目标方法执行前和执行后实施增强,可以应用于日志记录、事务处理等功能。 2.前置通知 前置通知是在目标方法执行前实施增强,可应用于权限管理等功能。 3.后置返回通知 后置返回通知是在目标方法成功执行后实施增强,可应用于关闭流、删除临时文件等功能。 4.后置(最终)通知 后置通知是在目标方法执行后实施增强,与后置返回通知不同的是,不管是否发生异常都要执行该通知,可应用于释放资源。 5.异常通知 异常通知是在方法抛出异常后实施增强,可以应用于处理异常、记录日志等功能。 6.引入通知 引入通知是在目标类中添加一些新的方法和属性,可以应用于修改目标类(增强类)。
基于注解开发AspectJ的过程 1.使用Eclipse创建Web应用并导入JAR包 2.创建接口及实现类 3.创建切面类 4.创建配置类 5.创建测试类 6.运行测试类
package aspectj.dao; public interface TestDao { public void save(); public void modify(); public void delete(); }
package aspectj.dao; import org.springframework.stereotype.Repository; @Repository("testDao") public class TestDaoImpl implements TestDao { @Override public void save() { System.out.println("保存"); } @Override public void modify() { System.out.println("修改"); } @Override public void delete() { System.out.println("删除"); } }
package aspectj.annotation; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * 切面类,在此类中编写各种类型通知 */ @Aspect // @Aspect声明一个切面 @Component // @Component让此切面成为Spring容器管理的Bean public class MyAspect { /** * 定义切入点,通知增强哪些方法。 "execution(* aspectj.dao.*.*(..))" 是定义切入点表达式, * 该切入点表达式的意思是匹配aspectj.dao包中任意类的任意方法的执行。 * 其中execution()是表达式的主体,第一个*表示的是返回类型,使用*代表所有类型; * aspectj.dao表示的是需要匹配的包名,后面第二个*表示的是类名,使用*代表匹配包中所有的类; 第三个*表示的是方法名,使用*表示所有方法; * 后面(..)表示方法的参数,其中“..”表示任意参数。 另外,注意第一个*与包名之间有一个空格。 */ @Pointcut("execution(* aspectj.dao.*.*(..))") private void myPointCut() { } /** * 前置通知,使用Joinpoint接口作为参数获得目标对象信息 */ @Before("myPointCut()") // myPointCut()是切入点的定义方法 public void before(JoinPoint jp) { System.out.print("前置通知:模拟权限控制"); System.out.println(",目标类对象:" + jp.getTarget() + ",被增强处理的方法:" + jp.getSignature().getName()); } /** * 后置返回通知 */ @AfterReturning("myPointCut()") public void afterReturning(JoinPoint jp) { System.out.print("后置返回通知:" + "模拟删除临时文件"); System.out.println(",被增强处理的方法:" + jp.getSignature().getName()); } /** * 环绕通知 ProceedingJoinPoint是JoinPoint子接口,代表可以执行的目标方法 返回值类型必须是Object * 必须一个参数是ProceedingJoinPoint类型 必须throws Throwable */ @Around("myPointCut()") public Object around(ProceedingJoinPoint pjp) throws Throwable { // 开始 System.out.println("环绕开始:执行目标方法前,模拟开启事务"); // 执行当前目标方法 Object obj = pjp.proceed(); // 结束 System.out.println("环绕结束:执行目标方法后,模拟关闭事务"); return obj; } /** * 异常通知 */ @AfterThrowing(value = "myPointCut()", throwing = "e") public void except(Throwable e) { System.out.println("异常通知:" + "程序执行异常" + e.getMessage()); } /** * 后置(最终)通知 */ @After("myPointCut()") public void after() { System.out.println("最终通知:模拟释放资源"); } }
package aspectj.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration // 声明一个配置类 @ComponentScan("aspectj") // 自动扫描aspectj包下使用的注解 @EnableAspectJAutoProxy // 开启Spring对AspectJ的支持 public class AspectjAOPConfig { }
package aspectj.config; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import aspectj.dao.TestDao; public class AOPTest { public static void main(String[] args) { // 初始化Spring容器ApplicationContext AnnotationConfigApplicationContext appCon = new AnnotationConfigApplicationContext(AspectjAOPConfig.class); // 从容器中,获取增强后的目标对象 TestDao testDaoAdvice = appCon.getBean(TestDao.class); // 执行方法 testDaoAdvice.save(); System.out.println("================"); testDaoAdvice.modify(); System.out.println("================"); testDaoAdvice.delete(); appCon.close(); } }