likes
comments
collection
share

动态代理是如何在Spring AOP中起作用的?AOP是什么? AOP(Aspect Oriented Programm

作者站长头像
站长
· 阅读数 50

动态代理是如何在Spring AOP中起作用的?AOP是什么? AOP(Aspect Oriented Programm

JDK动态代理实现原理

AOP是什么?

AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,是一种技术解决方案,旨在通过分离横切关注点(cross-cutting concerns)来提高软件的模块化和可维护性。

它可以由Java进行实现(如AOP Alliance(Java/J2EE AOP标准) ,如实现该标准的 AspectJ,也可以由其他编程语言进行实现。

下面将以我自己的理解来解释 AOP 运行的原理以及他是如何让Shiro权限拦截生效的(如有不对的地方,欢迎指正~)

为什么需要AOP?

假设这么个场景:

技术经理要求所有业务方法执行前,记录一下传入参数,方法执行完成后,记录一下程序消耗时间,你会选择在每个业务方法进行修改以完成需求?如果业务数量庞大,这无疑会增加代码的冗余量,也会增加后续的维护难度。

那么我们是不是可以考虑将这些额外的需求封装成一个个通用的对象(Advice),然后通过特定匹配方式(PointCut),以决定额外的操作(如:纪录程序消耗时间)需要在哪些类的哪些方法(JoinPoint)执行前或执行后运行?这需要通过JDK动态代理/CGLIB 来实现。

动态代理是如何在Spring AOP中起作用的?AOP是什么? AOP(Aspect Oriented Programm

那么AOP编程思想在代码上是如何体现的呢?

AOP编程思想的代码实现

先来介绍AOP的几个主要的术语:

切点(Pointcut): 相当于一个“过滤器”,用来指定哪些类的哪些方法(即目标方法)的哪些执行时间点需要加入特殊的业务功能进行方法增强

通常通过特定的表达式来完成,如 AspectJ 的切点表达式语言,他可以基于方法签名、类名、包名等进行匹配。例如“execution (* com.example.service. . (..))” 表示匹配 com.example.service 包下的任何类的任何方法都需要进行增强。

通知(Advice): 对目标方法进行增强的特殊功能代码的封装,比如实现记录日志的业务功能代码。通常有以下类型的通知:

  • 前置通知(Before advice) :在目标方法执行之前执行。可以用于在方法执行前进行一些参数检查、权限验证等操作。
  • 后置通知(After advice) :在目标方法执行之后执行,无论方法是否抛出异常。通常用于清理资源、记录方法执行结果等。
  • 返回通知(After returning advice) :在目标方法正常返回后执行。可以对方法的返回值进行处理或记录。
  • 异常通知(After throwing advice) :在目标方法抛出异常后执行。用于处理异常情况,如记录错误日志、进行异常恢复等。
  • 环绕通知(Around advice) :可以在目标方法执行前后都执行代码,并且可以控制目标方法的执行流程。可以用于实现性能监控事务管理等功能。

切面(Aspect): 对系统多个模块共同关注的业务逻辑功能的抽象与封装。例如:日志记录可以被封装成一个切面,其中包含在方法执行前后进行日志记录的功能代码(通知),以及一个标记哪些方法需要进行日志记录的表达式(切入点)。

连接点(Joinpoint): 连接点是程序执行过程中的特定点,例如方法调用、方法执行、异常抛出等。这些时间点可以插入额外的业务逻辑,也就是前面说的各种不同类型的通知。

目标对象(Target): 系统中需要切面进行业务方法增强的对象,也就是提供原始业务代码的对象。

AOP Alliance 的两个核心接口

再来说说 AOP Alliance 的两个核心接口

动态代理是如何在Spring AOP中起作用的?AOP是什么? AOP(Aspect Oriented Programm

MethodInterceptor: 拦截目标方法的调用,可以在目标方法执行前后加入额外的业务逻辑处理,主要看invoke(MethodInvocation invocation),不同类型的 Advice 就是通过调整该方法中的 invocation.proceed() 与 通知方法的执行顺序来实现的。

MethodInvocation: 对目标方法的调用,主要看proceed()方法。该方法主要通过遍历目标对象的业务方法匹配到的所有 MethodInterceptor ,将自身作为参数传入 invoke(MethodInvocation invocation) 进行调用,最终达到目标方法的增强。

先看 ReflectiveMethodInvocation#proceed()

public Object proceed() throws Throwable {
		// 判断是不是拦截器链中的最后一个拦截器的执行,如果是,则可以执行目标方法了
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}
            //通过索引获取下一个拦截器
		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
                                //判断是否包含动态方法匹配器,如果包含,则需要对目标方法进行进一步的匹配,例如可以根据方法的参数、返回值、调用上下文等条件来决定是否应用拦截器实现更细粒度的控制
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
			if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
                        //调用拦截器的invoke 方法,并将当前MethodInvocation对象传入
				return dm.interceptor.invoke(this);
			}
			else {
				//如果动态匹配失败,则跳过此拦截器的执行,通过进入到下一个拦截器的匹配与执行(如果有)
				return proceed();
			}
		}
		else {
			//纯粹的拦截器,不包含动态方法匹配器,直接执行invoke方法
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

通过此方法,我们可以依次遍历并执行完所有匹配到的拦截器的invoke方法。

再看看 AspectJMethodBeforeAdvice#before

    public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
    //调用前置通知方法
    invokeAdviceMethod(getJoinPointMatch(), null, null);
}

该方法最终会被MethodBeforeAdviceInterceptor#invoke(MethodInvocation mi)调用

public Object invoke(MethodInvocation mi) throws Throwable {
    //调用前置通知方法
    this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
    //进行下一个拦截器的调用,如果当前已是最后一个拦截器,则去调用目标方法
    return mi.proceed();
}

再看看 AspectJAfterAdvice#invoke(MethodInvocation mi)

public Object invoke(MethodInvocation mi) throws Throwable {

    try {
        //调用目标方法
        return mi.proceed();
    }

    finally {
        //调用后置通知方法,不管目标方法的执行是否抛出异常
        invokeAdviceMethod(getJoinPointMatch(), null, null);
    }

}

其他类型的通知也是一样的道理,只不过通知方法执行的时间点不一样而已~

Spring AOP代理

Spring AOP是如何对目标方法进行增强的呢?

Spring AOP 主要通过JDK动态代理CGLIB来实现。而AbstractAdvisorAutoProxyCreatorAbstractAutoProxyCreator这两个自动代理创建器通过其子类在当前上下文中基于查找到的 Advisors 为每个目标bean实例创建AOP代理。这些创建器都最终实现BeanPostProcessor接口。

动态代理是如何在Spring AOP中起作用的?AOP是什么? AOP(Aspect Oriented Programm

例如:AnnotationAwareAspectJAutoProxyCreator 用于实现基于@AspectJ 注解的AOP代理,而Shiro注解@RequiresPermissions 则需要通过 DefaultAdvisorAutoProxyCreator 和 AuthorizationAttributeSourceAdvisor 完成AOP代理来让权限拦截生效。

下面通过AnnotationAwareAspectJAutoProxyCreator和JDK动态代理来讲解AOP的具体实现原理。

我们从创建bean实例的 AbstractAutowireCapableBeanFactory#doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)开始追踪代码:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		//其他代码
		Object exposedObject = bean;
		try {
                        //实例化进行对象属性注入
			populateBean(beanName, mbd, instanceWrapper);
                        //进行对象初始化
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
               //其他代码。。。
                }
		return exposedObject;
	}

从initializeBean(beanName, exposedObject, mbd)进一步追踪进入到 AbstractAutoProxyCreator#postProcessAfterInitialization(@Nullable Object bean, String beanName)

//使用已配置的拦截器为指定的bean创建代理
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {

    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
    if (this.earlyProxyReferences.remove(cacheKey) != bean) {
        return wrapIfNecessary(bean, beanName, cacheKey);
     }
    }
    return bean;

}

再进一步追踪进入到 wrapIfNecessary 方法里面

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
                //其他代码
		// 获取目标bean匹配到的通知(拦截器)
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
                        //构建目标对象的Advisor(包含了Advice+PointCut),创建代理对象
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

从 createProxy 到 ProxyFactory#getProxy(@Nullable ClassLoader classLoader)

public Object getProxy(@Nullable ClassLoader classLoader) {
    return createAopProxy().getProxy(classLoader);
}

此时,我们可以看到CglibAopProxyJdkDynamicAopProxy分别实现了AopProxy接口。 动态代理是如何在Spring AOP中起作用的?AOP是什么? AOP(Aspect Oriented Programm

以JDK动态代理为例,我们直接看 JdkDynamicAopProxy 的源码

/**
*  我们看到JdkDynamicAopProxy实现了 InvocationHandler 接口,所以接下来我们主要关注 
*  invoke(Object proxy, Method method, Object[] args)
*/
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {

	//其他代码省略。。。

	private final AdvisedSupport advised;

	private final Class<?>[] proxiedInterfaces;

	//其他代码省略。。。


	public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
		Assert.notNull(config, "AdvisedSupport must not be null");
		if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
			throw new AopConfigException("No advisors and no TargetSource specified");
		}
		this.advised = config;
		this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
		findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
	}


	@Override
	public Object getProxy() {
		return getProxy(ClassUtils.getDefaultClassLoader());
	}

	@Override
	public Object getProxy(@Nullable ClassLoader classLoader) {
		if (logger.isTraceEnabled()) {
			logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
		}
		return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
	}


	@Override
	@Nullable
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object oldProxy = null;
		boolean setProxyContext = false;

		TargetSource targetSource = this.advised.targetSource;
		Object target = null;

		try {
			//其他代码省略。。。
			else if (method.getDeclaringClass() == DecoratingProxy.class) {
				return AopProxyUtils.ultimateTargetClass(this.advised);
			}
			else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
					method.getDeclaringClass().isAssignableFrom(Advised.class)) {
				
				return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
			}

			Object retVal;

			if (this.advised.exposeProxy) {
				
				oldProxy = AopContext.setCurrentProxy(proxy);
				setProxyContext = true;
			}

			
			target = targetSource.getTarget();
			Class<?> targetClass = (target != null ? target.getClass() : null);

			// 获取目标方法的过滤器链
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

			//如果过滤器链为空,直接去调用目标方法,甚至连MethodInvocation对象都不用去创建
			if (chain.isEmpty()) {
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			else {
				// 创建方法调用对象 ReflectiveMethodInvocation
				MethodInvocation invocation =
						new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				//通过过滤器链的执行来完成目标方法的调用,最终完成方法的增强
				retVal = invocation.proceed();
			}

                     //其他代码省略。。。
			return retVal;
		}
		finally {
			//其他代码省略。。。
		}
	}
//其他代码省略。。。
}

invoke方法里面走到invocation.proceed()时间点的时候,不就是进入到文中 AOP Alliance 的两个核心接口 章节提到的内容了吗?

其他更多内容,后续补充~ 欢迎交流学习!

转载自:https://juejin.cn/post/7422190495466061833
评论
请登录