AOP是Aspect-oriented programming的缩写,意思是面向切面的编程,是对面向对象编程(OOP)的补充。为了搞懂AOP的概念,我们首先需要了解AOP是为了解决什么问题。
系统中存在交叉业务,一个业务就是要切入到系统的一个方面。比如上安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务。
AOP的目标就是使交叉业务模块化,模块化得交叉业务就是切面(Aspect)。
上面很好地解释了什么是面向切面编程。AOP框架可以将切面代码移动到原始代码周围,这样交叉业务代码只需要写一份,其他的地方全部自动应用上,而不需要每个地方都写重复的代码。
AOP的基本概念网上已经有很多总结了,我就直接挑了篇写的比较清楚的粘贴过来了。
原文链接
AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理。本文就主要讲解AOP的基本术语,然后用一个例子让大家彻底搞懂这些名词,最后介绍一下AOP的两种代理方式:
以AspectJ为代表的静态代理。
以Spring AOP为代表的动态代理。
切面是一个横切关注点的模块化,一个切面能够包含同一个类型的不同增强方法,比如说事务处理和日志处理可以理解为两个切面。切面由切入点和通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义。 Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。
@Component
@Aspect
public class LogAspect {
}
可以简单地认为, 使用 @Aspect 注解的类就是切面
目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。
程序执行过程中明确的点,如方法的调用或特定的异常被抛出。连接点由两个信息确定:
@Before("pointcut()")
public void log(JoinPoint joinPoint) { //这个JoinPoint参数就是连接点
}
切入点是对连接点进行拦截的条件定义。切入点表达式如何和连接点匹配是AOP的核心,Spring缺省使用AspectJ切入点语法。
一般认为,所有的方法都可以认为是连接点,但是我们并不希望在所有的方法上都添加通知,而切入点的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配连接点,给满足规则的连接点添加通知。
@Pointcut("execution(* com.remcarpediem.test.aop.service..*(..))")
public void pointcut() {
}
上边切入点的匹配规则是com.remcarpediem.test.aop.service包下的所有类的所有函数。
其实@Pointcut是可以不用单独写的,可以合并在连接点中,简写为
@Before("execution(* com.remcarpediem.test.aop.service..*(..))")
public void log(JoinPoint joinPoint) { //这个JoinPoint参数就是连接点
}
通知是指拦截到连接点之后要执行的代码,包括了“around”、“before”和“after”等不同类型的通知。Spring AOP框架以拦截器来实现通知模型,并维护一个以连接点为中心的拦截器链。
// @Before说明这是一个前置通知,log函数中是要前置执行的代码,JoinPoint是连接点,
@Before("pointcut()")
public void log(JoinPoint joinPoint) {
}
织入是将切面和业务逻辑对象连接起来, 并创建通知代理的过程。织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理。
Advisor是切面的另外一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。Advisor由切入点和Advice组成。
Advisor这个概念来自于Spring对AOP的支撑,在AspectJ中是没有等价的概念的。Advisor就像是一个小的自包含的切面,这个切面只有一个通知。切面自身通过一个Bean表示,并且必须实现一个默认接口。
// AbstractPointcutAdvisor是默认接口
public class LogAdvisor extends AbstractPointcutAdvisor {
private Advice advice; // Advice
private Pointcut pointcut; // 切入点
@PostConstruct
public void init() {
// AnnotationMatchingPointcut是依据修饰类和方法的注解进行拦截的切入点。
this.pointcut = new AnnotationMatchingPointcut((Class) null, Log.class);
// 通知
this.advice = new LogMethodInterceptor();
}
}
看完了上面的理论部分知识, 我相信还是会有不少朋友感觉AOP 的概念还是很模糊, 对 AOP 的术语理解的还不是很透彻。现在我们就找一个具体的案例来说明一下。
简单来讲,整个 aspect 可以描述为: 满足 pointcut 规则的 joinpoint 会被添加相应的 advice 操作。我们来看下边这个例子。
@Component
@Aspect // 切面
public class LogAspect {
private final static Logger LOGGER = LoggerFactory.getLogger(LogAspect.class.getName());
// 切入点,表达式是指com.remcarpediem.test.aop.service
// 包下的所有类的所有方法
@Pointcut("execution(* com.remcarpediem.test.aop.service..*(..))")
public void aspect() {}
// 通知,在符合aspect切入点的方法前插入如下代码,并且将连接点作为参数传递
@Before("aspect()")
public void log(JoinPoint joinPoint) { //连接点作为参数传入
if (LOGGER.isInfoEnabled()) {
// 获得类名,方法名,参数和参数名称。
Signature signature = joinPoint.getSignature();
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] argumentNames = methodSignature.getParameterNames();
StringBuilder sb = new StringBuilder(className + "." + methodName + "(");
for (int i = 0; i< arguments.length; i++) {
Object argument = arguments[i];
sb.append(argumentNames[i] + "->");
sb.append(argument != null ? argument.toString() : "null ");
}
sb.append(")");
LOGGER.info(sb.toString());
}
}
}
上边这段代码是一个简单的日志相关的切面,依次定义了切入点和通知,而连接点作为log的参数传入进来,进行一定的操作,比如说获取连接点函数的名称,参数等
所谓静态代理就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强。ApsectJ是静态代理的实现之一,也是最为流行的。静态代理由于在编译时就生成了代理类,效率相比动态代理要高一些。AspectJ可以单独使用,也可以和Spring结合使用。
与静态代理不同,动态代理就是说AOP框架不会去修改编译时生成的字节码,而是在运行时在内存中生成一个AOP代理对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式:JDK动态代理和CGLIB动态代理。