aop源码分析之 —— 创建代理对象

前言

在上一篇我们分析了AOP生成代理对象的源码流程,我们知道,springaop中,生成代理对象一般有2种,当目标对象实现了接口时,默认使用JDK代理,否则就采用cglib代理,而且,最终调用目标对象的方法的时候,我们发现了是代理对象调用的,如下图所示
在这里插入图片描述

于是就好奇了,spring到底是怎么完成了代理过程的呢?即这个代理对象是通过什么样的方式完成的呢?由于本篇研究JDK动态代理,下面,我们先通过一段调试用的代码分析一下,需要通过实现接口的方式来完成,代码直接贴出来

1、提供一个接口

public interface Caculate {

	int add(int numA, int numB);

}

2、接口实现类

@Component
public class ConggeCaculate implements Caculate {

	@Override
	public int add(int numA, int numB) {
		System.out.println("执行目标方法 add");
		//System.out.println(1/0);
		return numA + numB;
	}

}

3、全局bean扫描配置类

@ComponentScan(basePackages = "com.congge.v3")
@Component
@EnableAspectJAutoProxy
public class MainConfig {


}

4、AOP配置类

@Aspect
@Component
public class ConggeLogAspect {

	@Pointcut("execution(* com.congge.v3.ConggeCaculate.*(..))")
	public void pointCut(){

	}

	@Before(value = "pointCut()")
	public void methodBefore(JoinPoint joinPoint){
		String methodName = joinPoint.getSignature().getName();
		System.out.println("执行目标方法前置通知:" + methodName);
	}

	@After(value = "pointCut()")
	public void methodAfter(JoinPoint joinPoint){
		String methodName = joinPoint.getSignature().getName();
		System.out.println("执行目标方法后置通知:" + methodName);
	}

	@AfterReturning(value = "pointCut()",returning = "result")
	public void methodReturning(JoinPoint joinPoint,Object result){
		String methodName = joinPoint.getSignature().getName();
		System.out.println("执行目标方法返回通知:" + methodName+",返回结果:"+ result);
	}

	@AfterThrowing(value = "pointCut()")
	public void methodThrowing(JoinPoint joinPoint){
		String methodName = joinPoint.getSignature().getName();
		System.out.println("执行目标方法异常通知:" + methodName);
	}

}

5、测试方法

public class ConggeMainClass {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
		Caculate caculate = (Caculate)context.getBean("conggeCaculate");
		System.out.println(caculate.add(1,2));
	}
}

通过上一篇的执行流程分析,我们知道最终生成代理对象的位置在下面这个方法中,该方法通过循环查找合适的后置处理器,最终生成代理对象,

@Override
	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
			throws BeansException {
		//获取我们容器中所有的bean 的后置处理器
		Object result = existingBean;
		//AOP和事务都会在此处生成代理对象
		//AOP的注解@EnableAspectJAutoProxy为我们的容器导入了AnnotationAwareAspectJAutoProxyCreator
		//而通过调用关系链,可知最终实现了BeanPostProcessor接口
		for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
			Object current = beanProcessor.postProcessAfterInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}

于是我们的疑问最终就定位到这里了,为什么通过回调某个后置处理器就完成了代理对象的生成呢?

我们知道,在本例中,实现AOP一个很重要的注解是在某个类中加了一个@EnableAspectJAutoProxy,也就是说加了这个注解之后,目标对象通过spring一系列的过程变成了代理对象,那就是说,解决问题的关键点就在这个注解中了
在这里插入图片描述

点到这个注解中去,要重点关注一下图中圈起来的@Import注解,这个注解的作用就是为容器添加一个bean,这就很厉害了,也就是说通过这个注解就把AspectJAutoProxyRegistrar这个类以bean的形式注册到容器中来了,这么说我们就把焦点集中到AspectJAutoProxyRegistrar这个类里面去
在这里插入图片描述

扫描二维码关注公众号,回复: 9004848 查看本文章

来到AspectJAutoProxyRegistrar这个类,该类实现了一个ImportBeanDefinitionRegistrar的接口,这个接口是spring内置提供的一个可以为容器注入bean的一种方式,即只要你的类实现了这个接口,那么spring的IOC容器启动的时候就会扫描到这个类,相当于是一种扩展bean的实现方式

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

	/**
	 * Register, escalate, and configure the AspectJ auto proxy creator based on the value
	 * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
	 * {@code @Configuration} class.
	 */
	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy != null) {
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
			}
		}
	}

}

举例说明,我们有一个Hello的类,

public class Hello {
	public Hello(){
		System.out.println("Hello class");
	}
}

再写一个类实现ImportBeanDefinitionRegistrar接口,

public class BeanRegistry implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

		RootBeanDefinition beanDefinition = new RootBeanDefinition(Hello.class);
		registry.registerBeanDefinition("hello",beanDefinition);

	}
}

最后再在mainConfig中通过@Import的注解方式导入进去

@ComponentScan(basePackages = "com.congge.v3")
@Component
@EnableAspectJAutoProxy
@Import(BeanRegistry.class)
public class MainConfig {

}

通过这种方式,我们再测试一下代码,看看是否能够通过获取bean的方式拿到这个bean呢?通过结果可以看到,hello这个类已经变成了一个bean了,即可以通过上述的方式完成注入到spring容器中的功能
在这里插入图片描述

接着上面的代码,我们重点关注这段代码,很明显,通过字面意思也大概能明白意思是注册aspectj注解信息,大概就是看看哪些类上面标注了带有aspectj这样的注解信息,然后解析出来进行后面的处理

AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

