AOP 编程
AOP 编程
目录
1. AOP和OOP
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程
在说到AOP前,很容易想到OOP(面向对象编程),这两个都是编程思想中十分重要的部分,不同点在于:
- OOP面向对象编程:纵向切割,针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分;
- AOP面向切面编程:横向切割,针对业务处理过程中的切面进行提取,它所面对的是处理过程的某个步骤或阶段,以获得逻辑过程的中各部分之间低耦合的隔离效果。
- AOP 采取横向抽取机制,补充了传统纵向继承体系(OOP)无法解决的重复性,代码优化(性能监视、事务管理、安全检查、缓存),将业务逻辑和系统处理的代码(关闭连接、事务管理、操作日志记录)解耦。
2. AOP中的一些概念
术语
切面(Aspect)
: Aspect 声明类似于 Java 中的类声明,事务管理是AOP一个最典型的应用。在AOP中,切面一般使用@Aspect
注解来使用,在XML 中,可以使用<aop:aspect>
来定义一个切面。连接点(Join Point)
: 一个在程序执行期间的某一个操作,就像是执行一个方法或者处理一个异常。在Spring AOP中,一个连接点就代表了一个方法的执行。通知(Advice):
在切面中(类)的某个连接点(方法出)采取的动作,会有四种不同的通知方式: around(环绕通知),before(前置通知),after(后置通知), exception(异常通知),return(返回通知)。许多AOP框架(包括Spring)将建议把通知作为为拦截器,并在连接点周围维护一系列拦截器。切入点(Pointcut):
表示一组连接点,通知与切入点表达式有关,并在切入点匹配的任何连接点处运行(例如执行具有特定名称的方法)。由切入点表达式匹配的连接点的概念是AOP的核心,Spring默认使用AspectJ切入点表达式语言。介绍(Introduction):
introduction可以为原有的对象增加新的属性和方法。例如,你可以使用introduction使bean实现IsModified接口,以简化缓存。目标对象(Target Object):
由一个或者多个切面代理的对象。也被称为"切面对象"。由于Spring AOP是使用运行时代理实现的,因此该对象始终是代理对象。AOP代理(AOP proxy):
由AOP框架创建的对象,在Spring框架中,AOP代理对象有两种:JDK动态代理和CGLIB代理织入(Weaving):
是指把增强应用到目标对象来创建新的代理对象的过程,它(例如 AspectJ 编译器)可以在编译时期,加载时期或者运行时期完成。与其他纯Java AOP框架一样,Spring AOP在运行时进行织入。
通知类型
- 前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
- 后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
- 异常通知(After throwing advice):在方法抛出异常退出时执行的通知。
- 最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
- 环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。
环绕通知是最常用的通知类型。和AspectJ一样,Spring提供所有类型的通知,我们推荐你使用尽可能简单的通知类型来实现需要的功能。例如,如果你只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。比如,你不需要在JoinPoint上调用用于环绕通知的proceed()方法,就不会有调用的问题。
3. AOP实现方式
Spring AOP 是通过动态代理技术实现的,而动态代理是基于反射设计的。Spring AOP 采用了两种混合的实现方式:
- JDK 动态代理(interface):只能为接口创建代理对象,创建出来的代理都是java.lang.reflect.Proxy的子类;
- CGLib 动态代理:cglib弥补了jdk动态代理的不足,jdk动态代理只能为接口创建代理,而cglib不管是接口还是类,都可以使用cglib来创建代理。cglib创建代理的过程,相当于创建了一个新的类(继承),可以通过cglib来配置这个新的类需要实现的接口,以及需要继承的父类。
详细介绍见 Spring系列之jdk动态代理和cglib代理
4. AOP使用
这里只介绍Spring AOP的使用方法,还有AspectJ也能实现AOP。
1.添加依赖: 在项目的pom.xml
文件中添加 Spring AOP 依赖,以确保 AOP 模块可用。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.创建切面类: 创建一个 Java 类,并使用@Aspect
注解标记它,这个类将充当切面。
在切面类中定义通知方法,使用注解如@Before
、@After
、@Around
等来指定在何时执行横切逻辑。
下面的切点会在执行controller的周围进行我们需要的操作。
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Aspect
@Component
public class MethodExecutionTimeAspect {
private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class);
/** 定义一个切点 */
@Pointcut("execution(* com.example.myapp.controller..*Controller.*(..))")
@Before("controllerPointcut()")
public void beforeServiceMethodExecution() {
startTime = System.currentTimeMillis();
// 打印请求信息
LOG.info("------------- Begin -------------");
LOG.info("Request method: {} {}", request.getRequestURL().toString(), request.getMethod());
LOG.info("Class name: {}.{}", signature.getDeclaringTypeName(), name);
LOG.info("Remote address: {}", request.getRemoteAddr());
}
@Around("controllerPointcut()")
public void doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// 执行程序
Object result = proceedingJoinPoint.proceed();
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
LOG.info("Response: {}", JSONObject.toJSONString(result, excludefilter));
LOG.info("------------- End time:{} ms -------------", System.currentTimeMillis() - startTime);
}
}
参考文献