1. AOP的认识
面向切面编程:基于OOP基础之上新的编程思想,OOP面向的主要对象是类,而AOP面向的主要对象是切面,在处理日志、安全管理、事务管理等方面有非常重要的作用。AOP是Spring中重要的核心点,AOP提供了非常强大的功能,用来对IOC做补充。通俗点说的话就是在程序运行期间,在不修改原有代码的情况下 增强跟主要业务没有关系的公共功能代码到 之前写好的方法中的指定位置 这种编程的方式叫AOP。
AOP在Spring框架中被用于:
- 提供声明式的企业服务。最重要的此类服务是 声明式事务管理。
- 让用户实现自定义切面,用AOP补充他们对OOP的使用。
2. AOP核心概念和术语
2.1 切面(Aspect)
指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级Java应用中有关横切关注点的例子。 在Spring AOP中,切面可以使用通用类基于模式的方式(schemabased approach)或者在普通类中以@Aspect注解(@AspectJ 注解方式)来实现。
2.2 连接点(Join point)
在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在Spring AOP中,一个连接点总是代表一个方法的执行。
2.3 通知(Advice)
在切面的某个特定的连接点上执行的动作。通知有多种类型,包括“around”, “before” and “after”等等。通知的类型将在后面的章节进行讨论。 许多AOP框架,包括Spring在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。
前置通知(Before advice): 在连接点之前运行但无法阻止执行流程进入连接点的通知(除非它引发异常)。
后置返回通知(After returning advice):在连接点正常完成后执行的
通知(例如,当方法没有抛出任何异常并正常返回时)。
后置异常通知(After throwing advice): 在方法抛出异常退出时执行的通知。
后置通知(总会执行)(After (finally) advice): 当连接点退出的时候执行的通知(无论是正常返回还是异常退出)。
环绕通知(Around Advice):环绕连接点的通知,例如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它可以选择是否继续执行连接点或直接返回自定义的返回值又或抛出异常将执行结束。
2.4 切点(Pointcut)
匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用
AspectJ切点语义。
2.5 引入(Introduction)
声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被通知的对象上。例如,可以使用引入来使bean实现 IsModified接口, 以便简化缓存机制(在AspectJ社区,引入也被称为内部类型声明(inter))。
2.6 目标对象(Target object)
被一个或者多个切面所通知的对象。也被称作被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied)的对象。
2.7 AOP代理(AOP proxy)
AOP框架创建的对象,用来实现切面契约aspect contract)(包括通知方法执行等功能)。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。
2.8 织入(Weaving)
把切面连接到其它的应用程序类型或者对象上,并创建一个被被通知的对象的过程。这个过程可以在编译时(例如使用AspectJ编译器)、类加载时或运行时中完成。 Spring和其他纯Java AOP框架一样,是在运行时完成织入的。
3. 一图理解AOP核心概念
本节是个人对照业务需求对AOP的一些概念的解读。
我们通过一个场景和一张图通俗的理解一下AOP的切面、通知、连接点和切入点这几个概念;
场景: 我们有一个日志类,有before()、after()、AfterException()、afterReturn()这几个方法,我们需要记录我们的方法的日志,如果不适用AOP,那么我们就得在每个方法的前面、后面、异常调用这个日志类的方法。如果使用AOP呢? 我们如何理解呢?
理解:
切面指的是关注点模块化,那么我们抽出来的日志类是不是就可以理解为一个切面呢?
切面和我们的业务方法连接的点就是连接点;
通知是运行在连接点上的动作,那么抽出来的这个日志类的方法是不是就可以理解为通知呢?
切点是匹配连接点的断言,那就是说我们指定哪些需要增强的搭连接点就是切点
4. AOP的应用场景
- 日志管理
- 权限认证
- 安全检查
- 事务控制
5. 如何使用AOP
本节会通过AOP实现日志记录的需求进行演示如何使用AOP;本案例使用的是基于注解使用AOP;
第一步: 配置AOP的依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency>
第二步:开启扫面注解
<!--扫描包:扫描类中所有注解,不扫描注解不是生效--> <context:component-scan base-package="cn.learn.aop" > </context:component-scan> <!--因为我们使用的是注解方式的AOP,所以要开启注解AOP功能--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
第三步: 编写切面类
@Aspect // 标记为切面 @Component // 标记Bean组件, 才能切入到ioc当中的bean public class LogAspect { // 可以采用 声明切点的方式 让其他通知引用, 更重用性 @Pointcut("execution(* cn.myLearnAop.service.impl.*.*(..))") public void pointcut(){ } // 前置通知 @Before("pointcut()") public void before(JoinPoint joinPoint, Logger logger){ // 获取方法名 String methodName = joinPoint.getSignature().getName(); // 所有的参数 Object[] args = joinPoint.getArgs(); System.out.println(logger.name()+"方法运行,参数是:"+ Arrays.asList(args)+"注解的值是:"); } // 后置通知 @After("pointcut()") public void after(JoinPoint joinPoint){ // 获取方法名 String methodName = joinPoint.getSignature().getName(); // 所有的参数 Object[] args = joinPoint.getArgs(); System.out.println(methodName+"方法运行,参数是:"+ Arrays.asList(args)); } // 后置异常通知 @AfterThrowing(value="pointcut()", throwing="ex") public void afterThrowing(Exception ex){ StringWriter sw = new StringWriter(); ex.printStackTrace(new PrintWriter(sw, true)); System.out.println("后置异常通知"+sw.getBuffer().toString()); } // 后置返回通知 @AfterReturning(value="pointcut()", returning = "returnValue") public void afterReturning(Object returnValue){ System.out.println("返回值:"+returnValue); } // 环绕 @Around("pointcut()") public Object arround(ProceedingJoinPoint joinPoint){ // 获取方法名 String methodName = joinPoint.getSignature().getName(); // 所有的参数 Object[] args = joinPoint.getArgs(); Object returnValue=""; try { System.out.println("环绕:前置通知:"+methodName+"方法执行,参数:"+Arrays.asList(args)); returnValue=joinPoint.proceed(); System.out.println("环绕:后置通知:"+methodName+"方法执行,参数:"+Arrays.asList(args)); } catch (Throwable throwable) { System.out.println("环绕:异常通知:"+throwable); } finally { System.out.println("环绕:返回通知:"+returnValue); } return returnValue; } }