SpringBoot之集成SpringAOP分析

Posted 木叶之荣

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot之集成SpringAOP分析相关的知识,希望对你有一定的参考价值。

我们在之前的文章中简单的分析过SpringAOP和Spring的整合过程(Spring系列之Spring框架和SpringAOP集成过程分析(十)),我们在这篇文章中简单的分析一下SpringBoot整个SpringAOP的过程。
如果我们要在SpringBoot中使用SpringAOP我们需要哪些准备步骤呢?就一步:在你的应用中引入SpringBoot提供的aop-starter即可:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

为什么我们这样操作之后就可以在SpringBoot中畅快的使用SpringAOP了呢?我们在说SpringBoot的时候,总会念念不忘它自动注入功能的强大,而我们之所以可以在SpringBoot中如果简单的使用SpringAOP,也是依赖于此特性。
在SpringBoot的autoconfigure中有这样一个类:AopAutoConfiguration。从这个类的名字我们可以推测出这个类应该是用来完成AOP自动注入的。我们来看一下这个类的内容:

@Configuration
@ConditionalOnClass( EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
		AnnotatedElement.class )
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration 

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = false)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
	public static class JdkDynamicAutoProxyConfiguration 

	

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = true)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
	public static class CglibAutoProxyConfiguration 

	

从这个类中我们可以看到如下内容:

  1. 这个类上被添加了@Configuration注解。
  2. 这个类上添加了@ConditionalOnClass注解。说明这个类要想生效需要满足ConditionalOnClass中的条件,即需要在Classpath中存在EnableAspectJAutoProxy.class, Aspect.class, Advice.class,AnnotatedElement.class。
  3. 这个类上添加了@ConditionalOnProperty这个注解,这个注解限制了这个类要生效的条件,即spring.aop.auto这个属性为true。
  4. 这个类中定义了JdkDynamicAutoProxyConfiguration和CglibAutoProxyConfiguration这两个内部类,这两个内部类上同样有@Configuration和@ConditionalOnProperty注解,并且有@EnableAspectJAutoProxy这个注解,这个注解是AOP自动注入一个很关键的注解,我们在下面会分析。
    那么AopAutoConfiguration这个类是在什么时候被加载的呢?它的加载过程又是怎么样的呢?
    整个的加载过程比较复杂,我们这里只说几个重点的部分:在我们的SpringBoot的应用启动类上,我们通常会添加@SpringBootApplication这个注解,在这个注解上面又使用了@EnableAutoConfiguration这个注解,在@EnableAutoConfiguration这个注解上面又使用了@Import(AutoConfigurationImportSelector.class)这个注解。在SpringBoot启动Spring容器的时候,就会解析到我们上面提到的@Import(AutoConfigurationImportSelector.class)这个注解,在@Import这个注解中有如下说明:
public @interface Import 

	/**
	 * @link Configuration, @link ImportSelector, @link ImportBeanDefinitionRegistrar
	 * or regular component classes to import.
	 */
	Class<?>[] value();

翻译过来是我们可以在@Import这个注解中指定ImportSelector的实现类,ImportBeanDefinitionRegistrar的实现类,或者其他我们需要引入的Component类。而AutoConfigurationImportSelector就是一个ImportSelector的实现类,所以在SpringBoot的启动过程中会实例化AutoConfigurationImportSelector并调用它的selectImports方法类进行一些特殊的处理。那么我们就去AutoConfigurationImportSelector#selectImports中一探究竟。首先我们先看一下AutoConfigurationImportSelector的类图:

