spring-reading./spring-aop/spring-aop-advisor/README.md

239 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

## Advisor
- [Advisor](#advisor)
- [一、基本信息](#一基本信息)
- [二、基本描述](#二基本描述)
- [三、主要功能](#三主要功能)
- [四、接口源码](#四接口源码)
- [五、主要实现](#五主要实现)
- [六、类关系图](#六类关系图)
- [七、最佳实践](#七最佳实践)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading)
### 二、基本描述
`Advisor`接口是Spring框架中的一个关键接口用于将切点Pointcut和通知Advice组合起来以便在AOP面向切面编程中定义何时、何地以及如何应用横切关注点。
### 三、主要功能
1. **组合切点和通知**
+ Advisor接口允许将切点Pointcut和通知Advice组合在一起。切点确定何时应该应用通知而通知定义了在连接点处执行的代码。
### 四、接口源码
`Advisor`接口是Spring框架中的一个基础接口用于持有AOP通知在连接点执行的操作和确定通知适用性的过滤器例如切点。该接口定义了获取通知部分的方法`getAdvice()`,以及确定通知是否与特定实例相关联的方法`isPerInstance()`。同时,该接口还提供了一个常量`EMPTY_ADVICE`用作当未配置适当通知时的占位符。在Spring AOP中Advisor接口允许支持不同类型的通知例如拦截器、前置通知、异常通知等并且并非所有通知都需要使用拦截来实现。
```java
/**
* 基础接口持有AOP <b>通知</b>(在连接点执行的操作)和确定通知适用性的过滤器(例如切点)。
* <i>此接口不供Spring用户使用而是为了在支持不同类型的通知时提供共性。</i>
*
* <p>Spring AOP基于通过方法拦截interception提供的<b>环绕通知</b>符合AOP Alliance拦截API。
* Advisor接口允许支持不同类型的通知例如<b>前置</b>和<b>后置</b>通知,它们不一定要使用拦截来实现。
*
* @author Rod Johnson
* @author Juergen Hoeller
*/
public interface Advisor {
/**
* 如果尚未配置适当的通知,则从{@link #getAdvice()}返回一个空的{@code Advice}的常用占位符。
* @since 5.0
*/
Advice EMPTY_ADVICE = new Advice() {};
/**
* 返回此方面的通知部分。通知可以是拦截器、前置通知、异常通知等。
* @return 如果切点匹配,则应应用的通知
* @see org.aopalliance.intercept.MethodInterceptor
* @see BeforeAdvice
* @see ThrowsAdvice
* @see AfterReturningAdvice
*/
Advice getAdvice();
/**
* 返回此通知是否与特定实例相关联例如创建混入或与从同一Spring bean工厂获取的被通知类的所有实例共享。
* <p><b>请注意,框架当前不使用此方法。</b>
* 典型的Advisor实现总是返回{@code true}。
* 使用singleton/prototype bean定义或适当的编程代理创建来确保Advisor具有正确的生命周期模型。
* @return 此通知是否与特定目标实例关联
*/
boolean isPerInstance();
}
```
`PointcutAdvisor`接口是所有由切点驱动的Advisor的超级接口。它覆盖了几乎所有的Advisor但不包括引介Advisor因为引介Advisor不适用于方法级别的匹配。该接口表示由切点驱动的Advisor通过`getPointcut()`方法获取驱动该Advisor的切点。 PointcutAdvisor通常用于基于切点的切面通过指定切点来确定通知逻辑应该应用于哪些连接点。
```java
/**
* 所有由切点驱动的Advisor的超级接口。
* 这几乎涵盖了所有的Advisor除了引介Advisor
* 因为方法级别的匹配不适用于引介Advisor。
*
* 该接口是Advisor的子接口用于表示由切点驱动的Advisor。
* 切点驱动的Advisor通常用于基于切点的切面通过指定切点来确定通知逻辑应该应用于哪些连接点。
*
* 作者Rod Johnson
*/
public interface PointcutAdvisor extends Advisor {
/**
* 获取驱动该Advisor的切点。
*/
Pointcut getPointcut();
}
```
### 五、主要实现
1. **RegexpMethodPointcutAdvisor**
- 基于正则表达式来匹配方法名的切点。通过使用正则表达式,可以根据方法名模式匹配连接点,并将通知应用于匹配的连接点,从而实现基于方法名模式的切面逻辑。
2. **AspectJExpressionPointcutAdvisor**
- 基于AspectJ表达式来定义切点。通过使用AspectJ的语法可以更灵活地定义切面从而匹配连接点并将通知应用于匹配的连接点实现更复杂的切面逻辑。
3. **NameMatchMethodPointcutAdvisor**
- 基于方法名模式匹配来定义切点。通过使用方法名模式,可以轻松地匹配连接点,并将通知应用于匹配的连接点,从而实现基于方法名模式的切面逻辑。
4. **DefaultPointcutAdvisor**
- 一个通用的切点Advisor用于将切点和通知组合在一起。它允许将任何类型的通知与任何类型的切点结合使用并将通知应用于匹配的连接点从而实现横切关注点的管理。
### 六、类关系图
~~~mermaid
classDiagram
direction BT
class Advisor {
<<Interface>>
}
class AspectJPointcutAdvisor
class DefaultPointcutAdvisor
class NameMatchMethodPointcutAdvisor
class PointcutAdvisor {
<<Interface>>
}
class RegexpMethodPointcutAdvisor
AspectJPointcutAdvisor ..> PointcutAdvisor
DefaultPointcutAdvisor ..> PointcutAdvisor
NameMatchMethodPointcutAdvisor ..> PointcutAdvisor
PointcutAdvisor --> Advisor
RegexpMethodPointcutAdvisor ..> PointcutAdvisor
~~~
### 七、最佳实践
使用Advisor来创建代理对象并应用切面逻辑。首先通过创建代理工厂`ProxyFactory`,并将目标对象`MyService`传递给它。然后,通过`proxyFactory.addAdvisor(new MyCustomAdvisor())`添加了一个自定义的Advisor其中包含了切点和通知的定义。接着通过`proxyFactory.getProxy()`获取了代理对象`MyService`。最后,调用了代理对象的`foo()`方法,该方法触发了切面逻辑的执行。
```java
public class AdvisorDemo {
public static void main(String[] args) {
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory(new MyService());
// 添加Advisor
proxyFactory.addAdvisor(new MyCustomAdvisor());
// 获取代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用方法
proxy.foo();
}
}
```
`MyCustomAdvisor`类是一个自定义的Advisor它实现了`PointcutAdvisor`接口,并用于将通知应用于带有特定注解的方法。在该类中,我们定义了一个通知对象`advice`和一个切点对象`pointcut`,切点用于匹配带有自定义注解`MyCustomAnnotation`的方法。通过实现`getPointcut()`方法和`getAdvice()`方法,我们指定了切点和通知的逻辑。
```java
/**
* 自定义Advisor用于将通知应用于带有特定注解的方法。
*/
public class MyCustomAdvisor implements PointcutAdvisor {
/**
* 通知对象
*/
private final Advice advice = new MyAdvice();
/**
* 切点对象,用于匹配带有自定义注解的方法
*/
private final Pointcut pointcut = new AnnotationMatchingPointcut(null, MyCustomAnnotation.class);
@Override
public Pointcut getPointcut() {
return pointcut;
}
@Override
public Advice getAdvice() {
return advice;
}
@Override
public boolean isPerInstance() {
return true;
}
}
```
`MyAdvice`类是一个通知类,它实现了`MethodInterceptor`接口,用于在方法执行前后添加额外的逻辑。在`invoke`方法中,我们首先输出了正在调用的方法名,然后调用了`invocation.proceed()`方法来执行原始方法,并获取了方法执行的结果。最后,我们在方法执行之后再次输出了方法名。
```java
public class MyAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 在方法调用之前执行的逻辑
System.out.println("Before Method " + invocation.getMethod().getName());
// 调用原始方法
Object result = invocation.proceed();
// 在方法调用之后执行的逻辑
System.out.println("After Method " + invocation.getMethod().getName());
return result;
}
}
```
`MyCustomAnnotation`是一个自定义的注解,通常用于标记需要特殊处理的方法。
```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyCustomAnnotation {
}
```
`MyCustomAnnotation`类其中包含一个名为`foo`的方法。该方法被`@MyCustomAnnotation`注解标记,表明需要特殊处理。
```java
public class MyService {
@MyCustomAnnotation
public void foo() {
System.out.println("foo...");
}
}
```
运行结果,切面逻辑成功应用于带有特定注解的方法。
```java
Before Method foo
foo...
After Method foo
```