继续进入这个方法吧,我们最终来到下面的这段代码中

@Nullable
	public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry,
			@Nullable Object source) {
		return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
	}

说白了,通过这个方法,最终可以读取到AOP配置类的元信息并将和后面的生成的代理对象做关联,而AnnotationAwareAspectJAutoProxyCreator这个类是不是就可以理解成我们上面的Hello这个类呢?继续进入这个registerOrEscalateApcAsRequired方法,我们注意到其中的二行代码
在这里插入图片描述

看到这里,是不是觉得就和上面注册Hello的过程很像呢?其实就是这么回事,最终通过传入的AspectJAwareAdvisorAutoProxyCreator然后在容器中注册一个bean了,就是说通过@EnableAspectJAutoProxy这个注解,最终为容器创建了一个AspectJAwareAdvisorAutoProxyCreator的bean

下面我们把研究的点放到AspectJAwareAdvisorAutoProxyCreator这个类上面,进入这个类,我们发现,这个类继承了AbstractAdvisorAutoProxyCreator这个类

在这里插入图片描述
调出该类的类继承关系图谱,我们惊奇的发现,这个类的顶级接口,竟然是BeanPostProcessor,这就回到了本文最开始的那一段代码
在这里插入图片描述

即最终代理对象的生成,是通过bean的后置处理器中完成,按照上一篇创建代理对象的流程,我们通过循环后置处理器找到合适的后置处理器创建代理对象,最终来到AbstractAutoProxyCreator这个类的下面的方法
在这里插入图片描述

进入下面的方法
在这里插入图片描述

通过断点继续进入下面这个方法

@Override
	protected List<Advisor> findCandidateAdvisors() {
		// Add all the Spring advisors found according to superclass rules.
		List<Advisor> advisors = super.findCandidateAdvisors();
		// Build Advisors for all AspectJ aspects in the bean factory.
		if (this.aspectJAdvisorsBuilder != null) {
			//循环找出Aspect相关的信息并封装成一个advisor
			advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
		}
		return advisors;
	}

进入这个buildAspectJAdvisors方法,该方法是从容器中获取切面的信息并保存到缓存中,我们通过断点发现,在aspectBeanNames这个集合在中已经保存了bean的名称信息,但问题是,在哪一步,切面信息被解析出来了并放到缓存的集合中了,我们通过断点的时候发现,当前这个方法进入了两次,不妨直接将断点断到此处看一下,
在这里插入图片描述

第一次,这个集合是空的,根据左边的调用栈,发现竟然是在创建bean的过程中,通过resolveBeforeInstantiation这个方法进来的,那也就是说,第一次走到这里的时候,会把带有AOP注解的类信息解析出来,然后放进上述的集合中,第二次生成代理对象的时候,就把缓存的相关信息取出来使用即可,只是走了不同的逻辑

通过断点我们看到,第一次解析到AOP的信息的时候,会通过beanName和class信息构建出注解的元信息,然后将beanName和对应的AOP通知信息放到缓存中,供后面使用

到了这里我们有个疑问,AOP的信息是在哪里解析的呢?我们知道,在aop的方法注解中,有@Before,@After,@AfterReturning等注解信息,在spring最终产生代理对象之后,调用代理对象的方法的时候,最终标注了相应注解的方法,可以获取到不同的目标方法的参数、结果等信息,那么这些注解是怎么解析的?以及在哪里解析的呢?

继续上面的步骤,按照官方的定义带有@Before、@After等这样注解的方法称之为通知,在Spring解析到AOP的类的时候,就会解析这些通知类,然后包装成一个个不同的通知对象,接着断点继续往下走,我们来到buildAspectJAdvisors方法中的下面这段代码

List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);

进入getAdvisors这个方法,该方法用于获取切面通知

在这里插入图片描述

实例化通知对象,有了方法名称和方法的元信息,aspectName以及aspectInstanceFactory工厂,就可以通过下面这个方法构建出一个个通知对象

public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
			int declarationOrderInAspect, String aspectName) {

		validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
		//找到并构建方法上的切点表达式
		AspectJExpressionPointcut expressionPointcut = getPointcut(
				candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
		if (expressionPointcut == null) {
			return null;
		}
		//实例化通知对象
		return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
				this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
	}

最关键的是最后一段代码,即这个new InstantiationModelAwarePointcutAdvisorImpl 这个对象,传入相关的参数就可以实现,我们进入这个方法,看一下到底是怎么创建出来的,我们最终来到下面的这个getAdvice方法,在这个方法中,根据解析到的注解,判断标注在方法上的注解类型,构建不同的通知对象
在这里插入图片描述
通知对象创建完毕并放进集合中以后,继续往下执行,返回到wrapIfNecessary方法中,走到下面的这个地方,通过getAdvicesAndAdvisorsForBean这个方法判断是否能够拿到通知信息
在这里插入图片描述
其实就是上一步解析得到的各类通知对象,
在这里插入图片描述
如果判断不为空,就通过createProxy具体创建代理对象,下面就具体来看创建代理对象逻辑,
在这里插入图片描述
最终走到下面的方法创建代理对象

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}

由于本例中我们的被代理的类是实现了接口的形式,因此最终会通过JDK动态代理的方式创建一个代理对象

在这里插入图片描述
到这里,我们通过源码简单调试了一下AOP创建代理对象的完整的流程,下面是整个调试过程中主要走过的代码块的调用链路,可酌情参考
在这里插入图片描述

发布了193 篇原创文章 · 获赞 113 · 访问量 23万+

猜你喜欢

转载自blog.csdn.net/zhangcongyi420/article/details/103722352