AutoConfigurationImportSelector实现了 DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,BeanFactoryAware, EnvironmentAware, Ordered这几个接口,这几个结论的作用就先不一一说明了,注意的是它实现了ImportSelector这个接口。它的selectImports方法内容如下:

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) 
		if (!isEnabled(annotationMetadata)) 
			return NO_IMPORTS;
		
		try 
			//这个方法的作用是从META-INF/spring-autoconfigure-metadata.properties中加载一些配置项
			AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
					.loadMetadata(this.beanClassLoader);
			//根据传进来的注解元数据获取属性信息
			AnnotationAttributes attributes = getAttributes(annotationMetadata);
			//从spring.factories中加载key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置信息
			List<String> configurations = getCandidateConfigurations(annotationMetadata,
					attributes);
			//去掉混合的配置信息 用LinkedHashSet做的
			configurations = removeDuplicates(configurations);
			//配置项排序
			configurations = sort(configurations, autoConfigurationMetadata);
			//满足annotationMetadata中的exclude方法条件的类
			Set<String> exclusions = getExclusions(annotationMetadata, attributes);
			checkExcludedClasses(configurations, exclusions);
			//移除掉不符合条件的类
			configurations.removeAll(exclusions);
			//过滤掉不符合条件的类 主要逻辑是configurations中的元素+.ConditionalOnClass为key,然后
			//判断其对应的value是否可以被当前类加载器加载
	//如org.springframework.boot.autoconfigure.aop.AopAutoConfiguration.ConditionalOnClass=
	//org.springframework.context.annotation.EnableAspectJAutoProxy,
	//org.aspectj.lang.annotation.Aspect,
	//org.aspectj.lang.reflect.Advice,org.aspectj.weaver.AnnotatedElement
			//则判断EnableAspectJAutoProxy Aspect AnnotatedElement这三个类是否能被当前类加载器加载 
			//如果能则 AopAutoConfiguration被保留 否则被过滤掉
			//当我们在引入spring-boot-starter-aop这个Maven依赖的时候,SpringAOP相关的jar就被引入进来了
			//所以在最终的结果中可能会有AopAutoConfiguration这个类的
			configurations = filter(configurations, autoConfigurationMetadata);
			fireAutoConfigurationImportEvents(configurations, exclusions);
			//返回最终符合条件的EnableAutoConfiguration对应的类
			return StringUtils.toStringArray(configurations);
		
		catch (IOException ex) 
			throw new IllegalStateException(ex);
		
	

在调用selectImports时方法的调用链信息如下所示:

我们在上面说过通过调用selectImports这个方法是会获取到AopAutoConfiguration这个类的,那么接下来的处理过程又是怎么样的呢?我们根据方法的调用链进行分析,接下来会调用org.springframework.context.annotation.ConfigurationClassParser#processImports这个方法。这个方法的信息我们先不具体的分析了,先聚焦关注下面这一段代码:

this.importStack.registerImport(currentSourceClass.getMetadata(),candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));

具体要看的是processConfigurationClass这个方法,processConfigurationClass这个方法在SpringBoot启动的过程中是要被反复递归调用的,在这个方法中主要调用了doProcessConfigurationClass这个方法,这是一个很关键很关键的一个方法,我们以后要专门分析一下。

	protected void processConfigurationClass(ConfigurationClass configClass) throws IOException 
		//如果当前的configClass不满足被加载的条件的话,会进行跳过 具体的验证逻辑很多 主要是Conditional相关的内容 以后慢慢分析
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) 
			return;
		
		//判断configClass是否已经被加载过了
		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
		if (existingClass != null) 
			if (configClass.isImported()) 
				if (existingClass.isImported()) 
					existingClass.mergeImportedBy(configClass);
				
				// Otherwise ignore new imported config class; existing non-imported class overrides it.
				return;
			
			else 
				// Explicit bean definition found, probably replacing an import.
				// Let's remove the old one and go with the new one.
				this.configurationClasses.remove(configClass);
				this.knownSuperclasses.values().removeIf(configClass::equals);
			
		
		// Recursively process the configuration class and its superclass hierarchy.
		SourceClass sourceClass = asSourceClass(configClass);
		do 
			//这是一个递归调用的过程
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		
		while (sourceClass != null);

		this.configurationClasses.put(configClass, configClass);
	

在调用doProcessConfigurationClass的时候会调用processMemberClasses这个方法,processMemberClasses主要是用来处理一个类的内部类或者接口的。其内容如下:

	private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException 
		//获取sourceClass所代表的类的内部类和接口 有点绕。。。 configClass和sourceClass都是对所以解析的类做了包装
		Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
		if (!memberClasses.isEmpty()) 
			List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
			for (SourceClass memberClass : memberClasses) 
				//判断内部类是否满足所以解析的条件 如这个内部类上是否有@Configuration注解或者
				//是否有Component ComponentScan Import ImportResource注解或者其方法是否有@Bean注解
				if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
						!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) 
					candidates.add(memberClass);
				
			
			OrderComparator.sort(candidates);
			for (SourceClass candidate : candidates) 
				if (this.importStack.contains(configClass)) 
					this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
				
				else 
					this.importStack.push(configClass);
					try 
						//这里又调用了processConfigurationClass方法,是一个递归分析的过程 很绕
						processConfigurationClass(candidate.asConfigClass(configClass));
					
					finally 
						this.importStack.pop();
					
				
			
		
	

在下篇文章中做个总结。

以上是关于SpringBoot之集成SpringAOP分析的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot之集成SpringAOP分析(续)

SpringBoot之集成SpringAOP分析(续)

Spring系列之Spring框架和SpringAOP集成过程分析

Spring系列之Spring框架和SpringAOP集成过程分析

Spring系列之Spring框架和SpringAOP集成过程分析

Springboot源码分析之EnableAspectJAutoProxy