一、前言
本系列是在重看源码分析过程中,对一些遗漏内容的补充,内容仅用于个人学习记录,写的会比较随性,难免有错漏,欢迎指正。
全集目录:Spring源码分析:全集整理
本文系列:
- Spring源码分析十一:@Aspect方式的AOP上篇 - @EnableAspectJAutoProxy
- Spring源码分析十二:@Aspect方式的AOP中篇 - getAdvicesAndAdvisorsForBean
- Spring源码分析十三:@Aspect方式的AOP下篇 - createProxy
- Spring源码分析二十四:cglib 的代理过程
本文衍生篇:
补充篇:
本文,想不借助 AspectJ 框架来实现 Aop 的功能,因此简单看了一下 Spring Aop 其中的一些类的实现。
二、关键类
Spring Aop 有三个关键类 ,如下:
- Pointcut :决定什么时候切入
- Advice :决定切入后的具体行为
- Advisor :包含 Advice 和 Pointcut
下面我们具体来看:
1. Pointcut 分类
public interface Pointcut {
// 检验拦截哪个类
ClassFilter getClassFilter();
// 校验拦截的方法
MethodMatcher getMethodMatcher();
// 恒为 true 的拦截实例
Pointcut TRUE = TruePointcut.INSTANCE;
}
从 Pointcut 的 方法我们就可以看出 Pointcut 的拦截精度在 Method 级别,即通过 ClassFilter 确定拦截哪个类,MethodMatcher 确定拦截哪个方法。
下面是 Pointcut 的一些实现类,我们来简单介绍一下:
名称 | 功能 |
---|---|
ControlFlowPointcut | 可以实现更精细的切入点。如我们对一个方法进行切入通知,但只有这个方法在一个特定方法中被调用的时候执行通知,我们可以使用ControlFlowPointCut流程切入点。但该种方式效率较低,慎重使用。 |
DynamicMethodMatcherPointcut | 该切点为抽象类, 在Pointcut 的基础上增加了接口DynamicMethodMatcher。所谓动态切入点,即每次方法执行前都会进行条件判断,满足条件才会执行。如我们在增强某些方法时指定为方法入参值某个值时才会执行增强,即可使用该类。 |
StaticMethodMatcherPointcut | 该切点为抽象类,在Pointcut 的基础上增加了接口 StaticMethodMatcher 。与 DynamicMethodMatcherPointcut 相反,StaticMethodMatcherPointcut 针对每个方法的增强判断只会执行一次,让将结果缓存起来,之后的判断依赖于缓存结果。 |
ComposablePointcut | 可组合切入点。通过 union 或者 intersection 方法来设置多个 Pointcut、ClassFilter、ClassFilter 的交并集。 |
ExpressionPointcut | ExpressionPointcut 是一个接口,存在两个实现类 AbstractExpressionPointcut,AspectJExpressionPointcut。多提供了一个 getExpression 方法用来获取表达式。可以使用表达式的方式进行匹配。 |
AnnotationMatchingPointcut | 查找被指定注解修饰的类作为切点。 |
TruePointcut | 恒为true的切点 |
为了更好的解释各个 PointCut 的功能,这里我们来以下面的代码具体举例:
package com.kingfish.util;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Advisor;
import org.springframework.aop.DynamicIntroductionAdvice;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
// 这里为了举例,所有所有类写在一起。
public class PointcutMain {
public static class PointCutDemo {
public void say(String msg) {
System.out.println("Hello " + msg);
}
}
public static void main(String[] args) {
Advice advice = createAdvice();
Pointcut pc = createPointCut();
// 创建代理顾问
Advisor advisor = new DefaultPointcutAdvisor(pc, advice);
run(new PointCutDemo(), advisor);
}
// 运行方法
private static void run(PointCutDemo demo, Advisor advisor){
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(demo);
// 创建了代理对象
final PointCutDemo proxyDemo = (PointCutDemo) proxyFactory.getProxy();
// 直接调用 say() 方法
proxyDemo.say("World");
}
// 创建 PointCut 的方法,下面我们会分别实现该方法来分析
private static Pointcut createPointCut() {
return null
}
private static Advice createAdvice() {
// 方法拦截器 Advice
return new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
System.out.println("before");
return invocation.proceed();
} finally {
System.out.println("after");
}
}
};
}
public static void runSay(PointCutDemo demo) {
demo.say("Method World");
}
}
我们预留了一个 createPointCut 方法来创建 Pointcut ,下面我们来演示不同的 Pointcut 创建后的效果。
1.1 ControlFlowPointcut
ControlFlowPointcut 会动态判断调用场景,在合适的调用场景下才会执行增强。但该种方式效率较低,慎重使用。
我们修改上面代码中的方法,如下:
// 运行方法
private static void run(PointCutDemo demo, Advisor advisor){
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(demo);
// 创建了代理对象
final PointCutDemo proxyDemo = (PointCutDemo) proxyFactory.getProxy();
// 直接调用 say() 方法
proxyDemo.say("World");
System.out.println("/************************/");
// 通过 ControlFlowPointcutDemo.runSay 调用runSay 方法
PointcutMain.runSay(proxyDemo);
}
// 创建 ControlFlowPointcut
private static Pointcut createPointCut() {
return new ControlFlowPointcut(PointcutMain.class, "runSay");
}
在运行后,输出结果如下。
Hello World
/************************/
before
Method World
after
这里我们的代码调用了两次 say 方法。不同的是第二次是通过 PointcutMain#runSay 的方法调用。两次调用的结果没有区别。但是由于这里使用 ControlFlowPointcut,而 ControlFlowPointcut在构造时指定了是 PointcutMain.class
和"runSay"
,所以只有通过 PointcutMain#runSay 方法调用目标对象时才会执行增强。
下面我们简单介绍一下其原理:
ControlFlowPointcut 实现了MethodMatcher 接口,而 ControlFlowPointcut#matches方法实现如下,
// 通过构造 保存了 目标 Class 和 methodName。
public ControlFlowPointcut(Class<?> clazz, @Nullable String methodName) {
Assert.notNull(clazz, "Class must not be null");
this.clazz = clazz;
this.methodName = methodName;
}
// 该方法返回ture,则每次方法调用时都会触发下面的 matches 方法。
@Override
public boolean isRuntime() {
return true;
}
// 该方法返回false,则不会执行代理增强。
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
// 统计调用此时,可以通过此属性来判断触发了多少次以方便进行优化。
this.evaluations.incrementAndGet();
for (StackTraceElement element : new Throwable().getStackTrace()) {
// 如果 当前 Class 与目标 Class 匹配 && 当前 methodName 与目标 methodName 匹配返回true。
if (element.getClassName().equals(this.clazz.getName()) &&
(this.methodName == null || element.getMethodName().equals(this.methodName))) {
return true;
}
}
return false;
}
在 ControlFlowPointcut#matches 方法中判断了调用类和方法满足匹配条件后才会返回true,进而触发增强。
1.2 DynamicMethodMatcherPointcut
DynamicMethodMatcherPointcut 为动态切入点。当使用 DynamicMethodMatcherPointcut 时,目标对象每次调用方法都会通过 DynamicMethodMatcherPointcut#matches 方法来确定是否匹配。基于此,我们甚至可以针对每次调用的参数不同来决定是否执行增强。
DynamicMethodMatcherPointcut 和 ControlFlowPointcut类似,不同的是 ControlFlowPointcut 自身实现了 matches 方法,而 DynamicMethodMatcherPointcut 交由我们来实现 matches 方法。
如下:方法第一个入参是 World 时才执行增强。
private static void run(PointCutDemo demo, Advisor advisor) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(demo);
// 创建了代理对象
final PointCutDemo proxyDemo = (PointCutDemo) proxyFactory.getProxy();
// 直接调用 say() 方法
proxyDemo.say("World");
proxyDemo.say("Morld");
}
private static Pointcut createPointCut() {
// 切点
Pointcut pc = new DynamicMethodMatcherPointcut() {
// 每次调用目标对象的方法都会执行该方法
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return "World".equals(args[0]);
}
};
return pc;
}
执行结果如下:
before
Hello World
after
Hello Morld
这里可以看到第二次调用 Morld 的入参并没有执行增强。
其原理简单说明一下:
-
当代理类调用代理方法时会通过
AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice
来获取当前调用方法的方法拦截器,并将结果缓存,之后调用则直接依赖于缓存结果。// AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice。 // 当前 this 为 ProxyFactory,所以这里的缓存是作用域是在 ProxyFactory中,如果换一个 ProxyFactory则需要重新加载一次。 public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) { // 生成当前调用 方法的 Key MethodCacheKey cacheKey = new MethodCacheKey(method); // 尝试从缓存中获取增强 List<Object> cached = this.methodCache.get(cacheKey); if (cached == null) { // 缓存没命中,调用DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice 方法来获取 cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice( this, method, targetClass); // 放入缓存 this.methodCache.put(cacheKey, cached); } return cached; }
-
而
DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice
方法则会调用MethodMatcher#matches(Method method, Class<?> targetClass)
来判断当前的方法拦截器是否适用于当前调用方法。而DynamicMethodMatcherPointcut#matches(Method method, Class<?> targetClass)
返回恒为True,随后程序会判断DynamicMethodMatcherPointcut#isRuntime
是否为 True,如果为True则认为是动态切入点,将其封装成InterceptorAndDynamicMethodMatcher
类型保存到集合中,如果为false则直接保存到集合中。
-
随后在执行增强方法时会通过
DynamicAdvisedInterceptor#intercept
方法调用CglibMethodInvocation#process
,而在CglibMethodInvocation#process
中会判断当前增强是否为InterceptorAndDynamicMethodMatcher
类型,如果是,则触发其MethodMatcher#matches(Method method, Class<?> targetClass, Object... args)
方法来判断是否执行增强。如果不是InterceptorAndDynamicMethodMatcher
类型,则执行其增强方法。
1.3 StaticMethodMatcherPointcut
StaticMethodMatcherPointcut 作为静态切入点并不会在每次方法调用时都去校验,而是在第一次调用时进行校验后便将结果缓存起来,之后的调用依赖于缓存。
这里需要注意 StaticMethodMatcherPointcut 的抽象方法和 DynamicMethodMatcherPointcut 的抽象方法并非是同一个方法,而是MethodMatcher 的重载方法,两个方法调用时机的不同也造成了功能的不同,如下:
- StaticMethodMatcherPointcut#matches(Method method, Class<?> targetClass)
- DynamicMethodMatcherPointcut#matches(Method method, Class<?> targetClass, Object… args)
如下:
private static void run(PointCutDemo demo, Advisor advisor) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(demo);
// 创建了代理对象
final PointCutDemo proxyDemo = (PointCutDemo) proxyFactory.getProxy();
// 直接调用 say() 方法
proxyDemo.say("World");
proxyDemo.say("World");
}
private static Pointcut createPointCut() {
Pointcut pc = new StaticMethodMatcherPointcut() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return"say".equals(method.getName());
}
};
return pc;
}
输出如下:
before
Hello World
after
before
Hello World
after
1.3 ComposablePointcut
ComposablePointcut 可以组合多个 PointCut 、ClassFilter 等,我们把上面两个 PointCut 都使用,并取并集,如下
private static Pointcut createPointCut() {
// 切点
Pointcut controlFlowPointcut = new ControlFlowPointcut(PointcutMain.class, "runSay");
Pointcut dynamicMethodMatcherPointcut = new DynamicMethodMatcherPointcut() {
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return "World".equals(args[0]);
}
};
// 取并集
return new ComposablePointcut(controlFlowPointcut)
.union(dynamicMethodMatcherPointcut);
}
输出如下,两种情况全部满足:
before
Hello World
after
/************************/
before
Hello Method World
after
1.4 ExpressionPointcut
用于表达式匹配的切点,AspectJ 就是创建了此接口 AspectJExpressionPointcut 的实现类 AspectJExpressionPointcut 。在 AspectJExpressionPointcut 中,针对 expression进行了解析完成了expression 匹配的功能。
如有需要详参: Spring源码分析十三:@Aspect方式的AOP中篇 - getAdvicesAndAdvisorsForBean 中 三、筛选合适的Advisors - findAdvisorsThatCanApply
章节中提到了 AspectJExpressionPointcut 。
1.5 AnnotationMatchingPointcut
AnnotationMatchingPointcut 即可以使用注解匹配的 PointCut。被指定注解修饰的 类或方法才会满足 要求。如下:
private static Pointcut createPointCut() {
// 这代表着:满足下面三个条件的类才会被增强 (构造函数有很多重载,这里选择最复杂的一个)
// 1. 被 ClassComponent注解修饰的类
// 2. 被 MethodComponent 注解修饰的方法,
// 3. false 代表不检查超类和接口,即需要当前类满足前两个条件,而不能是父类或接口。
return new AnnotationMatchingPointcut(ClassComponent.class, MethodComponent.class, false);
}
1.7 TruePointcut
恒为 True 的切点,作为某些场景的默认切点。
2. Advice 分类
名称 | 功能 |
---|---|
org.aopalliance.intercept.Interceptor | 代表一个通用的拦截器,一般不直接使用。 而使用 两个子接口 MethodInterceptor、ConstructorInterceptor 拦截特定事件。其中 MethodInterceptor 非常重要,是实现 Aop 功能的核心拦截器。 |
org.springframework.aop.BeforeAdvice | 前置通知的通用标记接口,实现该接口的类会在方法调用前执行增强方法 |
org.springframework.aop.DynamicIntroductionAdvice | DynamicIntroductionAdvice 和 IntroductionAdvisor 一起实现了 Spring Aop 的引介增强功能 |
org.springframework.aop.aspectj.AbstractAspectJAdvice | 抽象类,用于AspectJ 实现 Aop 功能 |
org.springframework.aop.AfterAdvice | 后置通知的通用标记接口,实现该接口的类会在方法调用后执行增强方法 |
2.1 Interceptor
我们一般不直接使用 Interceptor,而是使用其子接口 MethodInterceptor。其子接口提供了 invoke 方法,我们可以在方法执行前后调用。
如下:
private static Advice createAdvice() {
// 方法拦截器 Advice
return new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
System.out.println("before");
return invocation.proceed();
} finally {
System.out.println("after");
}
}
};
}
2.2 BeforeAdvice
BeforeAdvice 作为一个标识接口,代表实现该接口的类会在方法调用前执行。
- MethodBeforeAdvice :BeforeAdvice 的子接口,可以实现该接口完成前置调用。
public interface MethodBeforeAdvice extends BeforeAdvice { void before(Method method, Object[] args, @Nullable Object target) throws Throwable; }
- MethodBeforeAdviceInterceptor :BeforeAdvice 的实现类,MethodBeforeAdvice 会被封装成MethodBeforeAdviceInterceptor,MethodBeforeAdvice 的前置调用基于此类完成。
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable { private final MethodBeforeAdvice advice; public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) { Assert.notNull(advice, "Advice must not be null"); this.advice = advice; } @Override public Object invoke(MethodInvocation mi) throws Throwable { // 前置执行advice.before 方法。 this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis()); return mi.proceed(); } }
如下:
private static Advice createAdvice() {
// 方法拦截器 Advice
return new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("MethodBeforeAdvice");
}
};
}
2.3 DynamicIntroductionAdvice
DynamicIntroductionAdvice 的功能很强大,引介切面。
引入增强(Introduction Advice)的概念:一个Java类,没有实现A接口,在不修改Java类的情况下,使其具备A接口的功能。
详参 Spring 源码分析衍生篇十二 :AOP 中的引介增强
2.4 AbstractAspectJAdvice
AspectJ 功能实现基于该抽象类。这里不再赘述。
2.5 AfterAdvice
AfterAdvice 与 BeforeAdvice 类似,作为一个后置调用标志,实现该接口的类会在方法调用后调用。不同于 BeforeAdvice 的是,后置调用分为正常执行结束的后置调用和执行异常的后置调用。
我们这里不再介绍 AspectJ 的相关实现类,
- AfterReturningAdvice :AfterAdvice 的子接口,可以实现该接口完成正常后置调用。
- AfterReturningAdviceInterceptor :AfterAdvice 的实现类,AfterReturningAdvice 会被封装成AfterReturningAdviceInterceptor ,AfterReturningAdvice 的前置调用基于此类完成。
- ThrowsAdvice :AfterAdvice 的子接口,可以实现该接口完成异常后置调用。需要注意该接口并没有提供额外方法供实现,而是在 ThrowsAdviceInterceptor 中指定了方法名为 afterThrowing。
- ThrowsAdviceInterceptor :AfterAdvice 的实现类,ThrowsAdvice 会被封装成ThrowsAdviceInterceptor ,ThrowsAdvice 的前置调用基于此类完成。
这里我们看一下 ThrowsAdviceInterceptor 的实现来了解一下异常时是如何执行增强流程的:
public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {
private static final String AFTER_THROWING = "afterThrowing";
private static final Log logger = LogFactory.getLog(ThrowsAdviceInterceptor.class);
private final Object throwsAdvice;
/** Methods on throws advice, keyed by exception class. */
private final Map<Class<?>, Method> exceptionHandlerMap = new HashMap<>();
public ThrowsAdviceInterceptor(Object throwsAdvice) {
Assert.notNull(throwsAdvice, "Advice must not be null");
this.throwsAdvice = throwsAdvice;
Method[] methods = throwsAdvice.getClass().getMethods();
for (Method method : methods) {
// 获取 throwsAdvice 中方法名为 afterThrowing && 入参个数为 1或者4的方法
if (method.getName().equals(AFTER_THROWING) &&
(method.getParameterCount() == 1 || method.getParameterCount() == 4)) {
Class<?> throwableParam = method.getParameterTypes()[method.getParameterCount() - 1];
// 判断最后一个入参是否是异常类
if (Throwable.class.isAssignableFrom(throwableParam)) {
// An exception handler to register...
// 如果是则将将当前方法和对应的异常类保存起来
this.exceptionHandlerMap.put(throwableParam, method);
}
}
}
if (this.exceptionHandlerMap.isEmpty()) {
throw new IllegalArgumentException(
"At least one handler method must be found in class [" + throwsAdvice.getClass() + "]");
}
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
catch (Throwable ex) {
// 查找当前异常对应的处理方法
Method handlerMethod = getExceptionHandler(ex);
if (handlerMethod != null) {
// 当方法调用出现时调用 invokeHandlerMethod 方法 类执行对应异常的增强。
invokeHandlerMethod(mi, ex, handlerMethod);
}
throw ex;
}
}
private void invokeHandlerMethod(MethodInvocation mi, Throwable ex, Method method) throws Throwable {
Object[] handlerArgs;
if (method.getParameterCount() == 1) {
handlerArgs = new Object[] {ex};
}
else {
// 四个方法入参,依次为 方法实例、参数数组、调用类、异常内容
handlerArgs = new Object[] {mi.getMethod(), mi.getArguments(), mi.getThis(), ex};
}
try {
method.invoke(this.throwsAdvice, handlerArgs);
}
catch (InvocationTargetException targetEx) {
throw targetEx.getTargetException();
}
}
}
如下:
定义一个 CustomThrowsAdvice 类来进行异常增强
private static Advice createAdvice() {
// 方法拦截器 Advice
return new CustomThrowsAdvice();
}
public static class CustomThrowsAdvice implements ThrowsAdvice {
// 也可以直接写这样一个异常参数
// public void afterThrowing(Throwable throwable){
// System.out.println("CustomThrowsAdvice.afterThrowing");
// }
// 参数依次是 调用方法, 调用方法入参, 代理的原始对象,抛出的异常
public void afterThrowing(Method method, Object[] params, Object object, Throwable throwable){
System.out.println("CustomThrowsAdvice.afterThrowing");
}
}
运行结果:
Hello World
CustomThrowsAdvice.afterThrowing
...抛出的异常日志
3. Advisor 分类
类名 | 功能 |
---|---|
IntroductionAdvisor | 只能应用于类级别的拦截,只能使用Introduction型的Advice 。用于引入增强 |
PointcutAdvisor | 可以使用任何类型的Pointcut,以及几乎任何类型的Advice。用于普通的增强 |
关于 IntroductionAdvisor ,我们开设了衍生篇内容专门解释,详参: Spring 源码分析衍生篇十二 :AOP 中的引介增强
三、Spring Aop Demo
本来想模拟一下事务的,懒得连库就写个极简Demo:被 @CustomAnnotation 注解修饰 类会被代理。算抛砖引玉吧。
// 自定义注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CustomAnnotation {
}
// 配置类,由于是 Demo ,所以一切从简
@Configuration
public class AopConfig {
@Bean
public Advisor advisor() {
final DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();
// 被 @CustomAnnotation 注解修饰的类满足切点要求
defaultPointcutAdvisor.setPointcut(new AnnotationMatchingPointcut(CustomAnnotation.class));
// 设置增强方法
defaultPointcutAdvisor.setAdvice((MethodInterceptor) invocation -> {
System.out.println("before");
final Object proceed = invocation.proceed();
System.out.println("after");
return proceed;
});
return defaultPointcutAdvisor;
}
}
// 被代理类
@CustomAnnotation
@RestController
@RequestMapping("common")
public class CommonContorller {
@RequestMapping("common")
public String common(String msg){
System.out.println("CommonContorller.common");
return msg;
}
}
调用输出
before
CommonContorller.common
after
以上:内容部分参考
https://blog.csdn.net/daryl715/article/details/1743311
https://blog.csdn.net/daryl715/article/details/1732978
https://blog.csdn.net/f641385712/article/details/89303088
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正