AOP是spring框架的两大核心之一,在日常工作中也会经常会到它,典型的应用用来记录日志,包括spring的注解事务也是通过AOP实现的。大家是否都对AOP原理都很清楚呢?AOP底层到底是如何实现的?AOP在什么场景下会失效?
二. AOP失效例子带着这些疑问,我们从一个AOP失效的例子开始揭开AOP的面纱。
- 定义一个切点
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface AopTest {
String value() default "";
}
- 定义一个切面
@Aspect
@Component
public class AopAspect {
@Pointcut("@annotation(com.zsc.aspect.AopTest)")
public void pointCut() {}
@Around("pointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("-----------around before-----------");
Object proceed = point.proceed();
System.out.println("-----------around after-----------");
return proceed;
}
}
- 写一个被切service
@Service
public class AopService {
@Autowired
ApplicationContext applicationContext;
@AopTest(value = "test1")
public void test1() {
System.out.println("aop service test1 method");
test2();
}
@AopTest(value = "test12")
public void test2() {
System.out.println("aop service test2 method");
}
}
- 写一个测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = FormApplication.class,webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AopServiceTest {
@Resource
AopService aopService;
@Test
public void test1() {
aopService.test1();
}
}
- 运行结果
结果:从代码简单分析可以得出结论,test1方法的切面生效,test2方法的切面未生效。 - 分析
通过打断点看看test1和test2方法的调用对象分别是什么
test1方法的调用对象是AopService的代理对象
test2方法的调用对象是AopService的直接对象
所以很容易理解为什么test2方法的切面失效,因为调用test2方法的对象不是代理对象,故不能执行切面的代码。 - 解决方案
解决方法思路很简单,就是要用代理对象去调用test2方法,这里给出最简单的方法
开始之前先添加一些切面方法
@Aspect
@Component
public class AopAspect {
@Pointcut("@annotation(com.zsc.aspect.AopTest)")
public void pointCut() {
}
@Before("pointCut()")
public void before(JoinPoint point) {
System.out.println("before");
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("-----------around before-----------");
Object proceed = point.proceed();
System.out.println("-----------around after-----------");
return proceed;
}
@AfterReturning(value = "pointCut()")
public void afterReturning(JoinPoint joinPoint) {
System.out.println("afterReturning advise");
}
@AfterThrowing(value = "pointCut()")
public void afterThrowing(JoinPoint joinPoint) {
System.out.println("afterThrowing advise");
}
@After(value = "pointCut()")
public void after(JoinPoint joinPoint) {
System.out.println("after advise");
}
}
解决了上面的AOP失效问题,继续探索AOP的动态代理及Before、Around、After、AfterReturning、AfterThrowing等切面逻辑是如何执行的。
- 查看spring生成的代理对象内容
添加系统属性,保存spring生成代理类的class文件,注意:必需加在static静态块里,不能直接加在单元测试test1方法里,因为执行test1方法时,代理类已经生成结束了。运行单元测试方法,然后在指定目录可以找到生成的代理类,将生成的代理类直接拖到idea中进行反编译。
反编译后的代理类中的test1和test2方法代码很少而且逻辑完全一样的。
继续debug,可以看到调用代理类的test1方法时实际上就是调用代理类中的DynamicAdvisedInterceptor类的intercept方法
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
target = targetSource.getTarget();
Class> targetClass = (target != null ? target.getClass() : null);
// 获取所有before,after,around等切面
List
继续debug
调用了CglibMethodInvocation的proceed方法,该方法直接调用了其父类ReflectiveMethodInvocation的proceed方法
@Override
@Nullable
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
// 切面调用链执行完,调用被切面方法
return invokeJoinpoint();
}
// 使用currentInterceptorIndex计数完成调用链模式,而没有使用for循环
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
ReflectiveMethodInvocation有几个比较重要的属性
最后通过 AspectJAroundAdvice、AspectJAfterThrowingAdvice、MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、AspectJAfterAdvice等类中的invoke方法可以明白如何做的前置切面、后置切面、环绕切面等。



