文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
1. 概述
- AOP 全称是 Aspect Oriented Programming,即面向切面的编程,是一种开发理念,在程序开发中主要用于解决一些系统层面上的问题。
- 通过 AOP 可以把一些非业务逻辑的代码,比如安全检查,监控等代码从业务方法中抽取出来,以非侵入的方式与原方法进行协同。
- 这样可以使原方法更专注于业务逻辑,代码结构会更加清晰,便于维护。
-
spring-aop 提供了符合 aop 联盟规范的面向切面的编程实现,可以定义如方法拦截器和切入点,从逻辑上讲,可以减弱代码的功能耦合,清晰地被分离开。
- 利用源码级地元数据功能,还可以将各种行为信息合并到代码中。
- spring-aspects 集成了 AspectJ,这是一个功能强大且成熟的面向方面编程(AOP)框架。
2. 原理
- 通过代理模式为目标对象生产代理对象,并将横切逻辑插入到目标方法执行的前后。
2.1 Joinpoint(连接点)
- 连接点是指程序执行过程中的一些点,比如方法调用,异常处理等。
- Spring AOP 中,仅支持方法级别的连接点。
- 如下例所示,每个方法调用都是一个连接点。
// TestService.java
public interface TestService {
void save(String name);
void update(String name);
void delete(String name);
String findOne(String name);
List<String> findAll();
}
// TestServiceImpl.java
public class TestServiceImpl implements TestService {
public void save(String name) {// 连接点
}
public void update(String name) {// 连接点
}
public void delete(String name) {// 连接点
}
public String findOne(String name) {// 连接点
System.out.println("TestServiceImpl`s findOne");
return "";
}
public List<String> findAll() {// 连接点
System.out.println("TestServiceImpl`s findAll");
return new ArrayList<String>();
}
}
-
Joinpoint
接口中,proceed()
方法用于执行拦截器逻辑。- 以 前置通知拦截器 为例,在执行目标方法前,该拦截器首先会执行前置通知逻辑,如果拦截器链中还有其他的拦截器,则继续调用下一个拦截器逻辑,直到拦截器链中没有其他的拦截器后,再去调用目标方法。
// Joinpoint.java
public interface Joinpoint {
// 用于执行拦截器链中的下一个拦截器逻辑
Object proceed() throws Throwable;
Object getThis();
AccessibleObject getStaticPart();
}
- 一个方法调用是一个连接点,方法调用 接口的定义如下。
// Invocation.java
public interface Invocation extends Joinpoint {
Object[] getArguments();
}
// MethodInvocation.java
public interface MethodInvocation extends Invocation {
Method getMethod();
}
- 方法调用接口
MethodInvocation
继承自Invocation
,Invocation
接口又继承自Joinpoint
。
2.2 Pointcut(切点)
- 切点的作用在于选择连接点。
// Pointcut.java
public interface Pointcut {
// 返回一个类型过滤器
ClassFilter getClassFilter();
// 返回一个方法匹配器
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}
-
Pointcut
接口中定义了两个接口,分别用于返回类型过滤器和方法匹配器。 - 类型过滤器 和 方法匹配器 接口的定义如下。
// ClassFilter.java
public interface ClassFilter {
boolean matches(Class<?> clazz);
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
// MethodMatcher.java
public interface MethodMatcher {
boolean matches(Method method, Class<?> targetClass);
boolean matches(Method method, Class<?> targetClass, Object... args);
boolean isRuntime();
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
- 两个接口均定义了
matches()
方法,只需要实现该方法,即可对连接点进行选择。 - Spring Aspects 中使用 AspectJ 表达式对连接点进行选择,提供了一个 AspectJ 表达式的切点类
AspectJExpressionPointcut
。
![](https://img.haomeiwen.com/i5714666/7eab8266f00b47e3.png)
-
AspectJExpressionPointcut
实现了Pointcut
、ClassFilter
和MethodMatcher
接口,具备了通过 AspectJ 表达式对连接点进行选择的能力。 - 对 <2.1> 中的方法连接点用例使用 execution(* *.find*(..)) 表达式,则选择出以 find 开头的
findOne()
和findAll()
两个方法。
2.3 Advice(通知)
- Advice(通知)即定义的 横切逻辑,比如可以定义一个用于监控方法性能的通知,还可以定义一个用于安全检查的通知等。
- 切点解决了在 何处 调用通知的问题,Spring 中定义了几种通知类型,解决 何时 调用通知的问题。
通知类型 | 何时调用 |
---|---|
前置通知(Before advice) | 在目标方便调用前执行通知。 |
后置通知(After advice) | 在目标方法完成后执行通知。 |
返回通知(After returning advice) | 在目标方法执行成功后,调用通知。 |
异常通知(After throwing advice) | 在目标方法抛出异常后,执行通知。 |
环绕通知(Around advice) | 在目标方法调用前后均可执行自定义逻辑。 |
// Advice.java
public interface Advice {
}
// BeforeAdvice.java
public interface BeforeAdvice extends Advice {
}
// MethodBeforeAdvice.java
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method method, Object[] args, Object target) throws Throwable;
}
// AfterAdvice.java
public interface AfterAdvice extends Advice {
}
// AfterReturningAdvice.java
public interface AfterReturningAdvice extends AfterAdvice {
void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}
- 以实现类
AspectJMethodBeforeAdvice
为例。
![](https://img.haomeiwen.com/i5714666/44bc898fa6e33268.png)
2.4 Aspect(切面)
- 虽然有了切点和通知,但是还需要将它们整合起来,这样切点才可以为通知进行导航,然后精确的执行通知逻辑。
- Pointcut(切点)解决了 where 问题,通知(Advice)解决了 when 和 how 问题,Aspect(切面)整合了切点和通知两个模块,从而解决了 何处方法(where)在何时(when 前置、后置、环绕等)执行如何(how)的横切逻辑 的问题。
- AOP 中,切面只是一个概念,并没有一个具体的接口或类与此对应,不过
PointcutAdvisor
切点通知器接口与切面功能类似,该接口既可以返回切点,也可以返回通知。
// Advisor.java
public interface Advisor {
Advice getAdvice();
boolean isPerInstance();
}
// PointcutAdvisor.java
public interface PointcutAdvisor extends Advisor {
Pointcut getPointcut();
}
-
切面和
PointcutAdvisor
存在的差异是,在一个切面中,一个切点对应多个通知,是一对多的关系(也可以配置多个 Pointcut,形成多对多的关系)如下例配置,而PointcutAdvisor
的实现类中,切点和通知是一一对应的关系。 -
用例。
// TestService.java
public interface TestService {
void save(String name);
void update(String name);
void delete(String name);
String findOne(String name);
List<String> findAll();
}
// TestServiceImpl.java
public class TestServiceImpl implements TestService {
public void save(String name) {
}
public void update(String name) {
}
public void delete(String name) {
}
public String findOne(String name) {
System.out.println("TestServiceImpl`s findOne");
return "";
}
public List<String> findAll() {
System.out.println("TestServiceImpl`s findAll");
return new ArrayList<String>();
}
}
// AopTest.java
public class AopTest {
public void before() {
System.out.println("AopTest`s before");
}
public void after() {
System.out.println("AopTest`s after");
}
}
// Test.java
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
TestService testService = (TestService) context.getBean("testServiceImpl");
testService.findAll();
}
}
/* print
AopTest`s before
TestServiceImpl`s findAll
AopTest`s after
*/
- Spring 配置文件。
<!-- spring.xml -->
<aop:aspectj-autoproxy/>
<bean id="testServiceImpl" class="spring.test.aop.TestServiceImpl"/>
<bean id="aopTest" class="spring.test.aop.AopTest"/>
<aop:config expose-proxy="true">
<aop:aspect ref="aopTest">
<!-- pointcut -->
<aop:pointcut id="testPointcut" expression="execution(* spring.test.aop.*.find*(..))" />
<!-- advoce -->
<aop:before method="before" pointcut-ref="testPointcut"/>
<aop:after method="after" pointcut-ref="testPointcut"/>
</aop:aspect>
</aop:config>
- 切面中配置了一个切点和两个通知,两个通知均引用了同一个切点,即
pointcut-ref=“testPointcut”
,该配置最终会被转换成两个PointcutAdvisor
。
![](https://img.haomeiwen.com/i5714666/a5c7407cf60bb638.png)
2.5 Weaving(织入)
- 织入是在切点的引导下,将通知逻辑插入到方法调用上,使得通知逻辑在方法调用时得以执行。
- Spring 是通过 后置处理器 BeanPostProcessor 接口 实现的。
- 该接口是 Spring 提供的一个拓展接口,通过实现该接口,可在 bean 初始化前后做一些自定义操作。
- 在 bean 初始化完成后,即 bean 执行完初始化方法(init-method),Spring 进行织入操作。
- Spring 通过切点对 bean 类中的方法进行匹配。若匹配成功,则为该 bean 生成代理对象,并将代理对象返回给容器。容器向后置处理器输入 bean 对象,得到 bean 对象的代理,这样就完成了织入的过程。
网友评论